TutorialsPush Lab

4  Load and Measure

Goal

Move weights between the carts, compare their response, and keep readings after the motion has changed.

Drag a weight onto a cart. N selects weights; 1 and 2 load the selected weight, and Delete returns it. Space pushes; T inspects tickets; H closes the sheet.

Push Lab, Chapter 4 finishedMyth project

Open the downloaded file with File › Open Project... in Myth Engine.

4.1 The Idea

A weight leaves the rack and settles above Blue Cart. Give both carts the same push again. Blue answers differently now.

Last chapter, the carts had different masses that you could not rearrange. This chapter puts that difference in your hands. A cart’s mass, measured in kilograms, includes its body and the weights it carries.

The same impulse produces a smaller change in velocity when that mass is larger. You have already seen Copper lag behind Blue. Now you can change which cart carries the load and test your own prediction.

The gauges watch the carts as they move. A paper keeps what happened earlier. Think of a speedometer beside a photograph of its reading: one changes with the car, while the other keeps its moment.

Those two jobs must stay separate. A weight transfer stops the affected cart and ends its current comparison. An old paper still describes the push that happened before you moved the weight.

Each cart has named places for its load, called seats. A table remembers which weight occupies each seat. The pictures follow that arrangement; the native cart mass comes from the same record.

4.2 The Plan

Weight Pattern describes the rack. Prepare Workshop Weights creates its copies, and Select Workshop Weight gives each one an identity in Workshop Weights.

Transfer Workshop Weight changes that arrangement. It tells each affected cart that its load changed, and Change Native Cart Load updates the native mass.

Push Workshop Pair still measures the impulse itself. Record Pair Reading stores the result. The gauges follow current velocity; the printer and tickets read saved rows.

4.3 Set Up

Continue with your shared push controls and working Return button. Joining here? Download the Chapter 3 starting workshop, then open it through File > Open Project…. The finished Chapter 4 download above contains this chapter’s completed work. Keep the existing Game Settings. Add the new Commands and Project Globals from this setup card.

Leave the new runtime tables empty where their cards have no rows. The workshop will fill them when it runs. Enter the tuning rows now.

Make the new Object resources before placing the copies listed below. Weights and pinned tickets are created by their pattern Logic, so leave those out of the Scene. Their repeated positions belong to the tables.

Set upWhat this chapter adds
ART (sprites, from the starter kit)
Workshop needle
Workshop weight-1
Velocity Calibration
Printed Observation Paper
COMMANDS (Game Settings > Input Manager)
Next WeightButton
KeybindingsN
Load BlueButton
Keybindings1
Load CopperButton
Keybindings2
Return WeightButton
KeybindingsDelete
Inspect TicketButton
KeybindingsT
GLOBAL VARIABLES
$readingCapacityNumberstarts at 40
$selectedWeightNumberNumberstarts at 0
$weightCountNumberstarts at 0
$preparingWeightMassNumberstarts at 1
$preparingRackXNumberstarts at 0
$preparingRackYNumberstarts at 0
$preparingHeightNumberstarts at 1
$blueReadingValidBooleanstarts at false
$copperReadingValidBooleanstarts at false
$inspectedPushNumberstarts at 0
$preparingTicketSlotNumberstarts at 0
SCENE Lab: PLACE THESE
ObjectXY
Blue Live Gauge1.87510.2
with Z-Index = 7
Copper Live Gauge1.8757.45
with Z-Index = 7
Printer Reading21.62.8
with Z-Index = 25
Historical Blue Reading1212.25
with Z-Index = 25
Historical Copper Reading129
with Z-Index = 25
History Note125
with Z-Index = 25

This chapter has a longer quiet stretch. The records, transfer rules and weight Objects need one another before the rack can fill. The first running checkpoint comes after Game Rules is connected.

Keep Making what a card shows nearby for For Each Row, Declare Variable and component bindings. Make the Functions before choosing them as Actions in another resource.

4.4 Give the Bench Its Records

4.4.1 Push Readings Entry

A paper needs more than a final speed. It needs the mass and impulse that produced the change, along with both velocity readings. Push Readings Entry gives that saved observation its fields.

Keep the two cart names distinct. Each row will hold their answers to the same accepted push.

Data StructurePush Readings Entry

One row of the "Push Readings" Data Table.

batchNumberdefault 0
blueMassNumberdefault 0
blueAppliedImpulseNumberdefault 0
blueBeforeNumberdefault 0
blueAfterNumberdefault 0
blueDeltaNumberdefault 0
copperMassNumberdefault 0
copperAppliedImpulseNumberdefault 0
copperBeforeNumberdefault 0
copperAfterNumberdefault 0
copperDeltaNumberdefault 0

4.4.2 Push Readings

The printer starts without a measurement to print. Create Push Readings with the structure above and leave its rows empty. Accepted pushes will add the observations.

Data TablePush ReadingsData Structure: Push Readings Entry

4.4.3 Workshop Weights Entry

A weight can look the same as its neighbor while belonging to a different cart. Workshop Weights Entry records its identity, mass and occupied place. The picture cannot tell you which matching weight occupies a particular seat on a particular cart.

Data StructureWorkshop Weights Entry

One row of the "Workshop Weights" Data Table.

numberNumberdefault 0
cartNumberdefault 0
seatNumberdefault 0
massNumberdefault 0

4.4.4 Workshop Weights

Workshop Weights is the live arrangement of the load. Leave this table empty too. Each generated weight will register itself when it appears.

Data TableWorkshop WeightsData Structure: Workshop Weights Entry

4.4.5 Weight Pattern Entry

The rack has repeated stacks, with room between them for your pointer. Weight Pattern Entry describes one stack. Its fields say which picture to create and how to space the copies.

Data StructureWeight Pattern Entry

One row of the "Weight Pattern" Data Table.

objectTextdefault (empty)
massNumberdefault 1
copiesNumberdefault 4
rackXNumberdefault 1.425
rackYNumberdefault 3.825
stackRiseNumberdefault 0.12
initialCopperNumberdefault 0
heightNumberdefault 0.8

4.4.6 Weight Pattern

The same weight Object can appear in separate stacks. Each stack gets a row. Match the row names in the Entry cells, then fill their columns.

The object column holds the exact resource name as text. Before running the pattern, make the weight resources that its object column names exactly. The initial load belongs to these rows too. A long decimal tail on rackY comes from the stored coordinate calculation. Enter the card’s value. Those extra digits do not describe a more precise physical apparatus.

Data TableWeight PatternData Structure: Weight Pattern Entry
Row 0One Kilogram Left
objectOne Kilogram Weight
mass1
copies2
rackX1.1
rackY4.1000000000000005
stackRise0.1
initialCopper0
height0.8
Row 1One Kilogram Right
objectOne Kilogram Weight
mass1
copies2
rackX2.125
rackY4.1000000000000005
stackRise0.1
initialCopper0
height0.8
Row 2Two Kilograms
objectTwo Kilogram Weight
mass2
copies4
rackX3.15
rackY4.175
stackRise0.1
initialCopper2
height0.95
Row 3Five Kilograms Left
objectFive Kilogram Weight
mass5
copies2
rackX4.125
rackY4.25
stackRise0.1
initialCopper1
height1.1
Row 4Five Kilograms Right
objectFive Kilogram Weight
mass5
copies2
rackX5.15
rackY4.25
stackRise0.1
initialCopper0
height1.1

4.4.7 Cart Load Settings Entry

A dropped weight needs a place above the cart rather than inside its wheels. Cart Load Settings Entry describes the seat spacing and the area where a drop counts. Those are different jobs.

Data StructureCart Load Settings Entry

One row of the "Cart Load Settings" Data Table.

capacityNumberdefault 4
firstSeatXNumberdefault -1.125
spacingNumberdefault 0.75
cartTopNumberdefault 0.35
dropHalfWidthNumberdefault 2.1
dropHalfHeightNumberdefault 1.3
dropRiseNumberdefault 0.7

4.4.8 Cart Load Settings

Create the named cart row in Cart Load Settings. Its capacity limits the occupied seats. The drop area gives your hand some room while the seat positions keep the finished load arranged.

Data TableCart Load SettingsData Structure: Cart Load Settings Entry
Row 0cart
capacity4
firstSeatX-1.125
spacing0.75
cartTop0.35
dropHalfWidth2.1
dropHalfHeight1.3
dropRise0.7

4.4.9 Reading Ticket Settings Entry

The pinned papers form another pattern. Reading Ticket Settings Entry gives their Object name and the spacing along the board. A saved slot will distinguish each copy.

Data StructureReading Ticket Settings Entry

One row of the "Reading Ticket Settings" Data Table.

objectTextdefault Reading Ticket
firstXNumberdefault 18.95
spacingNumberdefault 1.775
paperYNumberdefault 14.2

4.4.10 Reading Ticket Settings

The papers row sets that pattern’s starting place. Enter it as the named row in Reading Ticket Settings, using the structure above. The ticket Object will come later, after its reading behavior is ready.

Data TableReading Ticket SettingsData Structure: Reading Ticket Settings Entry
Row 0papers
objectReading Ticket
firstX18.95
spacing1.775
paperY14.2

The records are ready for their first weights, although the bench has no new movement yet.

4.5 Let the Load Change the Cart

Now the weights will count as mass.

4.5.1 Calculate Cart Load

Copper’s load should come from the weights actually attached to Copper. Calculate Cart Load walks the live arrangement and adds the matching weights to the body’s base mass. It returns a mass, without moving the cart.

Look for this cart’s weights. The caller decides what a change in that answer should do.

FunctionCalculate Cart Load

Returns the base cart mass plus registered weights attached to cartNumber. A nonpositive base uses 1 kg. Callers decide whether to retain motion or start a new comparison.

PARAMETERS
NameTypeStarts at
cartNumberNumber1
baseMassNumber1
RETURN TYPENumber
ACTIONS
1Declare Variable
Name
loadedMass
Value
baseMass
2IfloadedMass <= 0
Then
3Change Variable
Variable
loadedMass
Value
1
4For Each Row
Table
Workshop Weights
Variable Name
"load"
5Ifload.cart == cartNumber
Then
6Change Variable
Variable
loadedMass
Operator
+=
Value
load.mass
7Return
Value
loadedMass

4.5.2 Set Starting Cart Load

Opening the workshop and restoring a saved experiment both need the mass to match the load. Set Starting Cart Load asks for that total and applies it. Restored velocity stays alone.

Prepare Workshop Weights will send weightsPrepared after the rack is ready. The Load Event is for Myth’s saved runtime state, rather than the later portable setup files.

Logic ComponentSet Starting Cart LoadAttach to: Blue Cart, Copper Cart

Derives native mass from baseMass and the saved live load arrangement at opening and after Load; keeps restored velocity.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
cartNumberNumberConstant1
baseMassNumberConstant1
On Weights Prepared
WhenweightsPreparedCustom Event
1Calculate Cart LoadFunction
Action Name
startingMass
cartNumber
@self.cartNumber
baseMass
@self.baseMass
2Change Component Value
Component Value
self.transform.mass
Value
startingMass.value
On Load:succeeded
Whenload:succeededCustom Event
1Calculate Cart LoadFunction
Action Name
restoredMass
cartNumber
@self.cartNumber
baseMass
@self.baseMass
2Change Component Value
Component Value
self.transform.mass
Value
restoredMass.value

4.5.3 Change Native Cart Load

Taking a weight off a moving cart ends that cart’s comparison. Change Native Cart Load receives cartLoadChanged, applies the new total and stops the affected body. Its latest reading becomes invalid.

The other cart keeps rolling. Earlier rows in Push Readings keep their original measurements too. This Component supplies the cartNumber and baseMass values that the starting-load Component also reads.

Logic ComponentChange Native Cart LoadAttach to: Blue Cart, Copper Cart

Changes this exact native cart mass and stops its affected comparison; the other cart and historical records stay unchanged.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
cartNumberNumberConstant1
baseMassNumberConstant1
On Cart Load Changed
WhencartLoadChangedCustom Event
1Calculate Cart LoadFunction
Action Name
changedMass
cartNumber
@self.cartNumber
baseMass
@self.baseMass
2Change Component Value
Component Value
self.transform.mass
Value
changedMass.value
3Set Velocity
X Velocity
0
Y Velocity
0
4If@self.cartNumber == 1
Then
5Change Variable
Variable
$blueReadingValid
Value
false
6Otherwise
7Change Variable
Variable
$copperReadingValid
Value
false

4.5.4 Blue Cart

Blue gains both load Components, in the shown attachment order. They share this cart’s existing identity. Keep its lane and drag behavior as they were.

An attachment that uses an existing Variable reads the same Object value. There is no second cartNumber to add.

ObjectBlue CartAddDelete
Transform
Bounding Box > SizeX 3.6, Y 0.9
Sprite
Z Index Within Object3
SpriteBlue Cart Art
Transform > SizeX 3.6, Y 0.9
Collider
Shape and Transform > SizeX 3.6, Y 0.9
Logic
Place Cart on Rail
Identify Workshop Cart
Set Starting Cart Load
Change Native Cart Load
Place Cart on Rail > Components > Collider (Collider)Collider
Identify Workshop Cart > Components > Collider (Collider)Collider
Shared Variables
homeX > TypeNumber
homeX > KindConstant
homeX > Value6.75
laneY > TypeNumber
laneY > KindConstant
laneY > Value10.975
leftLimit > TypeNumber
leftLimit > KindConstant
leftLimit > Value5.975
rightLimit > TypeNumber
rightLimit > KindConstant
rightLimit > Value20.875
cartNumber > TypeNumber
cartNumber > KindConstant
cartNumber > Value1

4.5.5 Copper Cart

Copper’s painted load is about to become movable weights. Remove the marked Rear Load, Middle Load and Front Load Sprites. The generated weights will take their places.

Keep Copper’s lower lane and identity while adding the two load Components. Until generation is connected, that bare cart is the expected picture.

ObjectCopper CartAddDelete
Transform
Bounding Box > SizeX 3.6, Y 0.9
Physics > Mass10
Sprite
Z Index Within Object3
SpriteCopper Cart Art
Transform > SizeX 3.6, Y 0.9
Collider
Shape and Transform > SizeX 3.6, Y 0.9
Logic
Place Cart on Rail
Identify Workshop Cart
Set Starting Cart Load
Change Native Cart Load
Place Cart on Rail > Components > Collider (Collider)Collider
Identify Workshop Cart > Components > Collider (Collider)Collider
Sprite(additional component)
NameRear Load
SpriteWorkshop weight-2
Z Index Within Object6.04
Transform > PositionX -1.125, Y 0.825
Transform > SizeX 0.625, Y 0.95
Sprite(additional component)
NameMiddle Load
SpriteWorkshop weight-2
Z Index Within Object6.05
Transform > PositionX -0.375, Y 0.825
Transform > SizeX 0.625, Y 0.95
Sprite(additional component)
NameFront Load
SpriteWorkshop weight-5
Z Index Within Object6.08
Transform > PositionX 0.375, Y 0.9
Transform > SizeX 0.75, Y 1.1
Shared Variables
homeX > TypeNumber
homeX > KindConstant
homeX > Value6.75
laneY > TypeNumber
laneY > KindConstant
laneY > Value8.2
leftLimit > TypeNumber
leftLimit > KindConstant
leftLimit > Value5.975
rightLimit > TypeNumber
rightLimit > KindConstant
rightLimit > Value20.875
cartNumber > TypeNumber
cartNumber > KindConstant
cartNumber > Value2

Both carts can answer a load change while keeping the rail behavior you already built.

4.6 Carry One Weight

The next pieces agree on where a weight belongs.

4.6.1 Transfer Workshop Weight

A weight leaves its old cart only when the requested destination can receive it. Transfer Workshop Weight checks the destination and finds a free seat before committing the move. A refused request leaves its place alone.

This is the chapter’s longest transfer rule. The search for an available seat occupies the opening stretch, ending at Action 15. The move occupies Actions 16 through 33; the affected-cart messages follow at Actions 34 through 41. Its Return reports success.

Destination zero means the rack; one means Blue, and two means Copper. freeSeat zero means no room; minus one marks the rack route. Later, a held weight uses cart minus one until release. Those values describe different jobs.

The rack is a destination too. Returning there detaches the picture from its cart and restores its own rack coordinates.

FunctionTransfer Workshop Weight

Moves the exact weight to its requested cart or original rack place. A free preferred seat wins; otherwise the first free seat is used. Full or missing destinations leave its current place unchanged. Returns whether the requested placement was accepted; callers choose the feedback.

PARAMETERS
NameTypeStarts at
weightInstance Setundefined
numberNumber0
destinationNumber0
rackXNumber0
rackYNumber0
heightNumber0
preferredSeatNumber0
RETURN TYPEBoolean
ACTIONS
1Declare Variable
Name
accepted
Type
Boolean
Value
false
2Declare Variable
Name
oldCart
Value
data("Workshop Weights","Weight " + number,"cart")
3Declare Variable
Name
freeSeat
Value
0
4Declare Variable
Name
seatCount
Value
round(data("Cart Load Settings","cart","capacity"))
5Ifdestination == 0
Then
6Change Variable
Variable
freeSeat
Value
-1
7Otherwise
8RepeatseatCounttimes
Each time
9Declare Variable
Name
candidateSeat
Value
local.index + 1
10Declare Variable
Name
occupied
Value
0
11For Each Row
Table
Workshop Weights
Variable Name
"occupant"
12Ifoccupant.cart == destination && occupant.seat == candidateSeat
Then
13Change Variable
Variable
occupied
Value
1
14Ifoccupied == 0 && (freeSeat == 0 || candidateSeat == preferredSeat)
Then
15Change Variable
Variable
freeSeat
Value
candidateSeat
16IfoldCart == destination
Then
17Change Variable
Variable
$pushStatus
Value
'That weight is already there.'
18Change Variable
Variable
accepted
Value
true
19Otherwise
20IffreeSeat != 0 && (destination == 0 || (destination == 1 && $blueCart.count == 1) || (destination == 2 && $copperCart.count == 1))
Then
21Detach from Parent
Applies to
weight
22Ifdestination == 0
Then
23Set Position
Applies to
weight
X
rackX
Y
rackY
24Otherwise
25Ifdestination == 1
Then
26Attach to Parent
Applies to
weight
Parent
$blueCart
Keep World Position
false
Local X
data("Cart Load Settings","cart","firstSeatX") + (freeSeat - 1) * data("Cart Load Settings","cart","spacing")
Local Y
data("Cart Load Settings","cart","cartTop") + height / 2
Destroy with Parent
false
27Otherwise
28Attach to Parent
Applies to
weight
Parent
$copperCart
Keep World Position
false
Local X
data("Cart Load Settings","cart","firstSeatX") + (freeSeat - 1) * data("Cart Load Settings","cart","spacing")
Local Y
data("Cart Load Settings","cart","cartTop") + height / 2
Destroy with Parent
false
29Set Data Cell
Table
Workshop Weights
Row
"Weight " + number
Column
"cart"
Value
destination
30Ifdestination == 0
Then
31Set Data Cell
Table
Workshop Weights
Row
"Weight " + number
Column
"seat"
Value
0
32Otherwise
33Set Data Cell
Table
Workshop Weights
Row
"Weight " + number
Column
"seat"
Value
freeSeat
34IfoldCart == 1
Then
35Trigger Custom Event on Object
Applies to
$blueCart
Custom Event
cartLoadChanged
36IfoldCart == 2
Then
37Trigger Custom Event on Object
Applies to
$copperCart
Custom Event
cartLoadChanged
38Ifdestination == 1
Then
39Trigger Custom Event on Object
Applies to
$blueCart
Custom Event
cartLoadChanged
40Ifdestination == 2
Then
41Trigger Custom Event on Object
Applies to
$copperCart
Custom Event
cartLoadChanged
42Change Variable
Variable
$pushStatus
Value
'Load changed. Push for a fresh comparison.'
43Change Variable
Variable
accepted
Value
true
44Otherwise
45Change Variable
Variable
$pushStatus
Value
'That cart is full or unavailable.'
46Return
Value
accepted

4.6.2 Place Weight From Hand

Your hand can let go above a full cart. Place Weight From Hand asks the transfer rule for that drop, then restores the previous place if the destination refuses it. The weight has somewhere to return.

A deliberate accepted drop can click. Interruption recovery stays silent, so returning to the workshop does not sound like another experiment.

FunctionPlace Weight From Hand

Places a released weight at its requested seat or restores its previous place. Only a deliberate drop opts into the quiet click; interruption recovery is silent.

PARAMETERS
NameTypeStarts at
weightInstance Setundefined
numberNumber0
destinationNumber0
rackXNumber0
rackYNumber0
heightNumber0
preferredSeatNumber0
previousCartNumber0
previousSeatNumber0
playClickBooleanNo
ACTIONS
1Ifdata("Workshop Weights","Weight " + number,"cart") == -1
Then
2Transfer Workshop WeightFunction
Action Name
placedWeightRequest1
weight
weight
number
number
destination
destination
rackX
rackX
rackY
rackY
height
height
preferredSeat
preferredSeat
3Play Workshop ClickFunction
accepted
placedWeightRequest1.value && playClick
4Ifdata("Workshop Weights","Weight " + number,"cart") == -1
Then
5Transfer Workshop WeightFunction
Action Name
placedWeightRequest2
weight
weight
number
number
destination
previousCart
rackX
rackX
rackY
rackY
height
height
preferredSeat
previousSeat
6Play Workshop ClickFunction
accepted
placedWeightRequest2.value && playClick
7Ifdata("Workshop Weights","Weight " + number,"cart") == -1
Then
8Transfer Workshop WeightFunction
Action Name
rackWeightRequest3
weight
weight
number
number
destination
0
rackX
rackX
rackY
rackY
height
height
preferredSeat
0
9Play Workshop ClickFunction
accepted
rackWeightRequest3.value && playClick

4.6.3 Show Weight Selection

The selected weight gets a tint so you can find it again among its neighbors. Show Weight Selection compares the saved selection with this weight’s number. Looking at a weight changes no load.

The status also names that selected mass on the optional readings page, beside the other experiment feedback.

FunctionShow Weight Selection

Shows one saved weight selection with a tunable Sprite tint and native status caption.

PARAMETERS
NameTypeStarts at
weightObject
Compatibility ModeStructural
Required Components
Slot Nametransform
Component TypeTransform
Slot Namesprite
Component TypeSprite
numberNumber0
massNumber1
selectedTintColor#8fdcff
ACTIONS
1If$selectedWeightNumber == number && number > 0
Then
2Change Component Value
Applies to
weight
Component Value
weight.sprite.tint
Value
selectedTint
3Change Variable
Variable
$pushStatus
Value
"Selected " + mass + " kg weight " + number + "."
4Otherwise
5Change Component Value
Applies to
weight
Component Value
weight.sprite.tint
Value
"#ffffff"

4.6.4 Select Workshop Weight

A new weight needs an identity. Select Workshop Weight records that identity and its starting information in the live table. Its Mouse Press chooses this exact copy.

A changed selection refreshes the tint. Load refreshes it too, while Destroy clears a selection that has lost its weight. The shared pressSelected name still belongs to one Object value.

Declare the card’s Sprite reference through Components > Add Component > Sprite, with Name sprite. The self. prefix appears automatically. Move Selected Weight needs that reference too. Each weight Object below will bind both references to its attached picture component.

Logic ComponentSelect Workshop WeightAttach to: One Kilogram Weight, Two Kilogram Weight, Five Kilogram Weight

Selects the exact pressed generated weight and shows the same saved selection on the bench. Selection does not change cart mass, motion or readings.

Attach toAny Object
COMPONENTS
self.spriteSprite
PER INSTANCE VARIABLES
NameTypeStarting Value
weightNumberNumber0
pressSelectedBooleanNo
rackXNumber0
rackYNumber0
weightHeightNumber0
When Created
WhenCreate
1Change Variable
Variable
$weightCount
Operator
+=
Value
1
2Change Variable
Variable
weightNumber
Value
$weightCount
3Control Data Row
Action Name
addedRow
Table
Workshop Weights
Row
"Weight " + self.weightNumber
Values > number
self.weightNumber
Values > cart
0
Values > seat
0
Values > mass
$preparingWeightMass
4Change Variable
Variable
rackX
Value
$preparingRackX
5Change Variable
Variable
rackY
Value
$preparingRackY
6Change Variable
Variable
weightHeight
Value
$preparingHeight
When the Left Mouse Button Is Pressed
WhenMouse PressMouse button: left
1Ifself.pressSelected == true
Then
2Change Variable
Variable
$selectedWeightNumber
Value
self.weightNumber
On Variable Changed · Selected Weight Number
WhenVariable ChangedGlobal Variable: selectedWeightNumber
1Show Weight SelectionFunction
weight
self
number
self.weightNumber
mass
data("Workshop Weights","Weight " + self.weightNumber,"mass")
On Load:succeeded
Whenload:succeededCustom Event
1Show Weight SelectionFunction
weight
self
number
self.weightNumber
mass
data("Workshop Weights","Weight " + self.weightNumber,"mass")
When Destroyed
WhenDestroy
1If$selectedWeightNumber == self.weightNumber
Then
2Change Variable
Variable
$selectedWeightNumber
Value
0
3Change Variable
Variable
$pushStatus
Value
'Selected weight removed. N selects another weight.'

4.6.5 Move Selected Weight

The keyboard can place the same weight your pointer selects. Move Selected Weight listens for the load and rack Commands, then checks this copy’s saved identity. Only the selected copy answers.

Each route calls the transfer Function. Removing a weight also removes its registry row, so later selection cannot find a vanished load.

Logic ComponentMove Selected WeightAttach to: One Kilogram Weight, Two Kilogram Weight, Five Kilogram Weight

Moves this weight only when its saved number is selected. Keys 1 and 2 choose a cart; Delete returns it to the rack.

Attach toAny Object
COMPONENTS
self.spriteSprite
PER INSTANCE VARIABLES
NameTypeStarting Value
weightNumberNumber0
pressSelectedBooleanNo
rackXNumber0
rackYNumber0
weightHeightNumber0
When Load Blue Is Pressed
WhenCommand PressedCommand: Load Blue
1If$selectedWeightNumber > 0 && $selectedWeightNumber == self.weightNumber && dataFind("Workshop Weights","cart",-1) < 0
Then
2Transfer Workshop WeightFunction
Action Name
blueWeightRequest1
weight
self
number
self.weightNumber
destination
1
rackX
self.rackX
rackY
self.rackY
height
self.weightHeight
3Play Workshop ClickFunction
accepted
blueWeightRequest1.value
When Load Copper Is Pressed
WhenCommand PressedCommand: Load Copper
1If$selectedWeightNumber > 0 && $selectedWeightNumber == self.weightNumber && dataFind("Workshop Weights","cart",-1) < 0
Then
2Transfer Workshop WeightFunction
Action Name
copperWeightRequest2
weight
self
number
self.weightNumber
destination
2
rackX
self.rackX
rackY
self.rackY
height
self.weightHeight
3Play Workshop ClickFunction
accepted
copperWeightRequest2.value
When Rack Weight Is Pressed
WhenCommand PressedCommand: Return Weight
1If$selectedWeightNumber > 0 && $selectedWeightNumber == self.weightNumber && dataFind("Workshop Weights","cart",-1) < 0
Then
2Transfer Workshop WeightFunction
Action Name
rackWeightRequest3
weight
self
number
self.weightNumber
destination
0
rackX
self.rackX
rackY
self.rackY
height
self.weightHeight
3Play Workshop ClickFunction
accepted
rackWeightRequest3.value
When Destroyed
WhenDestroy
1Declare Variable
Name
oldCart
Value
data("Workshop Weights","Weight " + self.weightNumber,"cart")
2Control Data Row
Action Name
addedRow
Operation
Remove
Table
Workshop Weights
Row
"Weight " + self.weightNumber
3IfoldCart == 1
Then
4Trigger Custom Event on Object
Applies to
$blueCart
Custom Event
cartLoadChanged
5IfoldCart == 2
Then
6Trigger Custom Event on Object
Applies to
$copperCart
Custom Event
cartLoadChanged

4.6.6 Drag Workshop Weight

Pick a weight near its edge. It keeps that point under your pointer as you lift it from the load. Drag Workshop Weight remembers its former place.

Cart Press Input supplies pressSelected, weightGrabOffsetX and weightGrabOffsetY at the exact press. Listen for Control Press connects each weight through Capture Control Press, using the existing supplied Script. Keep that listener on every weight Object, as its card shows. Replace the Library source in Fill the rack before testing pointer dragging; its updated helper recognizes weights.

Prepare While the Left Mouse Button Is Held with Released and Holding before filling any Switch Mode that targets it. Keep Released as the starting Mode. Its Actions can wait for the next panel.

Logic ComponentDrag Workshop WeightAttach to: One Kilogram Weight, Two Kilogram Weight, Five Kilogram Weight

Grabs the exact weight with its pointer offset. A held weight leaves its old load; release chooses the nearest available seat, and interruptions restore the previous place.

Attach toAny Object
PER INSTANCE VARIABLES
NameTypeStarting Value
weightNumberNumber0
rackXNumber0
rackYNumber0
weightHeightNumber0
weightGrabOffsetXNumber0
weightGrabOffsetYNumber0
previousCartNumber0
previousSeatNumber0
pressSelectedBooleanNo
When the Left Mouse Button Is Pressed
WhenMouse PressMouse button: left
1Ifself.pressSelected == true && dataFind("Workshop Weights","cart",-1) < 0 && ($blueCart.count == 0 || $blueCart.collider.fixed == false) && ($copperCart.count == 0 || $copperCart.collider.fixed == false)
Then
2Change Variable
Variable
previousCart
Value
data("Workshop Weights","Weight " + self.weightNumber,"cart")
3Change Variable
Variable
previousSeat
Value
data("Workshop Weights","Weight " + self.weightNumber,"seat")
4Declare Variable
Name
startX
Value
self.transform.position.x
5Declare Variable
Name
startY
Value
self.transform.position.y
6Transfer Workshop WeightFunction
Action Name
transferWorkshopWeightResult
weight
self
number
self.weightNumber
rackX
self.rackX
rackY
self.rackY
height
self.weightHeight
destination
0
7Set Data Cell
Table
Workshop Weights
Row
"Weight " + self.weightNumber
Column
"cart"
Value
-1
8Set Position
X
startX
Y
startY
9Switch Mode
Event
While the Left Mouse Button Is Held
Mode
Holding

Released starts empty. Holding follows the pointer with both grab offsets, so the weight stays beneath the place you touched.

Logic ComponentDrag Workshop Weightcontinued
While the Left Mouse Button Is Held
WhenMouse HeldMouse button: left
ModeReleased (Starting Mode)
(no Actions)
While the Left Mouse Button Is Held
WhenMouse HeldMouse button: left
ModeHolding
1Set Position
X
clamp(valueMember(event.position,"x") - self.weightGrabOffsetX,.6,23.4)
Y
clamp(valueMember(event.position,"y") - self.weightGrabOffsetY,.6,15.4)

Release asks Place Weight From Hand for the nearest available seat inside a cart’s drop area. Outside those areas, it requests the rack. A weight being held prevents another pair push.

Logic ComponentDrag Workshop Weightcontinued
When the Left Mouse Button Is Released
WhenMouse ReleaseMouse button: left
1Ifdata("Workshop Weights","Weight " + self.weightNumber,"cart") == -1
Then
2Declare Variable
Name
dropCart
Value
0
3Declare Variable
Name
dropSeat
Value
0
4If$blueCart.count == 1 && abs(valueMember(event.position,"x") - $blueCart.transform.position.x) < data("Cart Load Settings","cart","dropHalfWidth") && abs(valueMember(event.position,"y") - $blueCart.transform.position.y - data("Cart Load Settings","cart","dropRise")) < data("Cart Load Settings","cart","dropHalfHeight")
Then
5Change Variable
Variable
dropCart
Value
1
6Change Variable
Variable
dropSeat
Value
clamp(round((valueMember(event.position,"x") - $blueCart.transform.position.x - data("Cart Load Settings","cart","firstSeatX")) / data("Cart Load Settings","cart","spacing")) + 1,1,data("Cart Load Settings","cart","capacity"))
7Otherwise
8If$copperCart.count == 1 && abs(valueMember(event.position,"x") - $copperCart.transform.position.x) < data("Cart Load Settings","cart","dropHalfWidth") && abs(valueMember(event.position,"y") - $copperCart.transform.position.y - data("Cart Load Settings","cart","dropRise")) < data("Cart Load Settings","cart","dropHalfHeight")
Then
9Change Variable
Variable
dropCart
Value
2
10Change Variable
Variable
dropSeat
Value
clamp(round((valueMember(event.position,"x") - $copperCart.transform.position.x - data("Cart Load Settings","cart","firstSeatX")) / data("Cart Load Settings","cart","spacing")) + 1,1,data("Cart Load Settings","cart","capacity"))
11Place Weight From HandFunction
weight
self
number
self.weightNumber
rackX
self.rackX
rackY
self.rackY
height
self.weightHeight
destination
dropCart
preferredSeat
dropSeat
previousCart
self.previousCart
previousSeat
self.previousSeat
playClick
Yes
12Change Variable
Variable
$selectedWeightNumber
Value
self.weightNumber
13Switch Mode
Event
While the Left Mouse Button Is Held
Mode
Released

Focus Lost, Touch Cancelled, Load, Return and opening Help all restore an interrupted hand. These are the same cleanup moments you met with the carts. Each uses the same recovery call and Released destination; compare the trigger names as you repeat them. Prepare each named Event first.

Logic ComponentDrag Workshop Weightcontinued
On Focus Lost
WhenFocus Lost
1Place Weight From HandFunction
weight
self
number
self.weightNumber
rackX
self.rackX
rackY
self.rackY
height
self.weightHeight
destination
self.previousCart
preferredSeat
self.previousSeat
previousCart
self.previousCart
previousSeat
self.previousSeat
playClick
No
2Switch Mode
Event
While the Left Mouse Button Is Held
Mode
Released
On Touch Cancelled
WhenTouch Cancelled
1Place Weight From HandFunction
weight
self
number
self.weightNumber
rackX
self.rackX
rackY
self.rackY
height
self.weightHeight
destination
self.previousCart
preferredSeat
self.previousSeat
previousCart
self.previousCart
previousSeat
self.previousSeat
playClick
No
2Switch Mode
Event
While the Left Mouse Button Is Held
Mode
Released
On Load:succeeded
Whenload:succeededCustom Event
1Place Weight From HandFunction
weight
self
number
self.weightNumber
rackX
self.rackX
rackY
self.rackY
height
self.weightHeight
destination
self.previousCart
preferredSeat
self.previousSeat
previousCart
self.previousCart
previousSeat
self.previousSeat
playClick
No
2Switch Mode
Event
While the Left Mouse Button Is Held
Mode
Released
When Return Carts Is Pressed
WhenCommand PressedCommand: Return Carts
1Place Weight From HandFunction
weight
self
number
self.weightNumber
rackX
self.rackX
rackY
self.rackY
height
self.weightHeight
destination
self.previousCart
preferredSeat
self.previousSeat
previousCart
self.previousCart
previousSeat
self.previousSeat
playClick
No
2Switch Mode
Event
While the Left Mouse Button Is Held
Mode
Released
On Return Carts
WhenreturnCartsCustom Event
1Place Weight From HandFunction
weight
self
number
self.weightNumber
rackX
self.rackX
rackY
self.rackY
height
self.weightHeight
destination
self.previousCart
preferredSeat
self.previousSeat
previousCart
self.previousCart
previousSeat
self.previousSeat
playClick
No
2Switch Mode
Event
While the Left Mouse Button Is Held
Mode
Released
On Variable Changed · Details Open
WhenVariable ChangedGlobal Variable: detailsOpen
1If$detailsOpen
Then
2Place Weight From HandFunction
weight
self
number
self.weightNumber
rackX
self.rackX
rackY
self.rackY
height
self.weightHeight
destination
self.previousCart
preferredSeat
self.previousSeat
previousCart
self.previousCart
previousSeat
self.previousSeat
playClick
No
3Switch Mode
Event
While the Left Mouse Button Is Held
Mode
Released

Before moving on, compare all six recovery Events with this panel. There are two Return routes: the Command and the Custom Event. Help also needs its Details Open condition. Check their destination: Released.

4.6.7 One Kilogram Weight

The small weight gets a resource of its own. Keep the Logic attachment order on the card: the copies need their identities and press listeners before the hand can use them.

Shared Per Instance names across those attachments refer to this same running weight. Leave the starting values alone.

Select Move Selected Weight in this Object’s Logic workspace. Under Properties > Components, choose Workshop weight-1 for Sprite (Sprite). Repeat on Select Workshop Weight. That name identifies the attached Sprite component using this picture; you are connecting the component, rather than choosing artwork again. Repeat both bindings with each later weight’s own named component.

ObjectOne Kilogram Weight
Transform
Bounding Box > SizeX 0.5, Y 0.8
Sprite
Z Index Within Object6
SpriteWorkshop weight-1
Transform > SizeX 0.5, Y 0.8
Logic
Move Selected Weight
Select Workshop Weight
Listen for Control Press
Drag Workshop Weight
Move Selected Weight > Components > Sprite (Sprite)Workshop weight-1
Select Workshop Weight > Components > Sprite (Sprite)Workshop weight-1

4.6.8 Two Kilogram Weight

The next denomination is taller, but it uses the same behavior. Make Two Kilogram Weight from its card. The pattern row supplies its mass; the size of the picture does not calculate it.

ObjectTwo Kilogram Weight
Transform
Bounding Box > SizeX 0.625, Y 0.95
Sprite
Z Index Within Object6.04
SpriteWorkshop weight-2
Transform > SizeX 0.625, Y 0.95
Logic
Move Selected Weight
Select Workshop Weight
Listen for Control Press
Drag Workshop Weight
Move Selected Weight > Components > Sprite (Sprite)Workshop weight-2
Select Workshop Weight > Components > Sprite (Sprite)Workshop weight-2

4.6.9 Five Kilogram Weight

The broad weight uses those same attachments again. Match its picture and size, then leave placement to the pattern. These are resources for copies, rather than individual weights to drag into Lab.

ObjectFive Kilogram Weight
Transform
Bounding Box > SizeX 0.75, Y 1.1
Sprite
Z Index Within Object6.08
SpriteWorkshop weight-5
Transform > SizeX 0.75, Y 1.1
Logic
Move Selected Weight
Select Workshop Weight
Listen for Control Press
Drag Workshop Weight
Move Selected Weight > Components > Sprite (Sprite)Workshop weight-5
Select Workshop Weight > Components > Sprite (Sprite)Workshop weight-5

The weights have behavior. The rack still needs its maker.

4.7 Fill the Rack

The bench can now create the arrangement described by its tables.

4.7.1 Prepare Workshop Weights

The carts must register before the first weights arrive. Prepare Workshop Weights waits for its one-shot timer, then walks the pattern rows and creates their stacks. The table does the arranging.

Before creating a copy, the Logic supplies its mass and rack coordinates. Its Create Event registers the copy. A successfully created starting load can then attach to Copper.

The final weightsPrepared message tells both carts to calculate their native masses. Load restores the existing copies, including the saved identities that link them to their table rows. Keep the timer and creation work in their separate Events.

Logic ComponentPrepare Workshop WeightsAttach to: Game Rules

Starts the workshop weight pattern once after the placed exact carts have registered. Saved generated weights are restored by native Load, so Load never generates a second set.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
stackCopyLimitNumberConstant4
When Created
WhenCreate
1Start Recurring Timer
Timer Name
"weightPatternReady"
Repeat Every (s)
0.02
Ring Once
Yes
On the Weight Pattern Ready Timer
WhenRecurring TimerTimer name: weightPatternReady
1Check Data Row Exists
Action Name
seatSettings
Table
Cart Load Settings
Row
"cart"
2IfseatSettings.value
Then
3Set Data Cell
Table
Cart Load Settings
Row
"cart"
Column
"capacity"
Value
round(data("Cart Load Settings","cart","capacity"))
4Ifdata("Cart Load Settings","cart","capacity") < 1
Then
5Set Data Cell
Table
Cart Load Settings
Row
"cart"
Column
"capacity"
Value
1
6Ifdata("Cart Load Settings","cart","capacity") > 4
Then
7Set Data Cell
Table
Cart Load Settings
Row
"cart"
Column
"capacity"
Value
4
8Otherwise
9Control Data Row
Action Name
addedRow
Table
Cart Load Settings
Row
"cart"
Values > capacity
4
Values > firstSeatX
-1.125
Values > spacing
0.75
Values > cartTop
0.35
Values > dropHalfWidth
2.1
Values > dropHalfHeight
1.3
Values > dropRise
0.7
10Declare Variable
Name
seatsUsed
Value
0
11For Each Row
Table
Weight Pattern
Variable Name
"denomination"
12Declare Variable
Name
copyCount
Value
round(denomination.copies)
13IfcopyCount < 0
Then
14Change Variable
Variable
copyCount
Value
0
15IfcopyCount > @self.stackCopyLimit
Then
16Change Variable
Variable
copyCount
Value
@self.stackCopyLimit
17Set Data Cell
Table
Weight Pattern
Row
denomination.index
Column
"copies"
Value
copyCount
18Declare Variable
Name
placedCount
Value
0
19Declare Variable
Name
loadCount
Value
round(denomination.initialCopper)
20IfloadCount < 0
Then
21Change Variable
Variable
loadCount
Value
0
22IfloadCount > copyCount
Then
23Change Variable
Variable
loadCount
Value
copyCount
24IfloadCount > data("Cart Load Settings","cart","capacity") - seatsUsed
Then
25Change Variable
Variable
loadCount
Value
data("Cart Load Settings","cart","capacity") - seatsUsed
26Ifdenomination.mass <= 0
Then
27Set Data Cell
Table
Weight Pattern
Row
denomination.index
Column
"mass"
Value
1
28Repeatdenomination.copiestimes
Each time
29Change Variable
Variable
$preparingWeightMass
Value
denomination.mass
30Change Variable
Variable
$preparingRackX
Value
denomination.rackX
31Change Variable
Variable
$preparingRackY
Value
denomination.rackY + (copyCount - 1 - local.index) * denomination.stackRise
32Change Variable
Variable
$preparingHeight
Value
denomination.height
33Create Instance
Object to Create
denomination.object
X
denomination.rackX
Y
denomination.rackY + (copyCount - 1 - local.index) * denomination.stackRise
Variable Name
newWeight
34Check Exists
Action Name
weightExists
Applies to
newWeight
35IfweightExists.value && local.index < loadCount && $copperCart.count == 1
Then
36Attach to Parent
Applies to
newWeight
Parent
$copperCart
Keep World Position
No
Local X
data("Cart Load Settings","cart","firstSeatX") + seatsUsed * data("Cart Load Settings","cart","spacing")
Local Y
data("Cart Load Settings","cart","cartTop") + denomination.height / 2
Destroy with Parent
No
37Set Data Cell
Table
Workshop Weights
Row
"Weight " + $weightCount
Column
"cart"
Value
2
38Set Data Cell
Table
Workshop Weights
Row
"Weight " + $weightCount
Column
"seat"
Value
seatsUsed + 1
39Change Variable
Variable
seatsUsed
Operator
+=
Value
1
40Change Variable
Variable
placedCount
Operator
+=
Value
1
41IfweightExists.value == false
Then
42Change Variable
Variable
$pushStatus
Value
'A weight is unavailable; check Weight Pattern.'
43Set Data Cell
Table
Weight Pattern
Row
denomination.index
Column
"initialCopper"
Value
placedCount
44Trigger Custom Event
Custom Event
weightsPrepared

4.7.2 Choose Next Weight

Pressing the next-weight key should move the selection through the weights that remain. Choose Next Weight walks their registry, remembering the next identity and the first identity for wrapping.

Variable Name names the current row. The temporary firstNumber and nextNumber values are declared before that loop, so its nested changes can choose them.

Logic ComponentChoose Next WeightAttach to: Game Rules

N selects the next remaining generated weight in creation order using Workshop Weights, wrapping at the end. Destroy removes a weight from the list; Load restores it. An empty bench gives clear feedback.

Attach toAny Object
When Next Weight Is Pressed
WhenCommand PressedCommand: Next Weight
1Declare Variable
Name
nextNumber
Value
0
2Declare Variable
Name
firstNumber
Value
0
3For Each Row
Table
Workshop Weights
Variable Name
"weight"
4Declare Variable
Name
candidateNumber
Value
weight.number
5IffirstNumber == 0 || candidateNumber < firstNumber
Then
6Change Variable
Variable
firstNumber
Value
candidateNumber
7IfcandidateNumber > $selectedWeightNumber
Then
8IfnextNumber == 0 || candidateNumber < nextNumber
Then
9Change Variable
Variable
nextNumber
Value
candidateNumber
10IfnextNumber > 0
Then
11Change Variable
Variable
$selectedWeightNumber
Value
nextNumber
12Otherwise
13Change Variable
Variable
$selectedWeightNumber
Value
firstNumber
14IffirstNumber == 0
Then
15Change Variable
Variable
$pushStatus
Value
'No workshop weights remain.'

4.7.3 Game Rules

Game Rules gains the rack maker and keyboard selection. Its card also names the ticket Components you will make below. Add the two weight attachments now, keeping their shown order; return for the ticket attachments when they exist.

The placed copies can finally make their weights. Run Lab here and look for the rack and Copper’s restored load. Try N and a load key. This checkpoint uses the keyboard. Update the press helper next before testing weight dragging.

ObjectGame RulesAddDelete
Logic
Request Pair Push
Choose Next Impulse
Prepare Workshop Weights
Choose Next Weight
Prepare Reading Tickets
Choose Historical Ticket

4.7.4 Cart Press Input

The press helper now recognizes a weight above its cart. Replace its supplied source file. Keep the wiring as it was when you connected the earlier controls. Native Logic still owns movement and mass.

LibraryCart Press InputAddDelete
SOURCEfrom a file
Import .js...push-lab-cart-press-input-ch02.js
Size2.8 KB
Import .js...push-lab-cart-press-input-ch03.js
Size2.9 KB
ADVANCED WIRING AND LOAD ORDER
Alias (Lib.Ext....)cartPress
Global NameCartPressInput

4.7.5 Place Cart on Rail

A cart should wait while your hand is carrying a weight. The added guard in Place Cart on Rail checks that the weight hand is clear. Its other Events still perform the dragging and cleanup that you built for the carts earlier.

Logic ComponentPlace Cart on RailAttach to: Blue Cart, Copper CartAddDelete

Place one exact cart along its rail with the pointer, release it from rest, or return it home.

Attach toAny Object
COMPONENTS
self.colliderCollider
SHARED VARIABLES
NameTypeKindStarting Value
homeXNumberConstant6.75
laneYNumberConstant10.975
leftLimitNumberConstant5.975
rightLimitNumberConstant20.875
PER INSTANCE VARIABLES
NameTypeStarting Value
grabOffsetNumber0
handXNumber0
pressSelectedBooleanNo
When Created
WhenCreate
1Start Recurring Timer
Timer Name
"cartInputReady"
Repeat Every (s)
0.02
Ring Once
Yes
On the Cart Input Ready Timer
WhenRecurring TimerTimer name: cartInputReady
1Execute Script
Script
Capture Cart Press
When the Left Mouse Button Is Pressed
WhenMouse PressMouse button: left
1Ifself.pressSelected == true
Then
1Ifself.pressSelected == true && dataFind("Workshop Weights","cart",-1) < 0
Then
2Set Velocity
X Velocity
0
Y Velocity
0
3Change Component Value
Component Value
self.collider.fixed
Value
true
4Switch Mode
Event
While the Left Mouse Button Is Held
Mode
Holding
While the Left Mouse Button Is Held
WhenMouse HeldMouse button: left
ModeReleased (Starting Mode)
(no Actions)
While the Left Mouse Button Is Held
WhenMouse HeldMouse button: left
ModeHolding
1Change Variable
Variable
handX
Value
valueMember(event.position,"x") - self.grabOffset
2Set Position
X
clamp(self.handX, @self.leftLimit, @self.rightLimit)
Y
@self.laneY
When the Left Mouse Button Is Released
WhenMouse ReleaseMouse button: left
1Release CartFunction
cart
self
2Switch Mode
Event
While the Left Mouse Button Is Held
Mode
Released
On Focus Lost
WhenFocus Lost
1Release CartFunction
cart
self
2Switch Mode
Event
While the Left Mouse Button Is Held
Mode
Released
On Touch Cancelled
WhenTouch Cancelled
1Release CartFunction
cart
self
2Switch Mode
Event
While the Left Mouse Button Is Held
Mode
Released
On Load:succeeded
Whenload:succeededCustom Event
1Release CartFunction
cart
self
2Switch Mode
Event
While the Left Mouse Button Is Held
Mode
Released
3Execute Script
Script
Capture Cart Press
When Return Carts Is Pressed
WhenCommand PressedCommand: Return Carts
1Return Cart HomeFunction
cart
self
homeX
@self.homeX
laneY
@self.laneY
2Switch Mode
Event
While the Left Mouse Button Is Held
Mode
Released
On Return Carts
WhenreturnCartsCustom Event
1Return Cart HomeFunction
cart
self
homeX
@self.homeX
laneY
@self.laneY
2Switch Mode
Event
While the Left Mouse Button Is Held
Mode
Released
On Variable Changed · Details Open
WhenVariable ChangedGlobal Variable: detailsOpen
1If$detailsOpen
Then
2Release CartFunction
cart
self
3Switch Mode
Event
While the Left Mouse Button Is Held
Mode
Released

The same bench can now carry different loads.

4.8 Keep the Push After It Ends

An observation needs a longer life than the motion it measured.

4.8.1 Validate Inspected Push

An old ticket may point at a row the history no longer retains. Check the row before keeping the view. A missing row returns the sheet to latest readings.

It changes the view alone. It never invents an empty measurement.

FunctionValidate Inspected Push

Returns to the latest reading if the inspected historical row was removed or trimmed.

ACTIONS
1If$inspectedPush > 0
Then
2Check Data Row Exists
Action Name
inspectedRow
Table
Push Readings
Row
"Push " + $inspectedPush
3IfinspectedRow.value == false
Then
4Change Variable
Variable
$inspectedPush
Value
0

4.8.2 Record Pair Reading

A push finishes measuring both carts, then its record goes into Push Readings. Record Pair Reading keeps that pair together under its batch identity. Extra old rows leave from the beginning.

The limit comes from readingCapacity. Its next accepted push applies an edit to that capacity. The final validation keeps an inspected ticket from pointing beyond the retained history.

FunctionRecord Pair Reading

Records one complete pair measurement, trims the oldest excess rows and reports how many are kept. Project Global readingCapacity (default 40) takes effect on the next accepted push.

ACTIONS
1If$readingCapacity < 1
Then
2Change Variable
Variable
$readingCapacity
Value
1
3Change Variable
Variable
$readingCapacity
Value
round($readingCapacity)
4Control Data Row
Action Name
addedRow
Table
Push Readings
Row
"Push " + $pushBatch
Values > batch
$pushBatch
Values > blueMass
$blueMass
Values > blueAppliedImpulse
$blueAppliedImpulse
Values > blueBefore
$blueBefore
Values > blueAfter
$blueAfter
Values > blueDelta
$blueDelta
Values > copperMass
$copperMass
Values > copperAppliedImpulse
$copperAppliedImpulse
Values > copperBefore
$copperBefore
Values > copperAfter
$copperAfter
Values > copperDelta
$copperDelta
5IfdataCount("Push Readings") > $readingCapacity
Then
6RepeatdataCount("Push Readings") - $readingCapacitytimes
Each time
7Control Data Row
Action Name
addedRow2
Operation
Remove
Table
Push Readings
Row
0
8Change Variable
Variable
$pushStatus
Value
"Impulse " + $pushBatch + " recorded (" + dataCount("Push Readings") + " kept)."
9Validate Inspected PushFunction

4.8.3 Push Workshop Pair

A held weight joins the held carts as a reason to refuse a push. Add the new guard and validity changes. Both carts still receive their impulses as one accepted pair.

Record Pair Reading waits until the batch has advanced and both measured results are ready to store. That order gives the stored row the identity of this push, with both immediate measurements already complete.

FunctionPush Workshop PairAddDelete

Pushes the exact workshop pair once only when both hands are clear. A held cart rejects the whole push.

PARAMETERS
NameTypeStarts at
blueInstance Setundefined
copperInstance Setundefined
blueImpulseNumber4
copperImpulseNumber4
ACTIONS
1Ifparam:blue.count == 1 && param:copper.count == 1
Then
2Ifparam:blue.collider.fixed == false && param:copper.collider.fixed == false && dataFind("Workshop Weights","cart",-1) < 0compareparam:blue.collider.fixed == false && param:copper.collider.fixed == false
Then
3Apply Cart ImpulseFunction
Action Name
bluePush
cart
blue
impulse
blueImpulse
4Change Variable
Variable
$blueMass
Value
bluePush.mass
5Change Variable
Variable
$blueBefore
Value
bluePush.before
6Change Variable
Variable
$blueAfter
Value
bluePush.after
7Change Variable
Variable
$blueAppliedImpulse
Value
blueImpulse
8Change Variable
Variable
$blueDelta
Value
$blueAfter - $blueBefore
9Apply Cart ImpulseFunction
Action Name
copperPush
cart
copper
impulse
copperImpulse
10Change Variable
Variable
$copperMass
Value
copperPush.mass
11Change Variable
Variable
$copperBefore
Value
copperPush.before
12Change Variable
Variable
$copperAfter
Value
copperPush.after
13Change Variable
Variable
$copperAppliedImpulse
Value
copperImpulse
14Change Variable
Variable
$copperDelta
Value
$copperAfter - $copperBefore
15Change Variable
Variable
$blueReadingValid
Value
true
16Change Variable
Variable
$copperReadingValid
Value
true
17Change Variable
Variable
$pushBatch
Operator
+=
Value
1
18Change Variable
Variable
$pushStatus
Value
'Impulse applied to both carts.'
19Record Pair ReadingFunction
20Play Workshop ClickFunction
accepted
Yes
21Otherwise
22Change Variable
Variable
$pushStatus
Value
'Release the apparatus before pushing.'
Value
'Release both carts before pushing.'
23Otherwise
24Change Variable
Variable
$pushStatus
Value
'Both workshop carts are needed for Push Both.'

The observation can stay after the carts stop.

4.9 Watch Current Motion

The needles will watch the carts continuously, including the moment their bodies meet the end stops.

4.9.1 Choose Gauge Calibration

A fast cart can outgrow a small scale. Choose Gauge Calibration finds a readable range with room above the speed. Its marks follow a growing pattern: 1, 2, 5, 10, 20, 50. The pattern repeats at ten times the size, with a wanted range of 3 moving up to 5. The logarithm finds the group. Later comparisons choose its mark.

This chosen range describes the face, keeping its markings readable as the measured speed grows. Changing it applies no force to the cart.

FunctionChoose Gauge Calibration

Chooses a signed velocity scale from the readable 1, 2, 5 sequence, with headroom above the measured speed. All inputs and the result are m/s except the headroom ratio.

PARAMETERS
NameTypeStarts at
speedNumber0
minimumRangeNumber0.5
headroomNumber1.25
RETURN TYPENumber
ACTIONS
1Declare Variable
Name
wantedRange
Value
max(0.5, minimumRange, abs(speed) * max(1, headroom))
2Declare Variable
Name
decade
Value
pow(10, floor(log(wantedRange) / log(10)))
3Declare Variable
Name
calibration
Value
decade
4Ifcalibration < wantedRange - 0.000000001
Then
5Change Variable
Variable
calibration
Value
decade * 2
6Ifcalibration < wantedRange - 0.000000001
Then
7Change Variable
Variable
calibration
Value
decade * 5
8Ifcalibration < wantedRange - 0.000000001
Then
9Change Variable
Variable
calibration
Value
decade * 10
10Return
Value
calibration

4.9.2 Calibrate Recorded Gauge

The gauge begins with a scale suited to the expected push. After an observation, Calibrate Recorded Gauge considers the measured velocities around that push instead. It returns the range the face will use.

The velocity readings keep their signs, which still say whether a cart was moving right or left. Calibration needs their size, so it considers absolute speed.

FunctionCalibrate Recorded Gauge

Chooses the scale for one real paired observation, or the selected starting impulse when no observation exists. This never changes motion or a measurement.

PARAMETERS
NameTypeStarts at
cartNumberNumber1
minimumRangeNumber0.5
headroomNumber1.25
RETURN TYPENumber
ACTIONS
1Declare Variable
Name
observedSpeed
Value
0
2IfcartNumber == 1
Then
3If$pushBatch > 0
Then
4Change Variable
Variable
observedSpeed
Value
max(abs($blueBefore), abs($blueAfter))
5Otherwise
6Change Variable
Variable
observedSpeed
Value
$impulseMagnitude / max(0.001, $blueCart.transform.mass)
7Otherwise
8If$pushBatch > 0
Then
9Change Variable
Variable
observedSpeed
Value
max(abs($copperBefore), abs($copperAfter))
10Otherwise
11Change Variable
Variable
observedSpeed
Value
$impulseMagnitude / max(0.001, $copperCart.transform.mass)
12Choose Gauge CalibrationFunction
Action Name
recordedScale
speed
observedSpeed
minimumRange
minimumRange
headroom
headroom
13Return
Value
recordedScale.value

4.9.3 Update Velocity Gauge

A cart hits its stop. The needle returns toward the center, although the earlier paper still shows the push. Update Velocity Gauge reads current native velocity and points the needle at that value.

The range can expand if the motion needs more room. The Function returns that range for the next update, without altering history or driving the cart.

FunctionUpdate Velocity Gauge

Updates one live velocity instrument from an exact native cart, preserving historical readings. Calibration and speed are m/s; needleSweep is degrees.

PARAMETERS
NameTypeStarts at
cartInstance Setundefined
gaugeObject
Compatibility ModeStructural
Required Components
Slot Nametransform
Component TypeTransform
Slot Namesprite
Component TypeSprite
oldRangeNumber10
minimumRangeNumber10
needleSweepNumber10
headroomNumber1.25
RETURN TYPENumber
ACTIONS
1Declare Variable
Name
calibration
Value
oldRange
2Check Exists
Action Name
cartExists
Applies to
cart
3IfcartExists.value
Then
4Set Visible
Applies to
gauge
Visible
$detailsOpen == false
5Ifcalibration < minimumRange
Then
6Change Variable
Variable
calibration
Value
minimumRange
7Ifcalibration <= 0
Then
8Change Variable
Variable
calibration
Value
1
9Ifabs(param:cart.transform.velocity.x) > calibration
Then
10Choose Gauge CalibrationFunction
Action Name
expandedScale
speed
param:cart.transform.velocity.x
minimumRange
minimumRange
headroom
headroom
11Change Variable
Variable
calibration
Value
expandedScale.value
12Change Component Value
Component Value
gauge.sprite.transform.rotation.z
Value
-param:cart.transform.velocity.x / calibration * needleSweep
13Otherwise
14Set Visible
Applies to
gauge
Visible
false
15Return
Value
calibration

4.9.4 Follow Native Velocity

The instrument needs to watch continuously because the cart keeps moving. Follow Native Velocity watches on Step. Create and a changed push batch choose a scale around the observation.

Declare the Sprite reference before attaching this Logic. The Object’s attachment will connect that reference specifically to the separate Sprite named Velocity Needle. The fixed face will keep its own position.

Logic ComponentFollow Native VelocityAttach to: Blue Live Gauge, Copper Live Gauge

Shows one exact workshop cart's signed native velocity on its stationary calibrated instrument face. Native Text shows live speed and scale values; only its named needle rotates. Reads continuously because native velocity changes continuously.

Attach toAny Object
COMPONENTS
self.spriteSprite
SHARED VARIABLES
NameTypeKindStarting Value
cartNumberNumberConstant1
minimumRangeNumberConstant10
needleSweepNumberConstant65
scaleHeadroomNumberConstant1.25
PER INSTANCE VARIABLES
NameTypeStarting Value
gaugeRangeNumber0.5
Every Step
WhenStep
1If@self.cartNumber == 1
Then
2Update Velocity GaugeFunction
Action Name
followBlueCalibration
cart
$blueCart
gauge
self
oldRange
self.gaugeRange
minimumRange
@self.minimumRange
needleSweep
@self.needleSweep
headroom
@self.scaleHeadroom
3Change Variable
Variable
gaugeRange
Value
followBlueCalibration.value
4Otherwise
5Update Velocity GaugeFunction
Action Name
followCopperCalibration
cart
$copperCart
gauge
self
oldRange
self.gaugeRange
minimumRange
@self.minimumRange
needleSweep
@self.needleSweep
headroom
@self.scaleHeadroom
6Change Variable
Variable
gaugeRange
Value
followCopperCalibration.value
When Created
WhenCreate
1Calibrate Recorded GaugeFunction
Action Name
calibrateOpeningScale
cartNumber
@self.cartNumber
minimumRange
@self.minimumRange
headroom
@self.scaleHeadroom
2Change Variable
Variable
gaugeRange
Value
calibrateOpeningScale.value
On Variable Changed · Push Batch
WhenVariable ChangedGlobal Variable: pushBatch
1Calibrate Recorded GaugeFunction
Action Name
calibratePushScale
cartNumber
@self.cartNumber
minimumRange
@self.minimumRange
headroom
@self.scaleHeadroom
2Change Variable
Variable
gaugeRange
Value
calibratePushScale.value

4.9.5 Blue Live Gauge

The dial face, digits and needle belong to one gauge Object. Only Velocity Needle should rotate. Add the named extra Sprite and Text components before choosing that binding.

In the Object’s Logic workspace, select Follow Native Velocity. Under Properties > Components, set Sprite (Sprite) to Velocity Needle, as this card shows. Keep the Object’s minimumRange value from its card rather than the Logic’s default.

The digits read Blue’s current native velocity. The marks read the saved gauge range, so their numbers agree with the needle’s scale.

ObjectBlue Live Gauge
Transform
Bounding Box > SizeX 0.8, Y 0.8
Sprite
SpriteVelocity Calibration
Transform > SizeX 2.5, Y 2.5
Text / UI
Text{instanceMember($blueCart,"transform.velocity.x"):.2}
Font Size0.225
Color#39291B
AlignmentCenter
Text BoundsWrap to Width
Transform > Size X1.1
Transform > PositionX -0.6, Y 0.035
Logic
Follow Native Velocity
Follow Native Velocity > Components > Sprite (Sprite)Velocity Needle
Sprite(additional component)
NameVelocity Needle
SpriteWorkshop needle
Z Index Within Object2
Transform > PositionX 0, Y 0
Transform > SizeX 0.175, Y 0.6
Text / UI(additional component)
NameVelocity Mark 1
Text{round(instanceVar("self","gaugeRange") * -1 * 100) / 100}
Font Size0.1875
Color#39291B
AlignmentCenter
Text BoundsWrap to Width
Transform > PositionX -0.770361618981152, Y 0.359225522479594
Transform > Size X0.75
Text / UI(additional component)
NameVelocity Mark 2
Text{round(instanceVar("self","gaugeRange") * -0.5 * 100) / 100}
Font Size0.1875
Color#39291B
AlignmentCenter
Text BoundsWrap to Width
Transform > PositionX -0.4567046670948, Y 0.716882728940953
Transform > Size X0.75
Text / UI(additional component)
NameVelocity Mark 3
Text{round(instanceVar("self","gaugeRange") * 0 * 100) / 100}
Font Size0.1875
Color#39291B
AlignmentCenter
Text BoundsWrap to Width
Transform > PositionX 0, Y 0.85
Transform > Size X0.75
Text / UI(additional component)
NameVelocity Mark 4
Text{round(instanceVar("self","gaugeRange") * 0.5 * 100) / 100}
Font Size0.1875
Color#39291B
AlignmentCenter
Text BoundsWrap to Width
Transform > PositionX 0.4567046670948, Y 0.716882728940953
Transform > Size X0.75
Text / UI(additional component)
NameVelocity Mark 5
Text{round(instanceVar("self","gaugeRange") * 1 * 100) / 100}
Font Size0.1875
Color#39291B
AlignmentCenter
Text BoundsWrap to Width
Transform > PositionX 0.770361618981152, Y 0.359225522479594
Transform > Size X0.75
Text / UI(additional component)
NameVelocity Units
Textm/s
Font Size0.175
Color#39291B
AlignmentCenter
Text BoundsWrap to Width
Transform > PositionX 0.875, Y 0.035
Transform > Size X0.6
Shared Variables
cartNumber > TypeNumber
cartNumber > KindConstant
cartNumber > Value1
minimumRange > TypeNumber
minimumRange > KindConstant
minimumRange > Value0.5
needleSweep > TypeNumber
needleSweep > KindConstant
needleSweep > Value65

4.9.6 Copper Live Gauge

Copper gets its own stationary face and moving needle. Use the same attachment, with Copper’s cartNumber and the same named needle binding. This instrument watches the lower cart. Its marks use Copper’s saved range instead of Blue’s; the needle binding remains Velocity Needle.

Place both gauges using their setup rows. Run a shared push, then wait for Blue to reach its stop. Its live digits settle while Copper can still move.

ObjectCopper Live Gauge
Transform
Bounding Box > SizeX 0.8, Y 0.8
Sprite
SpriteVelocity Calibration
Transform > SizeX 2.5, Y 2.5
Text / UI
Text{instanceMember($copperCart,"transform.velocity.x"):.2}
Font Size0.225
Color#39291B
AlignmentCenter
Text BoundsWrap to Width
Transform > Size X1.1
Transform > PositionX -0.6, Y 0.035
Logic
Follow Native Velocity
Follow Native Velocity > Components > Sprite (Sprite)Velocity Needle
Sprite(additional component)
NameVelocity Needle
SpriteWorkshop needle
Z Index Within Object2
Transform > PositionX 0, Y 0
Transform > SizeX 0.175, Y 0.6
Text / UI(additional component)
NameVelocity Mark 1
Text{round(instanceVar("self","gaugeRange") * -1 * 100) / 100}
Font Size0.1875
Color#39291B
AlignmentCenter
Text BoundsWrap to Width
Transform > PositionX -0.770361618981152, Y 0.359225522479594
Transform > Size X0.75
Text / UI(additional component)
NameVelocity Mark 2
Text{round(instanceVar("self","gaugeRange") * -0.5 * 100) / 100}
Font Size0.1875
Color#39291B
AlignmentCenter
Text BoundsWrap to Width
Transform > PositionX -0.4567046670948, Y 0.716882728940953
Transform > Size X0.75
Text / UI(additional component)
NameVelocity Mark 3
Text{round(instanceVar("self","gaugeRange") * 0 * 100) / 100}
Font Size0.1875
Color#39291B
AlignmentCenter
Text BoundsWrap to Width
Transform > PositionX 0, Y 0.85
Transform > Size X0.75
Text / UI(additional component)
NameVelocity Mark 4
Text{round(instanceVar("self","gaugeRange") * 0.5 * 100) / 100}
Font Size0.1875
Color#39291B
AlignmentCenter
Text BoundsWrap to Width
Transform > PositionX 0.4567046670948, Y 0.716882728940953
Transform > Size X0.75
Text / UI(additional component)
NameVelocity Mark 5
Text{round(instanceVar("self","gaugeRange") * 1 * 100) / 100}
Font Size0.1875
Color#39291B
AlignmentCenter
Text BoundsWrap to Width
Transform > PositionX 0.770361618981152, Y 0.359225522479594
Transform > Size X0.75
Text / UI(additional component)
NameVelocity Units
Textm/s
Font Size0.175
Color#39291B
AlignmentCenter
Text BoundsWrap to Width
Transform > PositionX 0.875, Y 0.035
Transform > Size X0.6
Shared Variables
cartNumber > TypeNumber
cartNumber > KindConstant
cartNumber > Value2
minimumRange > TypeNumber
minimumRange > KindConstant
minimumRange > Value0.5
needleSweep > TypeNumber
needleSweep > KindConstant
needleSweep > Value65

You can now watch the motion without opening the sheet or changing an earlier observation.

4.10 Put History on Paper

The printer and pinned papers keep the experiment’s earlier answers.

4.10.1 Inspect Recorded Push

Press an old paper and its observation should open larger. Inspect Recorded Push checks for that batch’s row, then opens the reading sheet with that identity selected. Motion continues underneath.

A missing or trimmed row leaves the view alone. A batch number identifies an observation even when earlier rows have gone.

FunctionInspect Recorded Push

Opens the retained push identified by its batch number. Missing or trimmed rows leave the current sheet unchanged.

PARAMETERS
NameTypeStarts at
batchNumber0
ACTIONS
1Check Data Row Exists
Action Name
recordedPush
Table
Push Readings
Row
"Push " + batch
2IfrecordedPush.value
Then
3Change Variable
Variable
$inspectedPush
Value
batch
4Change Variable
Variable
$detailsOpen
Value
true

4.10.2 Read Latest Print

The printer always shows the latest retained observation, whichever old ticket you inspect. Read Latest Print hides its paper until a row exists and lets an exact press open the larger view.

A changed batch or table refreshes visibility. Opening Help covers the bench paper, while Load restores whether there is something to print.

Logic ComponentRead Latest PrintAttach to: Printer Reading

Prints the latest retained numerical measurement on the printer paper; an exact press opens its optional enlarged reading.

Attach toAny Object
PER INSTANCE VARIABLES
NameTypeStarting Value
pressSelectedBooleanNo
When Created
WhenCreate
1Set Visible
Visible
$detailsOpen == false && dataCount("Push Readings") > 0
On Variable Changed · Push Batch
WhenVariable ChangedGlobal Variable: pushBatch
1Set Visible
Visible
$detailsOpen == false && dataCount("Push Readings") > 0
On Variable Changed · Details Open
WhenVariable ChangedGlobal Variable: detailsOpen
1Set Visible
Visible
$detailsOpen == false && dataCount("Push Readings") > 0
On Load:succeeded
Whenload:succeededCustom Event
1Set Visible
Visible
$detailsOpen == false && dataCount("Push Readings") > 0
When the Left Mouse Button Is Pressed
WhenMouse PressMouse button: left
1Ifself.pressSelected == true
Then
2If$detailsOpen == false && dataCount("Push Readings") > 0
Then
3Inspect Recorded PushFunction
batch
data("Push Readings",dataCount("Push Readings") - 1,"batch")
On Data Table Changed · Push Readings
WhenData Table ChangedData Table: Push Readings
1Set Visible
Visible
$detailsOpen == false && dataCount("Push Readings") > 0

4.10.3 Printer Reading

The printer’s compact columns put Blue beside Copper. Its Text template reads the last retained row directly, so later cart motion does not rewrite those values. Build its listener and print attachments in the shown order.

Keep each line break in the template. The paper is small, with the larger inspection available on a press.

ObjectPrinter Reading
Transform
Bounding Box > SizeX 2.8, Y 2.9
Text / UI
TextPUSH {data("Push Readings",dataCount("Push Readings") - 1,"batch")} [new line] B C [new line] kg {data("Push Readings",dataCount("Push Readings") - 1,"blueMass"):.2} {data("Push Readings",dataCount("Push Readings") - 1,"copperMass"):.2} [new line] J {data("Push Readings",dataCount("Push Readings") - 1,"blueAppliedImpulse"):.2} {data("Push Readings",dataCount("Push Readings") - 1,"copperAppliedImpulse"):.2} [new line] v0 {data("Push Readings",dataCount("Push Readings") - 1,"blueBefore"):.2} {data("Push Readings",dataCount("Push Readings") - 1,"copperBefore"):.2} [new line] v1 {data("Push Readings",dataCount("Push Readings") - 1,"blueAfter"):.2} {data("Push Readings",dataCount("Push Readings") - 1,"copperAfter"):.2} [new line] dv {data("Push Readings",dataCount("Push Readings") - 1,"blueDelta"):.2} {data("Push Readings",dataCount("Push Readings") - 1,"copperDelta"):.2} [new line] m/s
Font Size0.225
Color#352C24
AlignmentCenter
Text BoundsWrap to Width
Transform > Size X2.7
Sprite
Z Index Within Object-1
SpritePrinted Observation Paper
Transform > PositionX -0.075, Y 1.35
Transform > SizeX 2.65, Y 2.95
Logic
Read Latest Print
Listen for Control Press

4.10.4 Read Pinned Ticket

Each pinned paper needs to know which recent observation it represents. Each copy receives its slot at creation, before any later input asks it to inspect a row. That slot follows the retained end of the history.

Row and view changes refresh visibility. Its press opens the batch currently printed on that paper. The printer keeps its own latest row, even while you examine this older piece of paper.

Logic ComponentRead Pinned TicketAttach to: Reading Ticket

Prints one of the three newest immutable numerical measurements on its pinned paper; an exact press opens its optional enlarged reading.

Attach toAny Object
PER INSTANCE VARIABLES
NameTypeStarting Value
ticketSlotNumber0
pressSelectedBooleanNo
When Created
WhenCreate
1Change Variable
Variable
ticketSlot
Value
$preparingTicketSlot
2Set Z Order
Depth Amount
25
3Set Visible
Visible
$detailsOpen == false && self.ticketSlot <= dataCount("Push Readings")
On Variable Changed · Push Batch
WhenVariable ChangedGlobal Variable: pushBatch
1Set Visible
Visible
$detailsOpen == false && self.ticketSlot <= dataCount("Push Readings")
On Variable Changed · Details Open
WhenVariable ChangedGlobal Variable: detailsOpen
1Set Visible
Visible
$detailsOpen == false && self.ticketSlot <= dataCount("Push Readings")
On Load:succeeded
Whenload:succeededCustom Event
1Set Visible
Visible
$detailsOpen == false && self.ticketSlot <= dataCount("Push Readings")
When the Left Mouse Button Is Pressed
WhenMouse PressMouse button: left
1Ifself.pressSelected == true
Then
2If$detailsOpen == false && self.ticketSlot <= dataCount("Push Readings")
Then
3Inspect Recorded PushFunction
batch
data("Push Readings",max(0,dataCount("Push Readings") - 3) + self.ticketSlot - 1,"batch")
On Data Table Changed · Push Readings
WhenData Table ChangedData Table: Push Readings
1Set Visible
Visible
$detailsOpen == false && self.ticketSlot <= dataCount("Push Readings")

4.10.5 Reading Ticket

A ticket combines paper, printed values and a listener. Its Text template uses the saved ticketSlot to find the matching recent row. Keep the template and its line breaks whole.

With five retained rows, the three slots display the last three observations in order. Slot one starts at row index two, counting from zero. The next two slots follow it. After another push, the window moves forward together.

Make the Object without placing copies; the pattern will arrange them in their board positions. The next Component will create the regularly spaced papers.

ObjectReading Ticket
Transform
Bounding Box > SizeX 1.7, Y 2.2
Text / UI
Text#{data("Push Readings",max(0,dataCount("Push Readings") - 3) + instanceVar("self","ticketSlot") - 1,"batch")} [new line] B {data("Push Readings",max(0,dataCount("Push Readings") - 3) + instanceVar("self","ticketSlot") - 1,"blueMass"):.2} kg [new line] {data("Push Readings",max(0,dataCount("Push Readings") - 3) + instanceVar("self","ticketSlot") - 1,"blueAfter"):.2} m/s [new line] C {data("Push Readings",max(0,dataCount("Push Readings") - 3) + instanceVar("self","ticketSlot") - 1,"copperMass"):.2} kg [new line] {data("Push Readings",max(0,dataCount("Push Readings") - 3) + instanceVar("self","ticketSlot") - 1,"copperAfter"):.2} m/s [new line] dv {data("Push Readings",max(0,dataCount("Push Readings") - 3) + instanceVar("self","ticketSlot") - 1,"blueDelta"):.2} [new line] / {data("Push Readings",max(0,dataCount("Push Readings") - 3) + instanceVar("self","ticketSlot") - 1,"copperDelta"):.2}
Font Size0.175
Color#352C24
AlignmentCenter
Text BoundsWrap to Width
Transform > Size X1.4
Logic
Read Pinned Ticket
Listen for Control Press

4.10.6 Prepare Reading Tickets

The board gains its papers. Prepare Reading Tickets supplies each slot before creating its copy, as the weight maker did with each weight’s starting information.

Create runs that pattern once. Native Load restores the existing tickets and their identities.

Logic ComponentPrepare Reading TicketsAttach to: Game Rules

Creates the three regularly spaced paper controls once. Native Load restores them and their saved slot identities without creating more.

Attach toAny Object
When Created
WhenCreate
1Repeat3times
Each time
2Change Variable
Variable
$preparingTicketSlot
Value
local.index + 1
3Create Instance
Object to Create
data("Reading Ticket Settings","papers","object")
X
data("Reading Ticket Settings","papers","firstX") + data("Reading Ticket Settings","papers","spacing") * local.index
Y
data("Reading Ticket Settings","papers","paperY")

4.10.7 Choose Historical Ticket

The keyboard can inspect the papers too. Choose Historical Ticket opens the first recent ticket, then advances through the pinned observations on later requests. An empty history has nothing to open.

Its table-change and Load Events validate the selected batch. A removed row cannot leave a fabricated reading on the sheet.

Logic ComponentChoose Historical TicketAttach to: Game Rules

T opens the oldest pinned reading, then cycles through the retained three. H closes the sheet; reopening Help returns to the latest page.

Attach toAny Object
When Inspect Ticket Is Pressed
WhenCommand PressedCommand: Inspect Ticket
1IfdataCount("Push Readings") > 0
Then
2Declare Variable
Name
ticketIndex
Value
dataFind("Push Readings","batch",$inspectedPush) + 1
3IfticketIndex < max(0,dataCount("Push Readings") - 3) || ticketIndex >= dataCount("Push Readings")
Then
4Change Variable
Variable
ticketIndex
Value
max(0,dataCount("Push Readings") - 3)
5Inspect Recorded PushFunction
batch
data("Push Readings",ticketIndex,"batch")
On Data Table Changed · Push Readings
WhenData Table ChangedData Table: Push Readings
1Validate Inspected PushFunction
On Load:succeeded
Whenload:succeededCustom Event
1Validate Inspected PushFunction

Return to Game Rules now. Add Prepare Reading Tickets and Choose Historical Ticket in the remaining positions on its card.

4.11 Separate the Sheet Views

The sheet can show either the latest comparison or an older observation.

4.11.1 Toggle Workshop Readings

Reopening Help returns to latest readings. The change to Toggle Workshop Readings clears that historical choice when the sheet opens. Closing still leaves the experiment running.

FunctionToggle Workshop ReadingsAddDelete

Toggles only the saved reading-sheet view. The experiment continues unchanged.

ACTIONS
1If$detailsOpen
Then
2Change Variable
Variable
$detailsOpen
Value
false
3Otherwise
4Change Variable
Variable
$detailsOpen
Value
true
5If$detailsOpen
Then
6Change Variable
Variable
$inspectedPush
Value
0

4.11.2 Set Reading Visibility

Moving a weight makes the affected latest comparison stale. Set Reading Visibility now receives a validity value as well as the caption. A genuine zero impulse remains a measurement.

The latest caption also waits while the historical page is selected. An old observation has its own captions below.

FunctionSet Reading VisibilityAddDelete

Shows a genuine measured push only in the optional reading sheet; zero impulse is a measurement, the opening workshop is not.

PARAMETERS
NameTypeStarts at
captionInstance Setundefined
validBooleanYes
ACTIONS
1If$detailsOpen && $pushBatch > 0
Then
1If$inspectedPush == 0 && ($detailsOpen && $pushBatch > 0 && valid)
Then
2Set Visible
Applies to
caption
Visible
true
3Otherwise
4Set Visible
Applies to
caption
Visible
false

4.11.3 Show Measured Reading

Blue and Copper can lose validity separately. Show Measured Reading uses its holding Object’s readingCartNumber to choose the matching validity value. The new Variable Changed Events refresh that choice when a load changes.

Keep the added inspectedPush Event too. It lets the latest captions return when you leave an older observation.

Logic ComponentShow Measured ReadingAttach to: Blue Push Reading, Copper Push ReadingAddDelete

Refreshes measurement visibility when the workshop opens, captures a push or finishes Load.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
readingCartNumberNumberConstant1
When Created
WhenCreate
1Set Reading VisibilityFunction
caption
self
valid
(@self.readingCartNumber == 1 && $blueReadingValid) || (@self.readingCartNumber == 2 && $copperReadingValid)
On Variable Changed · Push Batch
WhenVariable ChangedGlobal Variable: pushBatch
1Set Reading VisibilityFunction
caption
self
valid
(@self.readingCartNumber == 1 && $blueReadingValid) || (@self.readingCartNumber == 2 && $copperReadingValid)
On Load:succeeded
Whenload:succeededCustom Event
1Set Reading VisibilityFunction
caption
self
valid
(@self.readingCartNumber == 1 && $blueReadingValid) || (@self.readingCartNumber == 2 && $copperReadingValid)
On Variable Changed · Details Open
WhenVariable ChangedGlobal Variable: detailsOpen
1Set Reading VisibilityFunction
caption
self
valid
(@self.readingCartNumber == 1 && $blueReadingValid) || (@self.readingCartNumber == 2 && $copperReadingValid)
On Variable Changed · Blue Reading Valid
WhenVariable ChangedGlobal Variable: blueReadingValid
1Set Reading VisibilityFunction
caption
self
valid
(@self.readingCartNumber == 1 && $blueReadingValid) || (@self.readingCartNumber == 2 && $copperReadingValid)
On Variable Changed · Copper Reading Valid
WhenVariable ChangedGlobal Variable: copperReadingValid
1Set Reading VisibilityFunction
caption
self
valid
(@self.readingCartNumber == 1 && $blueReadingValid) || (@self.readingCartNumber == 2 && $copperReadingValid)
On Variable Changed · Inspected Push
WhenVariable ChangedGlobal Variable: inspectedPush
1Set Reading VisibilityFunction
caption
self
valid
(@self.readingCartNumber == 1 && $blueReadingValid) || (@self.readingCartNumber == 2 && $copperReadingValid)

4.11.4 Show Reading Page

The pages need different words. Show Reading Page compares the selected view with its historyPage value. The appropriate captions appear together.

Set historyPage on each holding Object after attachment. Yes chooses history. No chooses the current instructions and status, rather than an earlier observation’s page.

Logic ComponentShow Reading PageAttach to: Push Status, Workshop Instructions, Historical Blue Reading, Historical Copper Reading, History Note

Shows a latest-page or historical-page caption inside the optional reading sheet. historyPage chooses the historical page.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
historyPageBooleanConstantNo
When Created
WhenCreate
1Set Visible
Visible
$detailsOpen && (($inspectedPush > 0) == @self.historyPage)
On Variable Changed · Details Open
WhenVariable ChangedGlobal Variable: detailsOpen
1Set Visible
Visible
$detailsOpen && (($inspectedPush > 0) == @self.historyPage)
On Variable Changed · Inspected Push
WhenVariable ChangedGlobal Variable: inspectedPush
1Set Visible
Visible
$detailsOpen && (($inspectedPush > 0) == @self.historyPage)
On Load:succeeded
Whenload:succeededCustom Event
1Set Visible
Visible
$detailsOpen && (($inspectedPush > 0) == @self.historyPage)

4.11.5 Blue Push Reading

Blue’s latest caption gains its own cart identity. Match its Text. Blue keeps the default readingCartNumber value of one from Show Measured Reading; this Object card needs no override. The validity check now knows which cart this caption describes.

ObjectBlue Push ReadingAddDelete
Transform
Bounding Box > SizeX 0.8, Y 0.8
Text / UI
TextBlue: {$blueMass} kg | J {$blueAppliedImpulse} N s [new line] {round($blueBefore * 100) / 100}->{round($blueAfter * 100) / 100}; change:{round($blueDelta * 100) / 100}m/s
TextBlue: {$blueMass} kg | J {$blueAppliedImpulse} N s [new line] v {round($blueBefore * 100) / 100} -> {round($blueAfter * 100) / 100} m/s | change {round($blueDelta * 100) / 100} m/s
Font Size0.8
EmphasisMedium
Letter Spacing-0.032
AlignmentCenter
Text BoundsWrap to Width
Transform > Size X22
Logic
Show Measured Reading

4.11.6 Copper Push Reading

Give Copper the same connection to its own identity, keeping that value separate from Blue’s. Keep its own identity rather than Blue’s default, so a changed Copper load invalidates the correct latest comparison.

ObjectCopper Push ReadingAddDelete
Transform
Bounding Box > SizeX 0.8, Y 0.8
Text / UI
TextCopper: {$copperMass} kg | J {$copperAppliedImpulse} N s [new line] {round($copperBefore * 100) / 100}->{round($copperAfter * 100) / 100}; change:{round($copperDelta * 100) / 100}m/s
TextCopper: {$copperMass} kg | J {$copperAppliedImpulse} N s [new line] v {round($copperBefore * 100) / 100} -> {round($copperAfter * 100) / 100} m/s | change {round($copperDelta * 100) / 100} m/s
Font Size0.8
EmphasisMedium
Letter Spacing-0.032
AlignmentCenter
Text BoundsWrap to Width
Transform > Size X22
Logic
Show Measured Reading
Shared Variables
readingCartNumber2

4.11.7 Workshop Instructions

The reminder now includes weight placement and ticket inspection. Replace its marked Text and change the attachment to Show Reading Page. This reminder belongs on the latest page.

Replace [new line] with a break. The historical view has a shorter reminder of its own, with the controls for inspecting earlier observations.

ObjectWorkshop InstructionsAddDelete
Transform
Bounding Box > SizeX 0.8, Y 0.8
Text / UI
TextDrag carts or weights; release from rest. [new line] Space: Push Home: Return Arrows: J / sign [new line] N: Weight 1 / 2: Load cart Delete: Rack [new line] End: max 0: zero T: tickets H: close
TextDrag carts; release from rest. [new line] Space: Push Home: Return Arrows: J / sign [new line] End: max 0: zero [new line] Experiment continues. H: close
Font Size0.8
EmphasisMedium
AlignmentCenter
Text BoundsWrap to Width
Transform > Size X22
Logic
Show Reading Page
Show Workshop Detail

4.11.8 Push Status

Keep current feedback beside its controls, where a refused request can explain what needs changing. Push Status also changes to Show Reading Page, with historyPage No. An old paper describes its observation.

ObjectPush StatusAddDelete
Transform
Bounding Box > SizeX 0.8, Y 0.8
Text / UI
Text{$pushStatus}
Font Size0.8
EmphasisMedium
AlignmentCenter
Text BoundsWrap to Width
Transform > Size X22
Logic
Show Reading Page
Show Workshop Detail

4.11.9 Historical Blue Reading

The enlarged Blue reading looks up the selected batch in Push Readings. Its values come from that retained row, even after Blue’s load or velocity changes. Give its page attachment historyPage Yes.

ObjectHistorical Blue Reading
Transform
Bounding Box > SizeX 22, Y 3.3
Text / UI
TextBlue: {data("Push Readings","Push " + $inspectedPush,"blueMass")} kg | J {round(data("Push Readings","Push " + $inspectedPush,"blueAppliedImpulse") * 100) / 100} N s [new line] {round(data("Push Readings","Push " + $inspectedPush,"blueBefore") * 100) / 100} -> {round(data("Push Readings","Push " + $inspectedPush,"blueAfter") * 100) / 100} m/s [new line] change: {round(data("Push Readings","Push " + $inspectedPush,"blueDelta") * 100) / 100} m/s
Font Size0.8
EmphasisMedium
Color#F4E8CF
AlignmentCenter
Text BoundsWrap to Width
Transform > Size X22
Logic
Show Reading Page
Shared Variables
historyPageYes

4.11.10 Historical Copper Reading

Copper’s historical caption reads the same batch, with Copper’s recorded fields. Its setup row puts the caption below Blue’s, leaving space to compare the two recorded answers. The pair remains a comparison of one past push.

ObjectHistorical Copper Reading
Transform
Bounding Box > SizeX 22, Y 3.3
Text / UI
TextCopper: {data("Push Readings","Push " + $inspectedPush,"copperMass")} kg | J {round(data("Push Readings","Push " + $inspectedPush,"copperAppliedImpulse") * 100) / 100} N s [new line] {round(data("Push Readings","Push " + $inspectedPush,"copperBefore") * 100) / 100} -> {round(data("Push Readings","Push " + $inspectedPush,"copperAfter") * 100) / 100} m/s [new line] change: {round(data("Push Readings","Push " + $inspectedPush,"copperDelta") * 100) / 100} m/s
Font Size0.8
EmphasisMedium
Color#F4E8CF
AlignmentCenter
Text BoundsWrap to Width
Transform > Size X22
Logic
Show Reading Page
Shared Variables
historyPageYes

4.11.11 History Note

The historical sheet names its batch and explains why its values stay still. History Note gives the reader a way back to recent observations and the latest page. Place it using the remaining setup row.

Next Impulse, Impulse Dial Face, Reading Sheet, Reading Sheet Title and Help Control keep last chapter’s card values. Their existing view attachments still serve the same bench and sheet.

ObjectHistory Note
Transform
Bounding Box > SizeX 22, Y 3.8
Text / UI
TextPUSH #{$inspectedPush} / recorded snapshot [new line] Later changes do not rewrite this reading. [new line] T: next pinned ticket H: close [new line] Reopen Help for the latest reading.
Font Size0.8
EmphasisMedium
Color#F4E8CF
AlignmentCenter
Text BoundsWrap to Width
Transform > Size X22
Logic
Show Reading Page
Shared Variables
historyPageYes

The bench now shows motion and keeps its observations.

4.12 Play It!

Predict how adding a weight will change Blue’s answer to the same impulse. Keep the chosen impulse unchanged while testing that prediction.

  • Run Lab. Blue begins without a load; Copper carries its starting weights. A shared +4 N s push gives Blue a change of +4 m/s and Copper +0.4 m/s.
  • Return the carts with Home. Move a 1 kg weight from the rack onto Blue, then push again. Blue’s native mass is now 2 kg and its change is +2 m/s. Copper’s response stays +0.4 m/s.
  • Select a weight with N. Use 1 or 2 to load the selected weight, then Delete to return it to the rack. The selected weight changes tint.
  • Try a full cart. A refused extra weight keeps its previous place. Release it elsewhere on the bench to return it to its rack position.
  • Push, then change Blue’s load while Copper is still moving. Blue stops; Copper keeps moving. Open Help and look for Blue’s latest comparison to disappear until another push.
  • Watch a gauge while its cart reaches a stop. Its live digits reach zero. The printer keeps the earlier mass, impulse and before-and-after velocities.
  • Make several accepted pushes. The pinned papers follow the newest observations; the printer stays on the latest one. Inspect an older ticket with T.
  • Close with H, change the load, then inspect that retained ticket again. Its old mass and velocities remain unchanged. Close the sheet and reopen Help when you want to see the latest comparison again.
  • Hold a weight and press Space. The whole pair push is refused. Let go, then push again; the bench is ready for another observation.

4.13 How It Works

The pattern creates weights. Each weight records its place. A transfer changes the load, and each affected cart updates its native mass.

The push still measures velocity immediately before and after the impulse. That change equals impulse divided by the actual native mass measured inside the same call. The gauges watch later motion; the history keeps that earlier measurement.

Changing the apparatus starts a fresh comparison for the affected cart. Keeping the old observation separate lets you compare experiments without confusing them with the bench’s current state.

4.14 If It Didn’t Work

The rack stays empty? Look at Game Rules first. Prepare Workshop Weights needs its attachment, the named weight resources and matching object text in Weight Pattern. Restore one missing connection, then run Lab again.

Copper has two loads drawn in the same place? Compare the extra Sprite names with the removed components on Copper Cart’s marked Object card. The earlier painted load comes out when movable weights take over. Keep the generated copies and remove those old extra pictures.

A weight moves but mass stays unchanged? Follow its registry row and cartLoadChanged. Change Native Cart Load needs to be attached to each cart. Restore that listener and try the transfer again.

The whole gauge face turns? Select Follow Native Velocity on that gauge’s Logic workspace. Its Components Sprite picker needs Velocity Needle. Change that binding and the face should stay still while the needle turns.

A row expression names an unknown weight? Compare its For Each Row Variable Name with the expression below it. The row’s name belongs inside that loop. A temporary Declare Variable must also come before the Actions that change it.

An old paper changes when the cart stops? Check where its Text reads. Printer Reading and Reading Ticket use Push Readings; the gauges use native velocity. Restore that distinction and the paper can keep its moment.

The latest caption remains after its load changes? Compare that caption’s readingCartNumber with its cart, then check the added validity Events. A new accepted push restores a valid comparison.

The sheet opens on the wrong page? Follow inspectedPush through the view Functions, then compare the page choice on each caption Object. The historical captions use historyPage Yes; the current instructions and status use No. Restore the mismatched holder value.

4.15 Challenge

Choose two load arrangements that you think will give the same change in velocity with different impulses. Make each experiment and keep its paper. Use the historical view to compare your prediction with the recorded masses and changes.

The bench keeps what you observed. Next, you can ask it for a particular result.