SIDE B briefs + contracts: crate worlds, per-world quests, stamps
Contract: src/core/worlds.ts (three pocket worlds above the booth walls, portal/entry/exit anchors, stamp count) + additive world/stamp bus events. Briefs: SIDEB overview + WORLDS / MACHINES / SOCIAL(ambience) / AUDIO lanes, rescoped around the already-shipped SOCIAL_RESET (emotes, reset ritual, avatars) and first-five-minutes (heartbeat, beacons, flythrough) phases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
caac3da930
commit
758e9dc4d3
53
docs/briefs/SIDEB.md
Normal file
53
docs/briefs/SIDEB.md
Normal file
@ -0,0 +1,53 @@
|
||||
# SIDE B — the crate worlds (phase overview)
|
||||
|
||||
*The booth is fixed and living. Now the crate opens. Three records in the
|
||||
under-table crate are pressed with whole scenes — dive in, do the thing the
|
||||
sound asks, press your stamp, come home. Plus dust bunnies and the golden
|
||||
finish for collecting all three.*
|
||||
|
||||
**Already shipped — do NOT rebuild (and read before touching anything
|
||||
adjacent):** emotes (Z/X/C/V), avatar looks, the server-authoritative reset
|
||||
ritual + cooldown, and the Tab roster are the SOCIAL_RESET phase
|
||||
(`docs/briefs/SOCIAL_RESET.md`, commit 05c2765, `src/net/avatarLook.ts`,
|
||||
`'player:emote'`/`'quest:reset'` events); the heartbeat, quest spark beacons,
|
||||
first-visit flythrough, and `'block:mining'` juice are the first-five-minutes
|
||||
pass (commit caac3da). SIDE B must coexist with all of it.
|
||||
|
||||
**Read first:** `docs/CONTRACTS.md`, `docs/DESIGN.md`,
|
||||
`docs/INTEGRATION_NOTES.md`, and the new contract `src/core/worlds.ts`
|
||||
(world keys, pocket bounds, portal/entry/exit anchors, emote vocab — FIXED,
|
||||
do not edit). Bus additions are already in `src/core/events.ts`.
|
||||
|
||||
## The four lanes (disjoint ownership — see your own brief)
|
||||
|
||||
| Lane | Brief | Owns |
|
||||
|---|---|---|
|
||||
| W — worlds | SIDEB_WORLDS.md | `src/worldgen/worlds/**`, small additive hooks in `buildBooth.ts` |
|
||||
| M — machines | SIDEB_MACHINES.md | `src/machines/**`, `src/interact/**`, `src/net/NetClient.ts`, `server/relay.mjs` |
|
||||
| S — ambience | SIDEB_SOCIAL.md | `src/fx/**`, `src/ui/**` (NOT `Avatars.ts` — SOCIAL_RESET owns avatars now) |
|
||||
| E2 — audio | SIDEB_AUDIO.md | `src/audio/**` |
|
||||
|
||||
**Merge points** (integrator wires them; keep your edits additive and tiny):
|
||||
`src/main.ts`. Nobody edits `src/core/**` (contract), another lane's dirs, or
|
||||
the atlas/renderer. **No new block ids** — id 31 stays reserved; palette from
|
||||
existing 31 blocks + atlas-v2 paint variants.
|
||||
|
||||
## Shared design laws
|
||||
|
||||
1. **Sound first.** Every quest readable by ear; visuals confirm.
|
||||
2. **Places, not points.** Pockets are rooms with a mood, not obstacle courses.
|
||||
3. **Nothing hard-fails.** Quests are retryable; leaving mid-quest is fine.
|
||||
4. **Multiplayer semantics:** pockets are *solo dives* — your avatar simply
|
||||
moves there (pockets are inside the same voxel world, so peers technically
|
||||
could follow; that's fine — treat co-visits as a bonus, not a design
|
||||
dependency). Only STAMPS sync (global, like repairs).
|
||||
5. **Zero binary assets.** Procedural canvas/synth only, as ever.
|
||||
6. Determinism, budgets: booth build < 900 ms, chunk meshes < 700, 100+ fps.
|
||||
|
||||
## Verification bar (every lane)
|
||||
|
||||
`npm run typecheck` clean; your demo page (or the real game via
|
||||
`window.TURNCRAFT`) exercised live with zero console errors; a handoff doc
|
||||
`docs/SIDEB_<lane>_handoff.md` listing files touched, events emitted/consumed,
|
||||
and anything the integrator must wire. **Do not commit** — shared tree; the
|
||||
integrator commits.
|
||||
61
docs/briefs/SIDEB_AUDIO.md
Normal file
61
docs/briefs/SIDEB_AUDIO.md
Normal file
@ -0,0 +1,61 @@
|
||||
# SIDE B — Lane E2: per-world grooves + SIDE B sound
|
||||
|
||||
Read `docs/briefs/SIDEB.md` + `src/core/worlds.ts` first. You own
|
||||
**`src/audio/**`**. React to bus events only. The main booth groove/stems
|
||||
engine is yours already — extend, don't fork.
|
||||
|
||||
## A1. World grooves (the big one)
|
||||
|
||||
On `world:enter {world}`: duck the booth mix out (300 ms) and start that
|
||||
world's loop; on `world:exit`: reverse. Same synth toolkit (synth.ts), same
|
||||
shrunk-time BPM feel. Keep each loop 2–4 bars, hypnotic, mixable levels.
|
||||
|
||||
- **acid**: 118 BPM, four-on-floor kick + off-hat, and THE 303: a 16-step
|
||||
sawtooth acid line through a resonant lowpass (Q high) + accent/slide feel.
|
||||
The line's filter cutoff is driven by three bands mapped to
|
||||
`fader:move {faderId:'acid_0'|'acid_1'|'acid_2'}` (0..1 from Lane M's
|
||||
pedestals): 0 = detuned/wrong (cutoff badly off per-band offsets), target ≈
|
||||
band's sweet value (Lane M owns the targets; you just map value→cutoff
|
||||
smoothly so sweeping is AUDIBLE and finding the sweet spot is satisfying —
|
||||
add a subtle "lock shimmer" layer per locked band via
|
||||
`machine:interact {action:'acid_lock'}`). On stamp: the full line + a clap
|
||||
layer for 4 bars of glory.
|
||||
- **dub**: 74 BPM half-time — deep sub bassline, sparse rimshot skank on the
|
||||
2/4 through a **feedback echo** (delay ~3/8, feedback 0.55, highcut) and a
|
||||
spring-reverb boing patch. `machine:interact {action:'dub_feed'}` = the big
|
||||
SPROING (bandpassed noise burst into the echo, feedback momentarily 0.8).
|
||||
While carrying the charge (Lane M emits `machine:interact
|
||||
{action:'dub_pickup'|'dub_drop'}`): the echo wet rises — you carry the wet.
|
||||
- **disco**: 122 BPM — four-on-floor, octave bass, offbeat open hat, a little
|
||||
string stab loop. `machine:interact {action:'disco_cue', index}` = per-tile
|
||||
cue blip (pitch by index, pentatonic); player steps echo the blip a fifth
|
||||
down. Stamp = filtered-disco string swell.
|
||||
|
||||
## A2. SIDE B SFX + stem tie-ins
|
||||
|
||||
- Portal: `world:enter` whoosh-drop (pitch dive + vinyl slow-down effect),
|
||||
`world:exit` reverse spin-up.
|
||||
- `stamp:got`: press-stamp THUNK + one-shot fanfare in that world's key.
|
||||
- `stamp:all`: golden riser; then permanently add a **bonus percussion stem**
|
||||
(shaker/conga layer) to the booth mix (new stem in the stems set, unmuted
|
||||
only when stamp:all has fired — persists via Lane M's synced stamp state;
|
||||
derive "all stamped" at boot by listening for `stamp:got` count===total or
|
||||
an initial re-emit — coordinate exact boot handshake in your handoff).
|
||||
- `machine:interact {action:'bunny_flee'}`: a shy squeak (soft, quiet, rare).
|
||||
|
||||
**Already shipped — coexist, don't rebuild:** emote audio (airhorn etc.),
|
||||
the reset ritual's power-down collapse, and the zero-repair HEARTBEAT
|
||||
(commit caac3da) are live. World grooves must duck WHATEVER the booth is
|
||||
playing — including the heartbeat pre-first-repair — and hand it back on
|
||||
exit; verify entering a pocket in a dead booth and in a won booth both sound
|
||||
right, and that a reset while you're inside a pocket doesn't wedge the mix
|
||||
(the exit handback must re-evaluate booth state, not restore a stale snapshot).
|
||||
|
||||
## Verify + handoff
|
||||
|
||||
Typecheck clean; extend `demo-audio.html` with a SIDE B panel (world
|
||||
enter/exit buttons, the three acid faders, dub feed, disco cues, emote pops,
|
||||
reset) and verify every path by ear + zero console errors in the demo AND a
|
||||
real-game smoke (launch config `turncraft-laneB` port 5184 if free).
|
||||
Handoff: `docs/SIDEB_E2_handoff.md` incl. any events you need Lane M to emit
|
||||
that aren't listed here (coordinate via handoff, not by editing their code).
|
||||
90
docs/briefs/SIDEB_MACHINES.md
Normal file
90
docs/briefs/SIDEB_MACHINES.md
Normal file
@ -0,0 +1,90 @@
|
||||
# SIDE B — Lane M: portals, world quests, stamps, relay
|
||||
|
||||
Read `docs/briefs/SIDEB.md` + `src/core/worlds.ts` first — **plus
|
||||
`docs/briefs/SOCIAL_RESET.md` and the SOCIAL_RESET handoffs**: the reset
|
||||
ritual, emotes, and `interaction.setBreakGuard` already exist and are
|
||||
server-authoritative — you build AROUND them, never on them. You own
|
||||
**`src/machines/**`, `src/interact/**`, `src/net/NetClient.ts`,
|
||||
`server/relay.mjs`**. Reuse the proven patterns: interaction tags + press/hold
|
||||
(see `machines/workshop/`), kinematic colliders, `MachineBase`. Keep the
|
||||
Workshop untouched except where noted.
|
||||
|
||||
## M1. Portals (src/machines/worlds/portal.ts or similar)
|
||||
|
||||
At each `WORLD_DEFS[k].portalStand`: an interaction collider ("DIVE INTO
|
||||
<NAME> (E)" via `workshop:msg`-style HUD text — use `workshop:msg`). On E:
|
||||
emit `world:enter {world}`, teleport the player to `entry` (PlayerController
|
||||
has `teleport`). At `exitPad`: walking onto it (or E) → `world:exit {world}` +
|
||||
teleport to `returnPos`. Falling out of a pocket must be impossible (Lane W
|
||||
seals shells), but add a safety: if player y > 141 and NOT inside any pocket
|
||||
bounds, teleport to `returnPos` of the nearest world (belt-and-braces).
|
||||
While inside a pocket, suppress the booth machines' interactions? NOT needed —
|
||||
distance already prevents it.
|
||||
|
||||
## M2. One mini-quest per world (own module per world)
|
||||
|
||||
All retryable, sound-first, no hard fail. On completion: emit stamp via relay
|
||||
(see M4), `stamp:got {world, count, total}` locally, and a `workshop:msg`
|
||||
line. Already-stamped worlds stay visitable (quest replays as a toy, no
|
||||
double-stamp).
|
||||
|
||||
- **ACID — Tune the 303.** Three knob pedestals (Lane W builds plinths at
|
||||
x=52, z=44/52/60, pocket-relative — colliders yours). Audio (Lane E2) loops
|
||||
a 2-bar acid line whose filter cutoff is WRONG on entry. Hold-E a pedestal =
|
||||
sweep its band (reuse hold/torque pattern; emit `workshop:torque` for the
|
||||
HUD gauge). Each pedestal has a hidden target 0..1 (seeded per entry);
|
||||
within ±0.07 of target = locked (its pip glows — emit
|
||||
`machine:interact {action:'acid_lock'}`). All three locked → the line
|
||||
snaps into tune → stamp. Emit `machine:interact {action:'acid_knob'}` with
|
||||
each meaningful change; Lane E2 maps value→cutoff via
|
||||
`fader:move {faderId:'acid_0'..'acid_2'}` (reuse that event, values 0..1).
|
||||
- **DUB — Feed the spring.** An echo charge (glowing carryable, like the wire
|
||||
carry — reuse the carry pattern with `isCarrying`-style state) sits at the
|
||||
horn. Carry it across the spring-tank bridge: you overlay a 3-wide moving
|
||||
kinematic deck on Lane W's static chrome deck (segments like the workshop
|
||||
beam, gentle sine wobble ±0.35 rad/s max — the ride-snap handles it). Reach
|
||||
the tank mouth (chasm centre), E to drop → `machine:interact
|
||||
{action:'dub_feed'}` → stamp. Falling into the chasm floor: charge returns
|
||||
to the horn, subtitle "THE SPRING WANTS IT — TRY AGAIN".
|
||||
- **DISCO — Light the floor.** The 4×4 tile grid (Lane W lays it; your
|
||||
colliders detect standing-on-tile). On `audio:beat`, flash a sequence of 4
|
||||
growing to 8 tiles (`machine:interact {action:'disco_cue', index}` so Lane S
|
||||
can light them); the player repeats it by stepping tiles between beats.
|
||||
Wrong tile = gentle reset ("THE FLOOR FORGIVES — WATCH AGAIN"). Full 8 →
|
||||
stamp.
|
||||
|
||||
## M3. Stamps + golden unlock
|
||||
|
||||
Track stamps in a small `Stamps` store (Set<WorldKey>) synced via relay (M4),
|
||||
persisted server-side like repairs. On count===3 (any client): emit
|
||||
`stamp:all`, and swap deck A's slipmat material to a golden variant (you own
|
||||
platter.ts — add `setGoldenSlipmat(on)`) + Lane E2's bonus stem fires.
|
||||
On join, relay state re-applies stamps (idempotent).
|
||||
|
||||
## M4. Relay protocol (server/relay.mjs + NetClient)
|
||||
|
||||
ONE additive message, validated default-closed like `workshop` (emotes and
|
||||
unrepair/reset already exist — touch neither):
|
||||
- `{t:'stamp', w}` — w in acid|dub|disco. Adds to a persisted `stamps` set;
|
||||
rebroadcast; included in the join snapshot. Reject anything else.
|
||||
Keep frame caps. Client mirror: NetClient sends stamps, applies incoming +
|
||||
join snapshot (`stamp` → Stamps store + `stamp:got`, idempotent).
|
||||
|
||||
## M5. Coexist with the reset ritual (do not rebuild it)
|
||||
|
||||
The SOCIAL_RESET reset (5 s fuse hold → server-authoritative reset, 10-min
|
||||
cooldown) already resets the quest + workshop. Your additions must survive a
|
||||
full reset→re-win cycle: **stamps persist through resets** (they're pressed
|
||||
into the records, not the booth — assert this), portals keep working while
|
||||
the booth is dead, and world quests remain playable. Verify one full cycle.
|
||||
|
||||
## Verify + handoff
|
||||
|
||||
Typecheck clean; drive everything headless via `window.TURNCRAFT` in the real
|
||||
game (dev port: launch config `turncraft-laneD-verify`, 5194) with zero
|
||||
console errors: enter/exit each world, complete each quest, stamp sync across
|
||||
a relay round-trip (run `node server/relay.mjs` locally; NOTE: delete
|
||||
`server/booth-state.json` before AND after — stale state makes clients join
|
||||
completed), plus one full reset→re-win cycle with stamps surviving. Fuzz the
|
||||
new relay message (bad kinds/shapes rejected). Handoff:
|
||||
`docs/SIDEB_M_handoff.md` incl. exact event vocab emitted for Lanes S/E2.
|
||||
45
docs/briefs/SIDEB_SOCIAL.md
Normal file
45
docs/briefs/SIDEB_SOCIAL.md
Normal file
@ -0,0 +1,45 @@
|
||||
# SIDE B — Lane S: dust bunnies, portal FX, HUD stamp tray
|
||||
|
||||
Read `docs/briefs/SIDEB.md` + `src/core/worlds.ts` first. You own
|
||||
**`src/fx/**` and `src/ui/**`** — NOT `src/net/Avatars.ts` (the SOCIAL_RESET
|
||||
phase owns avatars/emotes now; `docs/briefs/SOCIAL_RESET.md` is required
|
||||
reading so you don't duplicate its FX). Consume bus events only — never
|
||||
import Lane M/W code.
|
||||
|
||||
## S1. Dust bunnies (fx critters, purely cosmetic)
|
||||
|
||||
3–4 fist-sized fluff-balls (procedural: a tiny THREE group of 8–12 grey
|
||||
dust-tone quads, jittered) drifting around the under-table PCB floor (y≈3).
|
||||
Keep-out zones: the three portal rings (`WORLD_DEFS[k].portalStand` ±6) and
|
||||
the parts-bin/fuse quest spots. Wander slowly; flee 8 voxels when the player
|
||||
comes within 5 (ease out, never through walls — sample `world.isSolid` ahead);
|
||||
settle again. No colliders, no game effect. Emit
|
||||
`machine:interact {action:'bunny_flee'}` at most 1/s when spooked (Lane E2
|
||||
squeaks). Budget: zero steady-state allocations, no fps regression.
|
||||
|
||||
## S2. Portal + stamp FX
|
||||
|
||||
- `world:enter`: 400 ms vinyl-warp iris — full-screen overlay of concentric
|
||||
groove rings collapsing to black, accent-tinted per world
|
||||
(`WORLD_DEFS[k].accent`); release on arrival. `world:exit`: reversed.
|
||||
(Coexist with the first-visit flythrough's cine override — never fight the
|
||||
camera; you're a screen overlay only.)
|
||||
- `stamp:got`: confetti burst (reuse the FxSystem burst pattern) + HUD toast
|
||||
via the tray (S3).
|
||||
- `stamp:all`: golden shimmer — a brief booth-wide emissive pulse via the
|
||||
existing `setEmissiveBoost` path, plus gold confetti at the crate.
|
||||
|
||||
## S3. HUD stamp tray
|
||||
|
||||
Small tray top-right under the signal lamps: three record-stamp dots
|
||||
(accent-coloured when got, dim ring when not), scale-pulse on `stamp:got`,
|
||||
gold ring on `stamp:all`. Initial state comes from replayed `stamp:got`
|
||||
events on join (Lane M re-emits from the join snapshot — coordinate exact
|
||||
timing via handoffs).
|
||||
|
||||
## Verify + handoff
|
||||
|
||||
Typecheck clean; exercise in the real game (`turncraft-laneE-verify` launch
|
||||
config) — bunnies wander/flee with zero console errors and measured fps
|
||||
unchanged; portal iris and tray driven by hand-emitted bus events. Handoff:
|
||||
`docs/SIDEB_S_handoff.md`.
|
||||
55
docs/briefs/SIDEB_WORLDS.md
Normal file
55
docs/briefs/SIDEB_WORLDS.md
Normal file
@ -0,0 +1,55 @@
|
||||
# SIDE B — Lane W: the three pockets (worldgen)
|
||||
|
||||
Read `docs/briefs/SIDEB.md` first. You own **`src/worldgen/worlds/**`** (new)
|
||||
plus small additive edits in `src/worldgen/buildBooth.ts` (portal dressing +
|
||||
calling your builders). Do not move any existing anchor or LAYOUT position.
|
||||
|
||||
## W1. Portal dressing at the crate (buildBooth additive)
|
||||
|
||||
The three portal discs (`WORLD_DEFS[k].portalDisc`, r=12, already built by
|
||||
buildCrate) get: a 1-voxel `led_*` ring inlay on the crate floor around
|
||||
`portalStand` (accent-coloured: acid=led_green, dub=led_amber, disco=led_blue —
|
||||
approximate accents with existing led blocks), and a small marquee of 2–3
|
||||
accent led voxels on the disc rim. Subtle — the crate stays dim; the rings are
|
||||
the "these three are special" tell.
|
||||
|
||||
## W2. Three pocket rooms (src/worldgen/worlds/)
|
||||
|
||||
One module per world + an index that exposes `buildWorlds(w: IVoxelWorld)`
|
||||
called once from buildBooth (after everything else; same seed discipline —
|
||||
use `mulberry32`, never Math.random). Each pocket: fully enclosed shell at
|
||||
`pocketMin..pocketMax` (outer skin `matte_black` so undersides read as shadow
|
||||
from the booth; NO light leaks — every interior emissive fully enclosed),
|
||||
floor/walls/ceiling themed inside, interior height ~14.
|
||||
|
||||
- **ACID WAREHOUSE** (x 20..84): raw concrete feel — `steel_grey`/`brushed_alu`
|
||||
floor slab variants, breeze-block wall rhythm from `ply_edge`, pillars,
|
||||
`led_green` cable runs snaking wall→floor→three knob pedestals (Lane M places
|
||||
machines AT `entry`-relative positions listed in their brief — you build the
|
||||
pedestals: three 3×3×3 `matte_black` plinths at z 44 / 52 / 60 along x=52,
|
||||
1-voxel green pip on each). A strobe rail of `strobe_dot` on the ceiling.
|
||||
- **DUB CHAMBER** (x 192..256): deep space — `vinyl_black` walls with sparse
|
||||
`led_amber` embers, a central chasm (floor drops 4 at x 210..238) crossed by
|
||||
a 3-wide wobbling **spring-tank bridge** footprint: you lay the STATIC deck
|
||||
(`chrome` 3×24 along z at x 224); Lane M overlays the moving collider. A big
|
||||
horn-speaker cone of stacked `speaker_mesh` rings on the far wall.
|
||||
- **DISCO LOFT** (x 364..428): warm parquet — `plywood`/`ply_edge` herringbone
|
||||
floor with a 4×4 grid of 5×5 **dance tiles** (flush, alternating
|
||||
`label_cream`/`rubber` borders, centre voxel `led_*` per tile — Lane M lights
|
||||
them; you place the tile grid centred on [396, 143, 52..76]), a mirrorball of
|
||||
`chrome`+`glass` voxels hanging centre-ceiling, `led_red`/`led_blue` wall
|
||||
wash strips.
|
||||
|
||||
Exit pads: 3×3 `rca_gold` pad flush at each `exitPad`, one accent led pip.
|
||||
|
||||
## W3. Budgets & proof
|
||||
|
||||
Deterministic (two builds byte-identical); booth build < 900 ms total; chunk
|
||||
meshes < 700; zero visibility from inside the booth (fly the cine camera
|
||||
around the booth at night — no glow leaks) AND from the first-visit
|
||||
flythrough path (commit caac3da rides the cine override high over the booth —
|
||||
run it and confirm the pocket shells read as, at most, faint black slabs in
|
||||
the dark). Update `worldgenDemo` validation
|
||||
with pocket assertions (shell sealed, entry/exit air, pedestals present).
|
||||
Handoff: `docs/SIDEB_W_handoff.md`. Dev port: use launch config
|
||||
`turncraft-laneC-verify` (5212).
|
||||
@ -36,6 +36,14 @@ export type GameEvents = {
|
||||
'workshop:torque': { value: number | null }; // radial HUD meter (null hides)
|
||||
'workshop:msg': { text: string }; // transient HUD subtitle
|
||||
|
||||
// SIDE B — crate worlds + stamps (additive per the SIDEB briefs; primitives
|
||||
// only — world vocab lives in core/worlds.ts). Emotes and the reset ritual
|
||||
// predate this phase ('player:emote' / 'quest:reset' above).
|
||||
'world:enter': { world: string }; // player dove into a pocket
|
||||
'world:exit': { world: string }; // player returned to the booth
|
||||
'stamp:got': { world: string; count: number; total: number };
|
||||
'stamp:all': Record<string, never>; // golden slipmat unlock
|
||||
|
||||
// Mining-in-progress juice (first-five-minutes pass). Emitted ~8 Hz while
|
||||
// a block is being held-mined; FX puffs and audio ticks react. FX-only —
|
||||
// no gameplay reads this.
|
||||
|
||||
57
src/core/worlds.ts
Normal file
57
src/core/worlds.ts
Normal file
@ -0,0 +1,57 @@
|
||||
// TURNCRAFT — SIDE B contract: the crate worlds. This file is CONTRACT: lanes
|
||||
// read it, never edit it. Like LAYOUT/QUEST anchors, these are the fixed
|
||||
// positions everything else builds against.
|
||||
//
|
||||
// Fiction: three of the records standing in the under-table crate are pressed
|
||||
// with whole scenes. Press E beside one and you dive into it — a pocket world
|
||||
// in the dark above the booth walls (fully shelled, invisible from inside the
|
||||
// booth). Each pocket holds one sound-first mini-quest; completing it presses
|
||||
// a STAMP into your copy. All three stamps → the golden slipmat rises.
|
||||
|
||||
import type { Vec3 } from './types';
|
||||
|
||||
export type WorldKey = 'acid' | 'dub' | 'disco';
|
||||
export const WORLD_KEYS: readonly WorldKey[] = ['acid', 'dub', 'disco'];
|
||||
export const STAMP_TOTAL = 3;
|
||||
|
||||
export interface WorldDef {
|
||||
key: WorldKey;
|
||||
name: string; // display name (HUD, flyers)
|
||||
/** Crate disc that is this world's portal (from buildCrate: x=26+5s, cy=15, cz=31, r=12). */
|
||||
portalDisc: Vec3; // disc centre
|
||||
portalStand: Vec3; // where the player stands to use it (crate floor)
|
||||
/** Pocket shell bounds, INCLUSIVE, fully enclosed (1-voxel shell, interior dark-lit). */
|
||||
pocketMin: Vec3;
|
||||
pocketMax: Vec3;
|
||||
entry: Vec3; // player spawn inside the pocket (feet)
|
||||
exitPad: Vec3; // stand here + E (or walk onto pad) to return
|
||||
returnPos: Vec3; // reappear here at the crate
|
||||
accent: [number, number, number]; // FX/HUD accent colour
|
||||
}
|
||||
|
||||
export const WORLD_DEFS: Record<WorldKey, WorldDef> = {
|
||||
acid: {
|
||||
key: 'acid', name: 'ACID WAREHOUSE',
|
||||
portalDisc: [31, 15, 31], portalStand: [31, 3, 36],
|
||||
pocketMin: [20, 142, 20], pocketMax: [84, 158, 84],
|
||||
entry: [52, 144, 30], exitPad: [52, 143, 78], returnPos: [34, 3, 36],
|
||||
accent: [96, 255, 96],
|
||||
},
|
||||
dub: {
|
||||
key: 'dub', name: 'DUB CHAMBER',
|
||||
portalDisc: [46, 15, 31], portalStand: [46, 3, 36],
|
||||
pocketMin: [192, 142, 20], pocketMax: [256, 158, 84],
|
||||
entry: [224, 144, 30], exitPad: [224, 143, 78], returnPos: [49, 3, 36],
|
||||
accent: [255, 176, 40],
|
||||
},
|
||||
disco: {
|
||||
key: 'disco', name: 'DISCO LOFT',
|
||||
portalDisc: [61, 15, 31], portalStand: [61, 3, 36],
|
||||
pocketMin: [364, 142, 20], pocketMax: [428, 158, 84],
|
||||
entry: [396, 144, 30], exitPad: [396, 143, 78], returnPos: [64, 3, 36],
|
||||
accent: [255, 120, 220],
|
||||
},
|
||||
};
|
||||
|
||||
// (Emotes shipped in the SOCIAL_RESET phase — see src/net/avatarLook.ts and
|
||||
// the 'player:emote' bus event. This contract deliberately adds none.)
|
||||
Loading…
Reference in New Issue
Block a user