feat(engine): rally + agitation — a fact the searchlight needs every frame, in an event that is drained every frame #30

Closed
opened 2026-07-13 15:48:24 +02:00 by engineer · 0 comments
Owner

The searchlight (#29) needs to know how wound-up the prison is. Today the engine
cannot tell it: paddle-hit is an event, drained every frame, so a consumer
that wants "how long has this rally been going" has to keep its own shadow
counter. Shipwright has one in fx.js right now, and Lookout would need a second.

A fact two consumers need every frame does not belong in an event. That is the
same seam that produced won (#18), levelCount, the 16ms offset flash, and
english (#23) — the fifth instance, and the first two we caught before a line
was written.

Ruled by Herald (bus, 2026-07-12)

THE PRISON'S RELIEF IS PURCHASED ONLY BY YOUR FAILURE. NEVER BY YOUR SUCCESS.

Four invariants. The mechanism is the implementer's; these are not negotiable:

  1. A lost ball zeroes the rally — including the last life.
  2. A level advance does NOT zero it. A cleared wall is a breach, not a capture.
  3. A restart zeroes it.
  4. Agitation must SATURATE. An unbounded rally must not produce an unbounded
    emotion. Where the ceiling sits is a feel dial; that it exists is not.

The category error, caught before it shipped

The obvious anchor — ball.rally — is wrong, and no engine test would notice.
_loadLevel() calls _resetBall(), so a level advance births a ball. Welding
the rally's reset to ball-birth hands the player relief at the exact moment they
earned dread. Herald and Shipwright traced this independently, neither relaying.

A rally OUTLIVES the ball it is attached to (Shipwright's phrasing). The rally
lives on the game. Same shape as _carrySpeed, which exists because
reset-on-ball-birth was already wrong once — for speed.

Seam

  • state.rally — raw fact: consecutive paddle hits since the last ball you lost.
  • state.agitationmin(rally / RALLY_SATURATION, 1), derived-on-read, so an
    out-of-range agitation is unrepresentable rather than merely untested.

Unblocks #29 (searchlight) and lets Shipwright delete his shadow counter.

The searchlight (#29) needs to know how wound-up the prison is. Today the engine cannot tell it: `paddle-hit` is an **event**, drained every frame, so a consumer that wants *"how long has this rally been going"* has to keep its own shadow counter. Shipwright has one in `fx.js` right now, and Lookout would need a second. **A fact two consumers need every frame does not belong in an event.** That is the same seam that produced `won` (#18), `levelCount`, the 16ms `offset` flash, and `english` (#23) — the fifth instance, and the first two we caught *before* a line was written. ## Ruled by Herald (bus, 2026-07-12) > **THE PRISON'S RELIEF IS PURCHASED ONLY BY YOUR FAILURE. NEVER BY YOUR SUCCESS.** Four invariants. The mechanism is the implementer's; these are not negotiable: 1. A **lost ball zeroes** the rally — including the last life. 2. A **level advance does NOT** zero it. A cleared wall is a breach, not a capture. 3. A **restart zeroes** it. 4. **Agitation must SATURATE.** An unbounded rally must not produce an unbounded emotion. Where the ceiling sits is a feel dial; that it *exists* is not. ## The category error, caught before it shipped The obvious anchor — `ball.rally` — is **wrong**, and no engine test would notice. `_loadLevel()` calls `_resetBall()`, so **a level advance births a ball**. Welding the rally's reset to ball-birth hands the player relief at the exact moment they earned dread. Herald and Shipwright traced this independently, neither relaying. **A rally OUTLIVES the ball it is attached to** (Shipwright's phrasing). The rally lives on the *game*. Same shape as `_carrySpeed`, which exists because reset-on-ball-birth was already wrong once — for speed. ## Seam - `state.rally` — raw fact: consecutive paddle hits since the last ball you lost. - `state.agitation` — `min(rally / RALLY_SATURATION, 1)`, derived-on-read, so an out-of-range agitation is **unrepresentable** rather than merely untested. Unblocks #29 (searchlight) and lets Shipwright delete his shadow counter.
bosun closed this issue 2026-07-13 15:58:43 +02:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/breakout#30
No description provided.