# BRIEF — Social & the Reset Ritual *The booth is shared; now make it feel inhabited. Three features: avatar customization, emotes, and a diegetic way to kill the mix and play the quest again — plus a who's-here list.* **For one Opus 4.8 session** (features are separable if split later, but the protocol changes should land as one coherent relay version). **READ FIRST — this is the law from the last phase:** on this shared tree you build against the *actual code*, not brief-guesses. Read `docs/CONTRACTS.md`, `docs/INTEGRATION_NOTES.md`, then the real files: `src/net/NetClient.ts` + `src/net/Avatars.ts` + `server/relay.mjs` (the protocol you're extending), `src/machines/quest.ts` + `src/machines/workshop/` (what reset must unwind), `src/ui/Menu.ts` (where customization lives), `src/audio/AudioEngine.ts` + `src/audio/sfx.ts` (how SFX are made), `src/fx/FxSystem.ts` (how the booth's mood follows quest state), `src/core/events.ts` (bus vocabulary — extend additively, primitives only). **You own:** `src/net/**`, `src/ui/**` (additive), `server/relay.mjs`, `src/machines/quest.ts` (+ small workshop reset hook), additive edits to `src/audio/**`, `src/fx/FxSystem.ts`, `src/core/events.ts`, `src/main.ts` wiring. Keep every edit additive; do not restructure working systems. --- ## Feature 1 — Avatar customization ("avatar your player") Players are already voxel DJs with headphones + name tags. Give them a look of their own. **Keep it palette-y and chunky — no free-form content, so there's nothing to moderate.** - **Options** (all enum/palette, no user-drawn anything): - `bodyHue`: 12 swatches (hue ring; keep the current golden-ratio default as "auto"). - `headgear`: `phones` (current), `cap` (brim box), `beanie`, `crown` (win-flavored, tiny), `none`. - `face`: 4 procedural canvas faces on the head front (dot eyes / shades / visor / happy) — painted, not typed. - **Settings UI**: a third Menu tab `AVATAR` with swatch buttons and a live preview (a small THREE scene in the panel is overkill — a 2D canvas mock-up of the front view is fine and cheap). Persists in the existing Settings localStorage object; **applies live** (no reload — see protocol). - **Protocol**: extend `hi` to `{ t:'hi', name, avatar }` where `avatar = { hue: 0-11, gear: 0-4, face: 0-3 }` (integers only — server validates ranges, defaults on garbage). Add `{ t:'avatar', avatar }` for live changes; server stores per-peer and rebroadcasts; `hello.peers[]` carries it. `Avatars.add/updateAvatar` renders it (rebuild that peer's meshes; they're tiny). - Your own avatar isn't rendered (first person) — the preview is the mirror. ## Feature 2 — Emotes No text chat (nothing to moderate, nothing to store). Expression through gesture + classic DJ noises. - **Four emotes** on `Z X C V` (pointer-locked only, no hotbar collision): - `Z` **wave** — arm swing (procedural bone-less animation: swing a grouped arm box; avatars currently have no arms — add two small arm boxes to the avatar build, default hanging). - `X` **head-nod** — the head bobs ON THE BEAT (subscribe to `audio:beat` for phase; when the booth is silent, nod at 118 BPM anyway — funnier). Toggles on/off; auto-off after 16 bars. - `C` **point** — arm extends toward look direction 1.5 s. - `V` **airhorn** — the classic. Synthesized (three detuned saws, pitch droop), positional at the emoter, plus a confetti puff. **Rate-limited client AND server side (1 per 4 s per peer)** or the booth becomes hell. - **Protocol**: `{ t:'emote', kind: 0-3 }`, relayed with peer id, server rate-limits (drop, don't queue). Local emote plays immediately; remote emotes animate that peer's avatar + positional sound. - A small HUD hint row in the help menu; also show `Z X C V` glyphs briefly when another player emotes near you the first time. - **Self view**: first-person players can't see their own wave/point — play the sound + a tiny camera nudge so it feels like something happened. ## Feature 3 — The Reset Ritual ("blow the fuse") Once the mix is live, the quest is spent — groups need a way to play it again. Make un-breaking it diegetic and deliberate: - **The act**: the glass fuse in the PCB-Depths fuse box becomes breakable again after a win. Mining it takes a **deliberate 5-second hold** (not the usual glass 0.25 s — special-case this block position while the booth is won) with a HUD warning during the hold: *"KEEP MINING TO KILL THE MIX — everyone starts over"*. - **The consequence** (server-authoritative): client sends `{ t:'reset' }` only when the hold completes. Server validates: booth actually won AND ≥ 10 minutes since the last win (**anti-grief cooldown** — reject silently otherwise, client shows "the fuse is still hot" subtitle). On accept: server clears `repaired`, resets `platters` to stopped, resets workshop state to **assembled-but-undone** (cart seated + rotated correctly, all four wires POPPED OFF, screws at 0.5, weight at a random notch 3–6 — the fun parts replay, the tedium doesn't), keeps ALL block edits (player builds survive), persists, broadcasts `{ t:'reset', by: name }`. - **Client side**: this needs the one genuinely new mechanism — `Quest.reset()` and a bus event `'quest:reset': { by: string }`. Systems react: stems → 0 (music dies mid-bar — make it a power-down pitch drop, not a mute: `AudioEngine` brake-to-silence on all stems + light thunk), platters spin down, HUD tracker unlights + big subtitle *"⚡ DJ dusty42 BLEW THE FUSE"*, FxSystem drops emissive boost back to 0.35 over ~3 s (the booth visibly dies — this moment should feel HUGE), power switch un-arms/un-glows, workshop applies its reset pose, and the blown fuse block reappears in the box (worldgen block via `world.setBlock` — it's the one edit the reset writes). - Late joiners: server state already carries everything; verify a joiner post-reset sees a dead booth mid-second-run. ## Feature 4 — Who's here (small) Hold **Tab**: a translucent list of DJ names in the booth (you first), with each name's avatar hue as a swatch. Data you already have (`hello.peers` + join/leave). Release to hide. Also show total count on the existing online chip (already done) — just verify it stays correct through avatar/emote traffic. --- ## Protocol & server discipline (ship-check will look here) - New/changed messages: `hi` (+avatar), `avatar`, `emote`, `reset` (client→server intent; server→clients authoritative). Every field validated: enums as bounded ints, unknown fields ignored, oversized frames already capped at 2 KB — keep new payloads tiny. - Server-side rate limits: emote 1/4 s/peer; avatar changes 1/2 s/peer; reset once per cooldown window globally. - `booth-state.json` gains `winAt` timestamp (for the cooldown) — must load old state files without it (treat as cooldown-expired). - Old clients vs new relay: unknown incoming message types are already ignored client-side — verify, don't assume. ## Acceptance (live browser, two tabs minimum) - [ ] Avatar editor: change hue/gear/face in tab A → tab B sees it within 1 s, no reload; persists across reload; garbage from console → server defaults it - [ ] All four emotes visible/audible cross-tab; airhorn spam is capped (hold V — max 1 per 4 s lands remotely); nod syncs to the beat when music plays - [ ] Full reset ritual: win the booth (debug handle ok) → fuse hold-break → BOTH tabs see the booth die (music power-down, lights fade, tracker clears, platters stop), workshop in its reset pose, blown fuse back in the box; quest replayable to a second win; block edits survived; cooldown rejects an immediate second reset with the subtitle; joiner mid-second-run sees correct state - [ ] Tab list shows both DJs with hue swatches - [ ] `npm run typecheck` clean; zero console errors across a full two-tab session; relay fuzz (bad enums, wrong types) all dropped - [ ] Update `docs/INTEGRATION_NOTES.md` (short section) + Menu help text (emote keys, reset ritual hint in TIPS) ## Out of scope Text chat (deliberately never), voice, friend lists, persistent accounts, avatar parts beyond the enums above, mobile input. Don't touch the deploy scripts — the integrator ships it.