TURNCRAFT/src/fx/HANDOFF.md
jing 9c668d340c Headshell Workshop + Glow-Up phase: assembly minigame, art pass, review fixes
- Workshop (WORKSHOP_CARTRIDGE): five-stage cartridge assembly at Deck A —
  seat/square, crimp four tag-wires, torque screws, ride-the-arm counterweight
  balance, needle-drop diagnostic with per-fault audio + scope. Relay-synced
  (per-field co-op merge: held screw + carried wire survive remote state).
- Glow-Up (G1-G5): 32px atlas with per-voxel variants, selective LED bloom
  (quality-gated), screen-print decal system + party flyers, mixer/PCB worldgen
  density pass, record groove-sheen side texture.
- 10 confirmed multi-agent review fixes, incl. co-op screw-stomp soft-lock,
  ride-snap collider-identity (magnet feet / eaten record-fling), workshop SFX
  exact-match map (rca_seated hijack), beam ride colliders to the head,
  double-crimp guard, WIRING INCOMPLETE diagnosis mode, skate-abort timer,
  completedState wiring, per-tick material churn, trackingHeavy platter drag.
- Crossfader playtest fix: slew-limited sled (3.4 v/s) + ribbed grip caps with
  amber index — the sled reads as a heavy handle, not a teleporting wall.
- Demo harnesses: machinesDemo hold-key wiring, playerDemo seesaw phase
  continuity, audioDemo incomplete fault button.
- Workshop sync: exact 1.0 screw endpoint gets its own emit signature.

Verified: typecheck + vite build clean, live solo quest smoke, two-client
co-op relay smoke (simultaneous torque, no rewind, exact convergence).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 21:32:07 +10:00

5.0 KiB
Raw Blame History

Lane E — FX HANDOFF (src/fx/**)

Owned by Lane E. See src/audio/HANDOFF.md for the full lane summary.

Public API

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.tsBurst (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.

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 CanvasTextures 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 14 + 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 + 0112 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.CanvasTextureLane 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 L121124 (where sideMat = blockMaterial(vinylId, { opacity … }) is built and applied to the outer + mid vinyl tier side cylinders):

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.