feat(render): canvas renderer, HUD + FX layer (visible vessel) #3
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/1-renderer-hud"
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?
Visible vessel: canvas renderer + HUD + FX layer. Pure
render(ctx, state)— owns no state, mutates nothing, advances nothing. Engineer owns the single rAF driver and calls it.⚠️ This also fixes main being broken right now: main's
index.htmlrequests/src/main.js, which does not exist (404 + dead loop, Carpenter 796c). This PR lands both, with the relative./src/path per Bosun's deploy convention.What it does
getBoundingClientRect. Inherited from cellblock's canvas idiom.FEEL.shatter) — Herald cut them for the 2h budget and calls that upgrade, not me. Flipping one flag turns them on.Three seams I caught while building, each of which fails silently or fatally
render(). Engineer's loop calls it with an interpolation alpha — a number in the slot where this renderer expected the FX object. A number is truthy, so the firstbrickFlash()call throws a TypeError on frame one and takes the whole game down. Now duck-typed: an FX layer is something that can answerbrickFlash(); anything else means "no FX", and the picture is still complete, just without juice. Two individually-correct modules that did not compose.brickType, nottype. On a brick-hit event,typenames the event; the brick's kind rides onbrickType. Readingtypecoloured every shard aswall— silently, no error. It was only ever harmless because shatter is gated off, which is luck, not design.x = 48 + col*72, and the brick is NOT centred in its cell. The 68×20 brick sits in a 72×24 cell with the 4px gutter falling right and below. Centring it — which my first pass did, because centring looks like the careful thing to do — drifts every brick by 2px. Invisible in a browser, obvious on a projector. Herald's formula is canon; I deleted my "improvement".Contract tolerance (deliberate)
Herald, Engineer and Carpenter were converging on one vocabulary while I built. Rather than force anyone to conform to my guess,
normalize()absorbs the variance in one function: it takesx/yif present and derives them fromcol/rowif not, readshporhits, and resolves the brick-type synonyms still live in the tree. When the contract settles, reconciliation is that function and nothing else in the codebase.An unknown brick type is not silently painted as a wall — that would draw a 2-hit brick as a 1-hit brick, a lie the player pays for and that no screenshot catches. It falls back to wall and warns, once per type, so the gap is loud. It maps a gap; it does not invent a look for one.
Verification — mutation, not screenshot
Two invariants matter and neither is visible in a still image, so both are proven by mutating state and requiring the pixels to change, then change back:
col:rowregisters in FX and lights the brick. This is the one I most expected to be broken. FX and render must key off the same identifier, and the events carry noid— a synthesised one would never match, the flash would never fire, and nothing would look broken enough to notice.My first flash test reported FAIL, and the instrument was wrong, not the code: I pushed the event into
state.events, which the stub clears at the top of the next tick, beforefx.ingest()runs. Worth stating plainly because it is a real hazard for whoever owns the loop: anything that clearseventsbefore FX drains them silently kills every flash.Surveyor's harness is clean — 960×640, 100% non-blank, 0 uncaught errors, 0 HTTP 4xx. Its
animating: NOat rest is correct: the READY card is legitimately still. I verified separately that the loop is alive by launching the ball and re-measuring frame hashes with a position-dependent hash.What this PR does NOT do
src/stub.jsis a throwaway test double, labelled as such, so the board renders before the engine lands (per Herald: "a stubbed board rendering beautifully at minute 20 is exactly what the guests should be looking at"). It is deliberately naive and must be replaced, not fixed.src/main.jsis a dev harness. Engineer owns the one loop (Bosun seam #2). At integration,index.html's entry flips to his driver, which importsrender(). I have offered to own that wiring, since I ownindex.html.🤖 Generated with Claude Code
ecd739b193to4e89c40daf4e89c40dafto716bc75916