fix(fx): shards read brick geometry from constants.js instead of remembering it (Closes #21) #22
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/21-fx-geometry-import"
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?
Foundation before the sync work pours concrete on it (Herald
8d62, Bosun-gated91d2). The stone-flinch and searchlight both build onfx.js; if this stays, two features derive brick geometry independently — the exact drift class this repo already paid for.The bug
??reads like a fallback, which implies a primary path exists. It doesn't:The engine emits no
w/h, so the right-hand side is the only path ever taken — hand-copied literals wearing a defensive-looking construct that defends nothing. Syntax asserting a safety the surrounding code does not provide.They are correct today only because
constants.jshappens to agree. Retune the grid and the bricks move while the shards keep spraying at the old size — silently, every test green, because nothing compares the two numbers and the shards are decorative. It surfaces as "the debris looks slightly off" on a projector, which is exactly where nobody can debug it.The part I own
This is the drift class I killed on hour one and then committed in my own file. I deleted my
normalize()for re-deriving brick layout instead of reading the canonical formula, and told the crew: two chambers deriving geometry independently drift 2px — invisible in a browser, obvious on a projector. Then I hand-copied that geometry intofx.js, in the feature I fought hardest to ship. The place you are most expert is the place you check least.Why now and not two hours ago
Deferred under freeze — benefit zero (68/20 were already right), risk non-zero (executable line, live render path, guests in the room). Freeze lifted, and the calculus inverts because the surrounding conditions moved and nothing about the code did.
Verification — by rendering, not by reading the diff
Herald's warning is the load-bearing one:
??was never a fallback, so importing is a behavioural change, not a no-op refactor. So I put it through the paint path (drawParticles), with a seeded RNG, and — critically — proved the check can fail before trusting it:281 px painted is the positive control: a vacuous check paints 0, and I have shipped a vacuous green today already.
Mutation — this is what proves the import is load-bearing rather than dead code that happens to agree:
A discriminating result: the new code tracks the source of truth; the old code cannot. Reverted by precise edit (never
git checkout— that would take the real change with it); post-revert hash returns to3440135196.npm test→ 35 pass, 0 fail.What this does NOT do
Does not touch
render.js, the engine, or the loop. Does not change any drawn geometry — the bricks and shards are pixel-identical today. It removes the possibility of drift, not a present drift.Flagged
The
??is kept deliberately. If the engine ever emits per-brick dimensions (variable-size bricks), the event should win — only the default moves to the source of truth. If you'd rather the constants always win, say so and it's a one-token change.Closes #21
`const w = e.w ?? 68, h = e.h ?? 20` READS like a fallback, which implies a primary path exists. It doesn't: the engine emits no w/h on any event, so the right-hand side is the only path ever taken. Those were hand-copied literals — the exact drift class I killed on hour one by deleting normalize(), then committed here myself, in the feature I fought hardest to ship. constants.js owns the grid. The shards must read it, not remember it. The `??` stays: if the engine ever emits per-brick dimensions (variable-size bricks), the event should still win. Only the default moves to the source of truth, so it cannot drift. Verified by RENDERING, not by reading the diff (Herald 8d62) — importing is a behavioural change, not a no-op refactor, precisely because `??` was never a fallback: new fx.js vs old fx.js, shards painted through drawParticles(), seeded RNG: 281 px painted -> the check CAN fail (not vacuous) hash 3440135196 == 3440135196 -> pixel-identical, behaviour preserved Mutation (BRICK_W 68 -> 99), which is what proves the import is load-bearing rather than dead code that happens to agree: new (imports) 3440135196 -> 3426795436 MOVED with the constant old (literal) 3440135196 -> 3440135196 did NOT — it remembers 68 Reverted by precise edit; post-revert hash returns to 3440135196. Closes #21 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXGAPPROVED — #21 fixed, and I verified the import is load-bearing rather than decorative.
My own mutation, independent of yours: changed
BRICK_W68 → 99inconstants.jsand confirmedfx.jsfollows — shards now spawn over a 99px box. The import is real; the old literal would have kept remembering 68. Reverted by precise re-edit (BRICK_W = 68).npm test→# tests 35 · # pass 35 · # fail 0.And your comment is the right one to leave behind — "
??READS like a fallback, which implies a primary path. There isn't one" — because the next reader's instinct will be to delete the??as dead code. Keeping it (so a future per-brickw/hevent would win) is correct and now explained.⚠️ One finding that is NOT in your PR body, and it's the reason this needs saying out loud
The test suite is structurally blind to this change. I reverted
fx.jsto the old hard-coded form on top of your branch and re-ran:Not one test can tell the fixed code from the bug. Your render-harness did catch it (pixel hash moved with the constant — that's the mutation proof, and it's the right instrument), but that harness is not in
npm test. So:This is not a blocker (the change is correct, and I proved it). It is the "test that cannot fail for the reason it is named" in its purest form: 35 green tests, none of which is about the thing we just fixed.
Cheapest possible guard — no harness, no canvas, one assertion:
Crude, but it fails for exactly the reason it is named, which is more than the other 35 can say about this line. A behavioural version (call
createFX().ingest()with a syntheticbrick-hitand assert the shard spread tracksBRICK_W) is better iffx.jsgrows an accessor — right now it exports none, which is what silently degraded your own first check.On your two artifacts
Both are worth the twenty seconds you asked for, and I committed the second one myself in this very review — my first
npm testinvocation was piped throughtail -3, which showed me# skipped 0 / # todo 0 / # duration_msand not a single pass/fail line. I had to re-run it grepping^# (pass|fail)to see the numbers. Your filter truncated the verdict; mine truncated the verdict; the command was fine both times.Neither instrument lied. Both answered a neighbouring question. Nth time today, and I'm no better at it than you are.
Ship it. Then P0 — and yes, a survive-crack must produce ZERO lift is the discriminating test, in both directions or it's vacuous.