# Lane E — FX HANDOFF (`src/fx/**`) Owned by Lane E. See `src/audio/HANDOFF.md` for the full lane summary. ## Public API ```ts const fx = new FxSystem({ scene, // THREE.Object3D to add fx to (Lane A's scene) setEmissiveBoost, // Lane A's injected hook (v:number)=>void getLevels: () => audio.getLevels(), }); fx.update(dt); // each fixed tick ``` `FxSystem` self-wires to the bus (`audio:beat`, `signal:repair`, `game:win`, `platter:state`, `block:break`) and adds all its own objects to `scene`. ## What's in here - `particles.ts` — `Burst` (round-robin, finite-life pool: break puffs, win confetti, 45-rpm rim sparkle) and `DustField` (continuous brownian motes). Both preallocate typed arrays and mutate them in place — **zero per-frame allocation**; a shared canvas glow texture, no external assets. - `overlays.ts` — emissive sprite overlays we own (never touching Lane A/C materials): `VuBank` (mixer VU towers, LED-segment stacks keyed to `getLevels()`), `SignalTrace` (a bright sprite that runs the signal-path polyline on each repair), `PatchBlink` (patch-bay socket blink). - `layout.ts` — all overlay anchor points derived from `src/core/constants.ts` `LAYOUT` (VU towers, `SIGNAL_PATH`, patch bay, dust box, deck rims). - `FxSystem.ts` — orchestrator: the global LED emissive pulse (base steps up per repair, pulses on beats), rim sparkle, and the win visual timeline (1.8 s dark → needle-drop flash + confetti → living-booth steady state). ## Notes for integration - The only material Lane E touches is via the injected `setEmissiveBoost`. - VU/trace/patch positions come from `LAYOUT`; if Lane C's real LED towers sit elsewhere, adjust `VU_TOWERS` in `layout.ts` (see friction #5 in the audio handoff). - `update(dt)` is safe at `FIXED_DT`; the win visual timeline advances on the `dt` you pass (keep calling it every tick so it doesn't stall mid-sequence). ## Round 2 — Headshell Workshop - **Screw confetti**: `FxSystem` auto-tosses a burst of brushed-steel screws when the workshop completes — it reacts to `signal:repair` with `node === 'stylus'` (Lane D calls `quest.repair('stylus')`). Also exposed as a public `fx.screwBurst(x, y, z)` for explicit use. - **The oscilloscope scope block is Lane D's** (`src/machines/workshop/`, its own canvas draw). Lane E does NOT render a scope — an earlier `src/fx/scope.ts` was removed to avoid two scopes. ## Glow-Up G3 + G5 — decals (`src/fx/decals.ts`) Screen-print decals + the record groove texture. Built once, purely cosmetic. ```ts import { DecalSet, recordSideTexture } from './fx/decals'; const decals = new DecalSet(); // no deps scene.add(decals.group); // ~13 plane meshes (< 25 draw-call budget) decals.dispose(); // frees geometries + canvas textures ``` - **G3** bakes text/markings onto `CanvasTexture`s applied to thin planes floating `0.05` proud with `polygonOffset` (no z-fight, verified at grazing angle). One canvas per gear piece keeps the count low. Positions come from `LAYOUT` (`core/constants.ts`) and the worldgen anchors (`MIXER`/`DECK_A`/`DECK_B`/`PATCH`/`UNDER`) — nothing hardcoded. Print set: mixer `CH 1–4` + fader ticks + `TURNCRAFT-800` + crossfader `A↔B`; each deck `START·STOP`/`33`/`45`, a pitch `+/−` ladder, and a "Quartz Direct Drive" plinth-front script; patch-bay `PATCH BAY` + `01–12` socket numbers; a `FUSE 250V` legend; and four procedural party flyers on the booth walls. - Flat (`up`) faces flip each glyph 180° in place so labels read from the player's front-edge viewpoint (tick marks / positions stay put). ### G5 — `recordSideTexture(): THREE.CanvasTexture` ← **Lane D wires this** **Signature:** `recordSideTexture(): THREE.CanvasTexture` — no args; returns an 8×128 sRGB canvas texture, `RepeatWrapping` (S+T) + `NearestFilter` already set, near-white base with fine dark groove lands so it **multiplies** cleanly over the tier's existing vinyl tint. A fine light/dark groove-ring texture for the vinyl tier **side** cylinders. Rings run horizontally (constant around the circumference, varying up the height). **This lane does not edit `platter.ts`.** **Exact merge point — `src/machines/platter.ts`, the `sideMat` around L121–124** (where `sideMat = blockMaterial(vinylId, { opacity … })` is built and applied to the outer + mid vinyl tier side cylinders): ```ts import { recordSideTexture } from '../fx/decals'; // after: const sideMat = blockMaterial(vinylId, { opacity: ... }); sideMat.map = recordSideTexture(); sideMat.map.repeat.set(1, Math.max(1, tierHeightVox / 4)); // ~4 grooves/voxel of height sideMat.needsUpdate = true; // NearestFilter + RepeatWrapping are already set on the texture. No geometry or // platter-logic change — purely a `material.map` assignment. ``` It's a standalone generator (no side effects); safe to call once and share the texture across both platters. Only the platter wiring is left — everything else here is live and browser-verified.