Lane docs F (audio), G (colour zones/puddles/MEGA-MINI), H (ghost/title)
Driven by the live fresh-eyes finding: a naive racer finishes with 0% paint — the core mechanic is currently skippable. G makes paint route-guaranteed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
dd94161b8e
commit
fedb43817c
44
lanes/LANE-F-audio.md
Normal file
44
lanes/LANE-F-audio.md
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Lane F — Audio (procedural, event-driven)
|
||||||
|
|
||||||
|
**Owns:** `src/audio/`, `demos/lane-f.html`, `src/demo/lane-f.ts`.
|
||||||
|
**Never touches:** contracts, world, game, main, package.json, other lanes' dirs.
|
||||||
|
No new deps, no audio asset files — **everything synthesized with WebAudio**
|
||||||
|
(oscillators, noise buffers, filters, envelopes). Comedy > realism.
|
||||||
|
|
||||||
|
## Mission
|
||||||
|
BLOBBO is silent. Sound is half the comedy of a wobble game. Build a tiny
|
||||||
|
procedural sound engine that reacts to the frozen wire events only.
|
||||||
|
|
||||||
|
## Deliverables (`src/audio/`)
|
||||||
|
1. `engine.ts` — `AudioEngine`: lazy `AudioContext` (create/resume ONLY after
|
||||||
|
the `ui:play` event or first user gesture — browsers block earlier), master
|
||||||
|
gain, mute toggle (persist in localStorage `blobbo:muted`), and a small
|
||||||
|
polyphony cap (~8 voices) so event storms don't clip.
|
||||||
|
2. `sounds.ts` — synthesized one-shots, all pitch-randomized ±10% per play:
|
||||||
|
- **squish** (blob:landed — gain and pitch scale with `impact`; big drops = big splat)
|
||||||
|
- **boing** (blob:jumped — springy sine sweep; also used by the gap launcher)
|
||||||
|
- **splat** (paint:splatted target:'blob' — wet noise burst; slightly
|
||||||
|
different pitch per colour, use PALETTE order for a pentatonic-ish mapping)
|
||||||
|
- **sparkle** (paint:request-cleanse — bubbly arpeggio)
|
||||||
|
- **clank+windup** (machine:signal — ratchet tick accelerating ~0.5s, matches the telegraph)
|
||||||
|
- **fanfare** (race:finished — dumb triumphant 4-note; extra flourish if new best)
|
||||||
|
- **tick** (race:started — single starter blip)
|
||||||
|
- optional ambient: very quiet lo-fi room tone / conveyor hum when near machines — only if cheap.
|
||||||
|
3. `install.ts` — `installAudio(world)`: subscribes to the events above on
|
||||||
|
`world.events`, returns `{ engine }`. Listens for `ui:play` to unlock.
|
||||||
|
A tiny 🔈/🔇 DOM button bottom-right toggles mute.
|
||||||
|
4. Demo `demos/lane-f.html` + `src/demo/lane-f.ts`: sound board — a button per
|
||||||
|
sound + sliders for the impact parameter. Verifies every sound in isolation.
|
||||||
|
|
||||||
|
## Acceptance
|
||||||
|
- `npm run build` passes. No AudioContext created before first gesture/`ui:play`
|
||||||
|
(verify: constructing the engine logs nothing and `ctx === null` until unlock).
|
||||||
|
- Every listed event produces a distinct, non-clipping sound; rapid splat storms
|
||||||
|
(10/s) stay clean via the voice cap.
|
||||||
|
- Mute persists across reloads.
|
||||||
|
- Sounds are SHORT (<400ms except fanfare) and mixed well below pain (master ~-12dB).
|
||||||
|
|
||||||
|
## Events you consume (never emit gameplay events)
|
||||||
|
`blob:landed {impact}`, `blob:jumped`, `paint:splatted {color,target}`,
|
||||||
|
`paint:request-cleanse`, `machine:signal {id}`, `race:started`,
|
||||||
|
`race:finished {time,best}`, `ui:play` (from Lane H's title screen).
|
||||||
65
lanes/LANE-G-zones.md
Normal file
65
lanes/LANE-G-zones.md
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# Lane G — Colour zones, paint puddles, MEGA/MINI
|
||||||
|
|
||||||
|
**Owns:** `src/paint/puddles.ts`, `src/paint/buffs.ts` (extend — you inherit
|
||||||
|
Lane B's file), `src/course/zones.ts`, `demos/lane-g.html`, `src/demo/lane-g.ts`.
|
||||||
|
**Never touches:** contracts, world, game, main, package.json, `src/blob/`,
|
||||||
|
`src/machine/` (USE machine parts via import — just don't edit them), other
|
||||||
|
lane dirs. No new deps.
|
||||||
|
|
||||||
|
## Why this lane exists (live-play finding, 2026-07-18)
|
||||||
|
A naive racer currently finishes the course with **0% paint** — ballistic
|
||||||
|
cannons miss fast movers and nothing on the ground paints you. The core
|
||||||
|
mechanic is skippable. Fix: paint must be guaranteed by ROUTE, not by aim, and
|
||||||
|
colours must come in ZONES so buffs actually activate (20% threshold is
|
||||||
|
unreachable when three colours dilute each other — also observed live).
|
||||||
|
|
||||||
|
## Deliverables
|
||||||
|
1. `src/paint/puddles.ts` — `PaintPuddle`: a flat coloured strip on the track
|
||||||
|
(mesh + config `{position, size, color}`). While the blob's ground contact
|
||||||
|
is inside the strip, stamp its UNDERSIDE via `skin.splatAtPoint` at the
|
||||||
|
contact point on a cadence (~6 stamps/s, small radius) — rolling through
|
||||||
|
paint coats your belly. Registered via an `installPuddles(world, blob, skin,
|
||||||
|
configs)` entry point (integration calls it). Emit `paint:splatted
|
||||||
|
{color, target:'blob'}` per stamp (audio hooks it).
|
||||||
|
2. `src/paint/buffs.ts` — extend the coverage→modifier mapping (GDD §6):
|
||||||
|
- **purple MEGA**: `size` 1.0→1.45 + extra `massMul` (heavy). Strength scales
|
||||||
|
20%→70% like the others.
|
||||||
|
- **pink MINI**: `size` 1.0→0.62, LESS mass (lighter than clean at high %).
|
||||||
|
- purple and pink fight: net size uses (purple − pink) strength.
|
||||||
|
- Existing red/green/blue unchanged. Keep the pure-function style +
|
||||||
|
UPDATE `src/paint/coverage-math.test.ts` for the new mappings.
|
||||||
|
3. `src/course/zones.ts` — `installZones(world, blob, skin)`: builds the colour
|
||||||
|
zones ON the existing greybox (import nothing from game.ts; use absolute
|
||||||
|
course coordinates — the course spans z 30 → -70, see src/course/greybox.ts):
|
||||||
|
- **RED zone** (ramp approach, z≈20..8): red puddle strip + the existing
|
||||||
|
red cannon position gets a second red cannon (crossfire). BURN speed helps
|
||||||
|
the climb.
|
||||||
|
- **GREEN zone** (shelves + slope, z≈8..-24): green puddles on both shelves;
|
||||||
|
GRIP helps the slope and gravity-flip future.
|
||||||
|
- **BLUE zone** (milk river, z≈-24..-37): blue puddle lines the riverbanks;
|
||||||
|
waterproof = your paint survives the water.
|
||||||
|
- **PURPLE/PINK fork** (machine district, z≈-37..-58): LEFT lane (x<0):
|
||||||
|
purple puddle → MEGA → heavy enough to trip the pressure plate → boot yeet
|
||||||
|
(the course TEACHES paint=weight). RIGHT lane (x>3): pink puddle → MINI →
|
||||||
|
a **low tunnel** (build it: a slab roof over the track at y≈0.9, only a
|
||||||
|
MINI blob fits under) that shortcuts toward the finish. Normal-size blobs
|
||||||
|
bonk and must take another lane.
|
||||||
|
- Cannons: relocate/add so each zone's cannons fire ITS colour (configs
|
||||||
|
returned so integration can place them; or place them yourself in
|
||||||
|
installZones — your call, document it).
|
||||||
|
4. Demo `demos/lane-g.html` + `src/demo/lane-g.ts`: a test ball rolling through
|
||||||
|
each zone in sequence with live coverage/modifier logging; prove: puddles
|
||||||
|
coat reliably, purple grows + trips a test plate, pink shrinks + fits the
|
||||||
|
tunnel while a clean ball bonks.
|
||||||
|
|
||||||
|
## Acceptance
|
||||||
|
- `npm run build` + updated coverage-math tests pass.
|
||||||
|
- A straight-line hold-forward run through the zones ends with ≥40% total
|
||||||
|
coverage and at least one active buff (puddles guarantee it — verify in demo).
|
||||||
|
- Purple demo ball trips a `massThreshold: baseMass*1.25` plate; clean ball doesn't.
|
||||||
|
- Pink demo ball (≥35% pink) passes under the tunnel; clean ball cannot.
|
||||||
|
- No edits outside owned paths.
|
||||||
|
|
||||||
|
## Contracts
|
||||||
|
Emit `paint:splatted` per puddle stamp. Existing events unchanged. `blob.modifiers.size`
|
||||||
|
is already honored by the controller (collider + visual scale) — do not touch the blob.
|
||||||
47
lanes/LANE-H-ghost-title.md
Normal file
47
lanes/LANE-H-ghost-title.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Lane H — Ghost replay + title screen
|
||||||
|
|
||||||
|
**Owns:** `src/ghost/`, `src/ui/`, `demos/lane-h.html`, `src/demo/lane-h.ts`.
|
||||||
|
**Never touches:** contracts, world, game, main, package.json, other lanes' dirs.
|
||||||
|
No new deps.
|
||||||
|
|
||||||
|
## Mission
|
||||||
|
Two pieces that turn a tech slice into a GAME you return to:
|
||||||
|
a ghost of your best run to race against, and a title card that explains the
|
||||||
|
game in five seconds (and unlocks audio).
|
||||||
|
|
||||||
|
## Deliverables
|
||||||
|
1. `src/ghost/recorder.ts` — `GhostRecorder`: on `race:started`, record the
|
||||||
|
blob root's position + uniform scale at 15Hz into a flat Float32Array-backed
|
||||||
|
buffer (cap ~5 min). On `race:finished`, if this run is the new best, save
|
||||||
|
quantized (int16 centimeters is plenty) + the time to localStorage
|
||||||
|
`blobbo:ghost` (base64). Keep total under ~200KB.
|
||||||
|
2. `src/ghost/player.ts` — `GhostPlayer`: if a saved ghost exists, on
|
||||||
|
`race:started` spawn a translucent ghost blob (simple sphere + eyes is fine,
|
||||||
|
opacity ~0.35, no physics — kinematic visual only) and replay it in race
|
||||||
|
time with linear interpolation between samples. Despawn on finish/respawn.
|
||||||
|
The ghost must be OBVIOUSLY not-you (ghostly white/desaturated, slight bob).
|
||||||
|
3. `src/ghost/install.ts` — `installGhost(world, blob)` wiring both to events.
|
||||||
|
4. `src/ui/title.ts` — `installTitle(world)`: full-screen overlay on boot:
|
||||||
|
- big BLOBBO wordmark (styled CSS bubble text is fine — thick white letters,
|
||||||
|
rainbow paint-drip accents, Comic-adjacent but tasteful),
|
||||||
|
- one-line pitch: "Race. Get painted. Paint is power.",
|
||||||
|
- controls card: WASD/stick · Space/A jump · R/Start restart,
|
||||||
|
- a fat **PLAY** button. On click/keypress/gamepad-button: fade out,
|
||||||
|
emit **`ui:play`** on world.events (audio unlocks on this), start allowed.
|
||||||
|
- While Rapier/world is still booting, show the same overlay with a wobbling
|
||||||
|
"loading…" blob dot instead of PLAY (installTitle can be handed a promise).
|
||||||
|
- Show saved best time + "👻 ghost ready" if `blobbo:ghost` exists.
|
||||||
|
5. Demo `demos/lane-h.html` + `src/demo/lane-h.ts`: fake a 20s scripted "run"
|
||||||
|
(a ball on a circle), record it, finish, then restart and race the ghost.
|
||||||
|
Title screen in front. Debug key G wipes the saved ghost.
|
||||||
|
|
||||||
|
## Acceptance
|
||||||
|
- `npm run build` passes; demo shows title → play → run 1 records → run 2 shows
|
||||||
|
interpolated ghost racing the circle; localStorage survives reload; wipe works.
|
||||||
|
- `ui:play` emitted exactly once per page life; overlay never reappears mid-game.
|
||||||
|
- Ghost replay drift vs recorded path < one blob radius at all sample points
|
||||||
|
(it's the SAME deterministic path in the demo — verify numerically).
|
||||||
|
- No edits outside owned paths.
|
||||||
|
|
||||||
|
## Events
|
||||||
|
Consume `race:started`, `race:finished {time,best}`. Emit `ui:play` only.
|
||||||
Loading…
Reference in New Issue
Block a user