One coin pusher.
The machine owns its own field: the reciprocating platform, the face that
rides it, the rigidbody coins and trinkets piled on the deck, and the pay edge
that turns something going over it into BANK. It knows nothing about quotas,
vitality or the run -- the director reads `banked` and drives `corruption` and
the relic modifiers in.
Every measurement comes from `cf_pusher.SPEC`, the same table the cabinet is
BUILT from, and every coin, bed and stroke goes through `cf_pusher`'s own
calls. A machine that carried its own copy of the playfield is a machine whose
field drifts away from the one on screen: the copy that used to live here put
the deck 90 mm low, drove the platform a metre into the air, and ran the
stroke across the cabinet's width instead of along its depth.
Coins are simulated in WORLD space. Parenting a rigidbody under a moving root
fights the solver, so every ledge and rail test converts a coin's world
position back into the machine's local frame through `cf_world.toLocal`.
toLocal(wx: number, wy: number, wz: number) → void
| arg | type | description |
|---|
| wx | number | |
| wy | number | |
| wz | number | |
toWorld(lx: number, ly: number, lz: number) → void
| arg | type | description |
|---|
| lx | number | |
| ly | number | |
| lz | number | |
built( ) → void
The shim `cf_pusher`'s bed and drive calls take. It is rebuilt from the two
entity refs rather than stored across a reload, because a hot reload of this
component clears the state table and the refs are what survive.
part(suffix: string) → void
A named part of this machine's cabinet. The pusher names every part it builds
with the machine's own prefix, so a mechanic can reach its hardware without
the director having to thread a dozen more refs through.
| arg | type | description |
|---|
| suffix | string | |
coinValue( ) → number
A coin's cash value at the current corruption. The rot is the difficulty AND
the reward: a machine deep in the red pays more than double.
track(e: ?, kind: string, value: number) → void
| arg | type | description |
|---|
| e | ? | |
| kind | string | |
| value | number | |
nextName(kind: string) → string
| arg | type | description |
|---|
| kind | string | |
spawnCoinAt(lx: number, ly: number, lz: number) → void
Everything loose on the field is spawned through here, so the bed a machine
opens with and the coin a player drops are the same object.
| arg | type | description |
|---|
| lx | number | |
| ly | number | |
| lz | number | |
spawnOddment(kind: string, lx: number, ly: number, lz: number) → void
| arg | type | description |
|---|
| kind | string | |
| lx | number | |
| ly | number | |
| lz | number | |
loadBed(count: number) → void
Lay the bed. Anything already standing on this machine's field that the
tracker does not know about is swept first: a hot reload of this component
clears the per-machine state table, and the coins it was tracking become
rigid bodies nothing owns, still simulating in a chamber the machine believes
is empty.
| arg | type | description |
|---|
| count | number | |
despawnLoose(i: number) → void
| arg | type | description |
|---|
| i | number | |
driveShelf(dt: ?) → void
Rate climbs with corruption and, past halfway, stops being regular: the
machine starts snatching at the pile instead of sweeping it. Direction flips
on the pusher's MEASURED position rather than on a timer, so a stroke held up
by a heavy bed stays in step with where the plate actually is.
runMechanic(dt: ?) → void
Each variant's rule acts on the bed every frame, and each has a piece of
hardware on the cabinet that moves with it, so the rule is legible from the
front before anything explains it.
mouthAt(lx: number) → number
Which of the three mouths a coin at local x belongs to.
| arg | type | description |
|---|
| lx | number | |
settleLoose(dt: ?) → void
Walk the loose objects: bank what crossed the edge, take what the machine's
own mechanic takes, and drop what left the chamber.
Events are collected during the walk and fired AFTER it. A bank event runs the
director's quota check, and a quota that is met calls `clearField` straight
back into this machine -- so firing inside the loop empties the list the loop
is still indexing, and the next step reads nil.
dropCoins(count: number, spread: number?) → void
Drop `count` coins down the chute. `spread` widens where they land, which
is how THIRD HAND's second coin ends up somewhere the player did not choose.
| arg | type | description |
|---|
| count | number | |
| spread | number? | |
preload(count: number) → void
Fill the field the way an operator fills a pusher before opening: a bed
of coins lying across the platform and the deck, with prizes riding on it.
A pusher a player walks up to is never bare steel -- it is loaded, and the
bed is the whole read of the machine. The bed reaches the lip and hangs over
it, so the first push already tips something in.
| arg | type | description |
|---|
| count | number | |
dropRelic( ) → void
Drop a relic token onto the bed among the coins.
dropDetritus(which: string) → void
Drop one of the things that ride the bed once the run rots. `which` is
"tooth" or "eye".
| arg | type | description |
|---|
| which | string | |
nudge( ) → void
NUDGE: hit the glass. Every loose object gets an impulse toward the edge
and a little lift, so a settled bed stops being settled.
halt(duration: number) → void
HALT: freeze the platform where it stands.
| arg | type | description |
|---|
| duration | number | |
magnet(duration: number) → void
MAGNET: drag the bed ledge-ward for a while.
| arg | type | description |
|---|
| duration | number | |
spill(count: number) → void
SPILL: dump a handful of coins across the whole width of the field.
| arg | type | description |
|---|
| count | number | |
clearField( ) → void
Clear the field. Called when a machine dies and when a run ends, so a
dead machine leaves no rigidbodies ticking behind it.
census( ) → void
How many loose objects are on the field, by kind -- the data query that
answers "is the pusher actually simulating" without looking at it.
stroke( ) → number
How far through its travel the platform is, 0 fully back to 1 fully
forward, measured off the body rather than off a timer.
isHalted( ) → boolean
Whether the platform is currently frozen by HALT.
mechanicState( ) → any
The mechanic's own read-out: which mouth is live, whether the tithe slot
is open, whether the maw is inhaling. The HUD names what the machine is doing
so the rule is visible while it happens.