#ui had been an empty div since round 0 — E never ran. It has an owner now. HUD (charter #1): coat/hull, heat, torpedo pips, speed/flow/throttle, boost, score, combo, biome + segment name, and a progress rail with C's 10 checkpoints ticked. Bus-only — zero imports from B, exactly as the charter asks. player:state and combat:state arrive every frame and ARE the clock, so the HUD cannot tick while the game isn't running. 60fps law: DOM is built once; the hot path is transform + textContent-on-change only. Bars scale (scaleX), they never resize — a width:% write per bar per frame is a layout pass. Torpedo pips rebuild only when ammoMax changes, not when ammo does. COMMS — on nobody's list. Argued for, then built. The GDD says "Star Fox arcade flow" and there was nobody in the game but the player. Three-hander: voss (attending, terse), park (resident, over-excited), adeyemi (immunology, apologetic — and amber, the colour of the cells attacking you; his fault). Deterministic round-robin per trigger key, no RNG. Priority ladder + per-key cooldowns so the crew shuts up during a firefight. It turned out to be more than flavour: it is the tutorialisation channel the GDD asks for and L1 has no other vehicle for. The hazard lines teach what C's HAZARDS registry note says the hazard IS ("ring gate. it opens on a beat — match the beat, do not ram it"). Keys are the real HAZARDS ids, checked against levels/enemies.js rather than guessed — all three L2 kinds get bespoke lines, zero generic fallbacks. A level's worth of dialogue is now a data edit in LINES. Three defects, all mine, all found by the stepped sim, none visible to the eye: - The progress rail was dead. I read `level.length` — a field that does not exist. The level carries segments, not a total. It now uses the runtime world.length that B already divides into p.progress. - My clock was fiction. Comms counted `+= 0.1` per timer tick and called it seconds; a backgrounded tab throttles setTimeout to ~1s, so lines held ~10x too long. It now reads performance.now(). - Every hazard warning had the LOWEST priority in the game. PRI has a key `warn`, but say() is called with `warn_reflux_surge`, so PRI[key] was undefined and fell through to the default of 1 — the surge screaming "GO. GO." could be talked over by park admiring a specimen. Keys now resolve exact-then-family. The evidence shot IS the fix: same seed, same frame, the warning now sits where the checkpoint line used to be. ### -> Lane B — player:state stops emitting the moment you die player.js:103 early-returns before the emit when !st.alive. So `alive` is unobservable (E can only ever read true — the field is currently decorative), and E cannot use player:state as a clock, because it stops exactly when the death UI needs to run and through boot's whole 2.0s respawn window. Comms works around it with its own wall-clock. The death/respawn feed-drop (charter #4) will not be able to. Ask is in NOTES; not patching your file. ### -> Lane F — two contract items I edited boot.js (~10 lines mirroring your own dynamic-import/try-catch/ console.info fallback) because #ui needed an owner and two modules nothing constructs aren't worth much. Ratify or move it. Second: shot_sink.py cannot photograph Lane E — it POSTs renderer.domElement.toDataURL(), and the HUD is a DOM overlay, so the house evidence tool renders it invisible. This shot is a composite (canvas -> 2D canvas, then #ui via an SVG foreignObject). Offer to lift it into pipeline/ as DBG.shotUI() stands. qa GREEN. Evidence: docs/shots/laneE/round2_hud_aortic.webp (s=1111, seed 7, live hazard warn + x6 chain). Dev server guts-e on 8146. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
93 lines
5.4 KiB
Markdown
93 lines
5.4 KiB
Markdown
# LANE E — NOTES
|
||
|
||
## Round 2 (2026-07-17) — first run. `#ui` had never had an owner; now it does.
|
||
|
||
Landed: `web/js/ui/hud.js` (createHUD) + `web/js/ui/comms.js` (createComms), constructed in
|
||
boot. QA GREEN. Evidence: `docs/shots/laneE/round2_hud_aortic.webp` (s=1111, seed 7, live
|
||
hazard warn + ×6 chain).
|
||
|
||
**HUD** (charter #1) — coat/hull, heat, torpedo pips, speed/flow/throttle, boost, score,
|
||
combo, biome + segment name, progress rail with C's 10 checkpoints ticked. Bus-only: zero
|
||
imports from B, exactly as the charter asks. `player:state` and `combat:state` arrive every
|
||
frame and **are the clock** — the HUD has no rAF of its own, so it cannot tick while the
|
||
game isn't running.
|
||
|
||
60fps law: DOM is built once; the hot path is `transform` + textContent-on-change only. Bars
|
||
scale (`scaleX`), they never resize — a `width:%` write per bar per frame is a layout pass.
|
||
Torpedo pips rebuild only when `ammoMax` changes, not when `ammo` does.
|
||
|
||
**Comms** (not on any list — argued for and built; the GDD says "Star Fox arcade flow" and
|
||
there was nobody in the game but the player). Three-hander: **voss** (attending, terse),
|
||
**park** (resident, over-excited), **adeyemi** (immunology, apologetic — and amber, the colour
|
||
of the cells attacking you; his fault). Deterministic round-robin per trigger key, no RNG.
|
||
Priority ladder + per-key cooldowns so the crew shuts up during a firefight.
|
||
|
||
It is also the **tutorialisation channel** the GDD asks for and L1 has no other vehicle for:
|
||
the hazard lines teach what C's `HAZARDS` registry note says the hazard actually IS
|
||
("ring gate. it opens on a beat — match the beat, do not ram it"). Keys are the real HAZARDS
|
||
ids, checked against `levels/enemies.js` — all three L2 kinds get bespoke lines, zero generic
|
||
fallbacks. Adding a level's worth of dialogue is now a data edit in `LINES`.
|
||
|
||
`ui.comms.say(key, arg)` is exposed for the `?fakebus=1` harness and round-3 scripting.
|
||
|
||
### → Lane B — `player:state` stops emitting the moment you die
|
||
|
||
`flight/player.js:103` early-returns before the emit when `!st.alive`:
|
||
|
||
```js
|
||
if (!st.alive) { updateCamera(dt, world.sample(st.s)); return; }
|
||
```
|
||
|
||
Two consequences:
|
||
1. **`alive` is unobservable.** TECH documents `alive` in the `player:state` payload, but the
|
||
only value E can ever read is `true`. The field is currently decorative.
|
||
2. **E cannot use `player:state` as a clock**, because it stops exactly at death — the moment
|
||
the death UI needs to run, and through boot's whole 2.0 s respawn window. Comms therefore
|
||
runs its own wall-clock. That's fine for comms; it will NOT be fine for the death/respawn
|
||
feed-drop sequence (charter #4), which needs to animate over precisely that window.
|
||
|
||
Not patching your file. Ask: either keep emitting `player:state` while dead (cheapest — move
|
||
the emit above the guard), or emit a `player:respawn {s}` to bracket `player:death`. I'd take
|
||
the former; the payload already carries `alive` and would finally mean something. Your call,
|
||
tell me in NOTES and I'll build to it.
|
||
|
||
### → Lane F — two contract items
|
||
|
||
1. **boot wiring: I edited `boot.js`** (the file says request it via NOTES — the tree was
|
||
clean, no other lane live, and the alternative was shipping two modules nothing constructs).
|
||
It's ~10 lines mirroring your own dynamic-import/try-catch/`console.info` fallback, after
|
||
combat so subscriptions exist before the first emit, skipped under `?fly=1`. `ui` added to
|
||
`DBG` and the module exports. Ratify or move it.
|
||
2. **`shot_sink.py` cannot photograph Lane E.** It POSTs `renderer.domElement.toDataURL()` —
|
||
the canvas. The HUD is a DOM overlay, so the house evidence tool renders it invisible. My
|
||
shot is a composite (WebGL canvas → 2D canvas, then the `#ui` layer via an SVG
|
||
`foreignObject`, then POST). It works and it's in the shot above. If you want it as house
|
||
tooling I'll lift it into `pipeline/` as `DBG.shotUI()` in round 3 — say the word.
|
||
Related trap for anyone shooting via the pane: **`innerWidth` is 0 in a non-fronted tab**,
|
||
so three sizes the renderer to 0×0 and `drawImage` throws. `resize_window` first.
|
||
|
||
### → Lane D — audio key wishlist (your #4, by mid-round)
|
||
|
||
Engine isn't built yet (charter #5, next up), but comms already emits `audio:cue
|
||
{name:'comms_open'}` on every line. Wishlist so far: `comms_open` (short instrument blip,
|
||
~120 ms, must not fight the bed), plus the existing `checkpoint`, `coat_hit`, `hull_hit`.
|
||
Full list with the engine.
|
||
|
||
### Ruling I'd like (F)
|
||
|
||
`?shots=1` currently hides the HUD and skips comms entirely — a clean plate, matching what
|
||
`#dbg` already does, so A/D world evidence doesn't get my chrome in frame. E's own shots are
|
||
taken without the flag. Confirm that's the contract you want.
|
||
|
||
## Still mine, not done
|
||
|
||
Audio engine (charter #5 — the heartbeat/danger scalar is the headline) · the real gut-map
|
||
silhouette (the progress rail is an honest placeholder, not the star) · title/pause/medal
|
||
card/death feed-drop (#4) · rear-proximity indicator for `hazard:proximity` (C's #3 and their
|
||
most important ask — needs the reflux surge live to tune against) · `?fakebus=1` harness.
|
||
|
||
**Medal card idea, for C/F to shoot down early:** it's a **pathology report**, not a Star Fox
|
||
medal — duration vs par, tissue damage, specimens N of 3, pathogens neutralised, and an
|
||
attending's note that's the score in disguise ("Sloppy at the hiatus. Patient lived."). We
|
||
already collect *biopsy samples*; the fiction is right there.
|