diff --git a/docs/briefs/SOCIAL_RESET.md b/docs/briefs/SOCIAL_RESET.md new file mode 100644 index 0000000..818a341 --- /dev/null +++ b/docs/briefs/SOCIAL_RESET.md @@ -0,0 +1,156 @@ +# 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.