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>
This commit is contained in:
jing 2026-07-16 23:04:46 +10:00
parent 74482e5693
commit 036748543b

156
docs/briefs/SOCIAL_RESET.md Normal file
View File

@ -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 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.