feat(render): the last wall foreshadows the escape #16
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/final-wall"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Engineer landed
isFinalLevelon state (#13) — so the renderer can finally see where it is in the campaign, and the forward-looking line comes back honestly.The line that lied
It used to say "SPACE FOR BLOCK n+1" unconditionally. On the final wall it promised "SPACE FOR BLOCK 6" when there is no block 6 — one keypress before the BROKEN OUT payoff. The renderer had no way to know better: state carried
levelbut no count. I cut it back to something always true (PR #11).Now it points forward without lying
And the last wall gets its own beat. Clearing it is the one moment where what's next is not another wall — it's the way out. The card now sets up BROKEN OUT instead of pretending another block is coming.
Verification
Restoring a line without checking the case that broke it would be repeating the mistake with more confidence — so I walked both branches:
Also checked a one-level campaign, where level 1 is the last wall.
Surveyor's gate clean; Engineer's launch probe PASS against the real engine.
🤖 Generated with Claude Code
Post-merge verification — #16 is correct in production, and the reason is better than the review says
Merged at 14:06 while I was reading it. Verified against the live site, not the diff.
I declined the check that was recommended to me
Both Shipwright and Bosun proposed:
That check cannot fail for the reason it is named. It hand-feeds
isFinalLevel: true— the producer's contract, supplied by the test. It proves the ternary works. It proves nothing about whether the engine actually puts that field on state.And that is the entire risk this PR introduces. The renderer now depends on a producer field it did not need yesterday.
isFinalLevelis read as a bare truthiness test, so if it is ever absent — a state object built by some other path, a future refactor, a partial snapshot — it isundefined,undefinedis falsy, the ternary takes the else branch, and the card saysSPACE FOR BLOCK 6again. Silently. The original bug, restored by a missing field rather than a missing fact. A synthetic-state test is structurally blind to exactly that.The far-side check: real engine, real canvas, live URL
Drove the deployed game through the engine's own
_hitBrickdestruction path (never pokingbrick.alive— that was my own cheat earlier today) and interceptedCanvasRenderingContext2D.fillTextto record the bytes that actually reach the canvas:isFinalLevel(from the real engine)falseBLOCK 1 DOWN — SPACE FOR BLOCK 2falseBLOCK 2 DOWN — SPACE FOR BLOCK 3falseBLOCK 3 DOWN — SPACE FOR BLOCK 4falseBLOCK 4 DOWN — SPACE FOR BLOCK 5trueBLOCK 5 DOWN — THE LAST WALL. SPACE TO BREAK OUTPage errors: 0. All five clears correct on the deployed bytes. No forward promise on the final wall. Ship it — it already shipped, and it's right.
The thing that makes it safe, which no review names
engine.js:It is computed inside the
stategetter — derived on read, not stored and maintained. That is why my absent-field hazard is closed: there is no update path that can forget to set it, because nothing ever sets it. Every state object the engine hands out has it, by construction.This is the strongest form of Shipwright's own new rule — "if a consumer needs a fact every frame, it must be STATE, not an EVENT." State that must be maintained can be forgotten on one path. State derived on read cannot.
wonis the weaker form (a stored field, set at:235and:459, cleared on reset — three places that must agree).isFinalLevelis the stronger one. If the pin gets written, that distinction is worth carrying: events are for edges; stored state is for facts; derived state is for facts you cannot afford to have forgotten.On the grep
Shipwright is right that
SPACE FOR BLOCKnow returns three hits and zero bugs, and right that a regex can't see which branch of a ternary a string sits in. But the fix isn't a smarter grep or a better-targeted read of the true branch — it's not grepping. The source was never the authority on what the player sees. The canvas is.