Warden
The rules of *First Light: Betocsin*. One instance, mounted on `mount_game` by the scene. It owns the round — the phase machine, the crane, the drop, the trim, the dawn clock, the hoist, the ring and the two ways to lose — writes `game.core`'s `state`, and emits every event that…
Warden
The rules of First Light: Betocsin. One instance, mounted on mount_game
by the scene. It owns the round — the phase machine, the crane, the drop, the
trim, the dawn clock, the hoist, the ring and the two ways to lose — writes
game.core's state, and emits every event that file lists under "rules".
It draws nothing, plays no sound and moves no camera.
The round
title ──start──▶ opening ──staged──▶ stacking ⇄ paused
│
course 24 ───┴──▶ hoist ──staged──▶ ring
│ │
collapse / sunrise ──▶ lost press ▶ won
- stacking — a course hangs from the crane and slides back and forth
along one lozenge edge: U on odd courses, V on even.
droplets it go, it falls forFALL_TIME, and what it overlaps with the course below is what survives. - the trim — a course is a parallelogram spanned by
aalongCore.UandbalongCore.V, centred at(cu, cv). Two courses share those edge directions, so their overlap is the intersection of the two u-intervals crossed with the intersection of the two v-intervals: a miss oftalong U leavesa - |t|re-centred atcu + t/2, and the|t|beyond it becomes the offcut. The footprint stays a parallelogram forever, and the error alternates between the two edge pairs instead of eating one of them. - a perfect seat — a miss inside
Core.PERFECT_TOLERANCEis not trimmed at all and the edge grows back byCore.PERFECT_REGROW, capped at the full lozenge. Perfect seats are what make course 24 reachable. - collapse — an edge trimmed under
Core.MIN_EDGEcannot carry the next course. The round is lost,state.lastLossreads"fell". - the night —
state.dawnadvances bydt / Core.NIGHT_SECONDSwhile stacking, and only while stacking. The first watch opens with the round and each boundary inCore.WATCHESopens the next, so there are four watches and four tocsin beats.dawnreaching 1 loses the round withstate.lastLoss = "sunrise". - the ring — the hung bell swings on
state.swing.twith an amplitude ofstate.swing.a / 2. Pressingdroprings it, and the quality handed to theringevent is how close to the top of the swing the press landed. No press withinRING_WINDOWtolls it anyway, badly; there is no way to fail here.
What it reads from other slices
game.blocks.Blocksfor every piece of geometry.spawnSlab,reshape,spawnOffcutandspawnPlinthare required;spawnCrane(withcrane:setHang(cu, cv, y)) andspawnBellare asked for and skipped when they are not there. A hero-asset call that raises is logged, dropped for the rest of the round, and asked for again on the next one — so a fix lands without anything here being edited, and a broken call never takes the round with it or floods the log.staged, emitted by the camera slice, releases theopeningandhoistbeats. Each carries a timeout (8 s and 6 s) so a beat that never answers cannot freeze the round.ui, emitted by the screens slice —start,howto,back,resume,restart,pause. The screens slice says what the player asked for; this component decides what that means from where the round is, and is the only thing that sets a phase.
Controls
game.tower is activated when a round begins and stood down at the title and
on both end cards, so the crane's buttons are on screen exactly while there is
a course to drop. drop seats the course and, at the top, rings the bell;
pause puts the pause card up and takes it down again. Both are bound for
keyboard, gamepad and touch by the map, and this component subscribes to the
control names only — never to a key. Activating a map hands back controls with
no subscribers, so the handlers go on at every activation.
Nothing here reads the screen size: the rules are metres and seconds, and every shape shows the same round.
Scoped to this part · feeds back into the world's score.