TURNCRAFT/docs/briefs/SOCIAL_RESET.md
jing 036748543b Brief: Social & the Reset Ritual (avatars, emotes, blow-the-fuse)
- Avatar customization: hue/headgear/face enums, Menu AVATAR tab with 2D
  preview, live sync via extended hi + new avatar message (no moderation
  surface - palettes only, no free-form content).
- Emotes on Z X C V: wave, beat-synced head-nod, point, rate-limited
  airhorn with positional synth + confetti.
- Reset ritual: after a win the glass fuse becomes hold-breakable (5s,
  warned); server-authoritative reset with 10-min cooldown - music
  power-downs, lights die, workshop resets to wires-off, block edits
  survive, quest replayable. Needs Quest.reset() + 'quest:reset' bus event.
- Tab-hold player list.
Protocol validation/rate-limit requirements spelled out for ship-check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 23:04:46 +10:00

157 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 36 — 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.