Three records in the under-table crate are pressed with whole scenes. E beside
one dives you into its pocket above the booth walls; complete its sound-first
quest to press a STAMP; all three raise the golden slipmat + a permanent bonus
percussion stem. Dust bunnies move in under the table.
- Worlds (Lane W): ACID WAREHOUSE / DUB CHAMBER / DISCO LOFT — sealed themed
pockets, portal rings, exit pads; deterministic, zero light leaks (incl. the
flythrough path), meshes unchanged at 469, build ~760 ms.
- Machines (Lane M): portal dive/exit/safety (membership-based escape catch),
tune-the-303 hold-sweep pedestals, carry-the-charge wobbling spring bridge,
beat-Simon dance floor with glow tiles; stamps store + relay {t:'stamp'}
(default-closed, persisted, join snapshot, fuzzed); golden slipmat; stamps
survive the reset ritual.
- Ambience (Lane S): dust bunnies (wander/flee, zero steady-state allocs),
vinyl-warp portal iris, HUD stamp tray, stamp confetti + golden shimmer.
- Audio (Lane E2): per-world grooves (acid 303 w/ fader-driven bands, dub
feedback-echo + spring, disco pentatonic tiles) over a boothGain
duck-and-handback that can never restore stale state; portal/stamp/golden
SFX; bonus stem.
Post-integration review (2 seam finders, 10 findings, all fixed + verified):
stamp events carry origin (local/remote/replay) so consumers celebrate
proportionately instead of wall-clock guessing — kills phantom join
celebrations, first-person confetti for peers' wins, and audio/visual grace
mismatches; reconnect re-uploads locally-won stamps the server missed;
relay saveState survives disk errors; pocket escape via mined floor counts as
a real exit at any altitude; exit-pad speed gate + dwell stops mid-quest
yank-outs; dive cooldown stops exit/re-dive ping-pong; locked acid bands
need deliberate holds (tap-spam dead); disco foot band excludes jump apex.
Verified: whole-tree typecheck + build clean; per-lane live verification
(27/27 worldgen assertions, relay fuzz, 18-phase audio matrix, fps unchanged);
integrated smoke: dive/exit/safety/stamps/golden + all origin paths live.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7.2 KiB
SIDE B — Lane S handoff (fx/ui ambience)
Dust bunnies, the vinyl-warp portal iris, and the HUD stamp tray + stamp
celebrations. All bus-driven, no imports from any other lane, nothing outside
src/fx/** + src/ui/** touched. Not committed (integrator commits).
Files
| file | change |
|---|---|
src/fx/bunnies.ts |
NEW — DustBunnies: 4 fluff-ball critters on the under-table floor |
src/fx/portalIris.ts |
NEW — PortalIris: full-screen 2D-canvas groove-ring iris |
src/fx/FxSystem.ts |
additive — attachAmbience(), iris + bunnies update, stamp confetti, stamp:all emissive shimmer |
src/ui/Hud.ts |
additive — stamp tray (3 accent dots, top-right under the signal lamps) |
src/fx/HANDOFF.md, src/ui/HANDOFF.md |
SIDE B sections appended |
Integrator wiring — ONE line in src/main.ts
// after the player is constructed (mirrors the fx.setBeacons pattern):
fx.attachAmbience(world, player);
That's it. Everything else self-wires to the bus in the existing
FxSystem/Hud constructors already called from main.ts. Without the line,
bunnies simply don't spawn (and stamp confetti falls back to the world's
entry anchor); iris + tray + shimmer work regardless.
Events
Consumed (new this phase): world:enter, world:exit (iris),
stamp:got (tray dot + pulse, accent confetti at the player),
stamp:all (gold tray rings, booth-wide emissive shimmer via the existing
setEmissiveBoost path, gold confetti at the three crate portal discs).
Emitted: machine:interact { machineId: 'bunny', action: 'bunny_flee' } —
on the wander→flee transition only, globally rate-limited to 1/s (a 10 s
continuous chase produces 1 emit, not 10 — the squeak marks the spook, per
brief "at most 1/s"). Lane E2 maps it to a squeak.
Behaviour details
- Bunnies (
DustBunnies): 4 critters, each a single mesh of 8–12 jittered dust-grey quads (soft glow-texture alpha, normal blending — 4 draw calls total). Homes: 2 in the record-crate area, 1 mixer PCB room, 1 deck-A PCB room, leashed to 14 voxels so they stay findable. Wander ≈0.3–0.8 v/s with idle pauses; spook at 5 voxels (player must be within 4 y — tabletop players don't spook them); flee exactly 8 voxels with ease-out (measured ramp 7→5.2→3.6→2.4→1.3 v/s), then settle (tremble) and resume. Movement uses aworld.isSolidlookahead: ground must be reachable within ±1 voxel (overhangs are never ground — that's what keeps them out of the crate's edge-standing records), keep-out rings are steered around, fleeing tries ±45°/±90° deflections before giving up. If someone builds a block into a bunny it pops on top. - Keep-out zones: the three
WORLD_DEFS[k].portalStand±6 rings, the parts-bin tray and the fuse-box spot. The last two mirrorworldgen/anchors.tsarithmetic (M.minX+15/M.minZ+10,M.minX+24/M.maxZ-8) derived fromLAYOUT— same convention asfx/layout.tsVU towers. If Lane C ever moves theUNDERanchors, updateKEEP_OUTinbunnies.ts. - Iris (
PortalIris): 400 ms ease-in collapse → 90 ms full black → 400 ms release; groove rings drift INTO the aperture onworld:enter, OUT onworld:exit; accent lip + rings tinted fromWORLD_DEFS[world].accent(warm-white fallback for unknown keys). Pure screen overlay on aposition:fixedcanvas at z-index 9 — under the HUD (z 10), so start/pause overlays and subtitles are never covered;pointer-events:none; the camera (incl.TURNCRAFT_CINE/ the flythrough) is never touched. Timeline runs onperformance.now()insidefx.update()(no private rAF loop); idle =display:none+ a single boolean check per tick. Retrigger mid-animation resumes from the current coverage (no pop). Canvas re-syncs its size on every trigger (guards embedded panes that boot with a 0-sized window). - Stamp tray (
Hud): three 15 px record dots (with a centre "spindle hole") attop:64px; right:14px, right under the signal-lamp tracker, inWORLD_KEYSorder withWORLD_DEFSaccents. Dim ring when not got; accent fill + glow when got; scale-pulse on a livestamp:got; gold rings on all three via.tc-stamps.allonstamp:all. stamp:allshimmer: 2.6 s additive boost wave (fast ramp, ~2 Hz glitter oscillation, ease-out) through the SAME injectedsetEmissiveBoostpath the LED pulse uses — composes with (never replaces) the win/reset timelines.
Join-replay coordination (Lane M — please confirm timing)
Lane M re-emits stamp:got (and stamp:all when applicable) from the join
snapshot to seed state. Both FxSystem and Hud treat stamp events arriving
< 4 s after construction as replays: state applies (dots light, gold ring
sticks) but celebrations are suppressed (no pulse, no confetti, no shimmer).
A genuine stamp can't happen 4 s into a session, so there's no false-suppress
risk; if the relay handshake can complete LATER than ~4 s after page load, the
window constant lives in FxSystem.isJoinReplay() and Hud.bornAt uses —
bump both. Verified against Lane M's real relay during this session: their
join replay lit all three dots + gold silently; a live re-emit afterwards
pulsed and threw confetti.
Flag for the integrator — disco disco_cue lighting
SIDEB_MACHINES.md M2 says Lane M emits machine:interact {action:'disco_cue', index} "so Lane S can light them", but
SIDEB_WORLDS.md says "Lane M lights them" and my brief (SIDEB_SOCIAL.md)
scopes Lane S to bunnies/iris/tray only — and the contract
machine:interact payload has no index field. No disco-tile lighting
exists in Lane S. If Lane M's handoff defines an encoding (e.g. index in
the action string), a small FX reaction can be added on request.
Verification (live game, port 5205, real code paths)
- Typecheck: exit 0 (whole tree).
- Bunnies, 60 sim-seconds sweep: 0 solid-cell penetrations, 0 keep-out violations (closest approach 6.14), max ground y=4 (rides copper traces, never climbs structures), all 4 wandered 3.9–8.4 voxels. Flee: exactly 8.00 voxels, ease-out ramp above, settle → wander. Emits: 1 per spook; 10 s continuous chase → 1 emit. Zero steady-state allocation: 0 KB heap delta over 3600 warm ticks.
- Iris phases probed pixel-level on the overlay canvas: 210 ms centre open /
edges black, 430 ms full black, 700 ms releasing, 900 ms hidden +
display:none; compositing over the game verified by screenshot; HUD stays above it. - Tray: dots at top:64/right:14 with correct accents; got/pulse/all classes exercised by hand-emitted events AND by Lane M's real relay replay (silent) + live emits (pulsed). Visually confirmed lit in-game.
stamp:all: shimmer curve measured through the boost path (0.56→1.02→glitter→base over 2.6 s); gold confetti seen over the portal discs.- fps unchanged: warm A/B with forced GPU sync — bunnies visible
1.124 ms/frame vs hidden 1.094 ms (~890 vs ~914 fps-equiv; delta = 4 tiny
draw calls);
fx.update7.6 µs/tick warm with everything attached; iris actively closing ≈ 1.08 ms/frame. All ~9× inside the 100 fps budget. - Zero console errors from fx/ui across the session (only the pre-existing relay-offline WebSocket retry noise, which predates this phase).