GDD v1 (flow-locked hybrid movement, 5 biomes + secret, boss roster), TECH contracts (world API, level schema v0, bus events, manifest law), ART_BIBLE (synthetic scanner look), PIPELINE (MODELBEAST-first, fal gated), PROCESS (PROCITY lane/round law), charters A-F + ROUND1 instructions. Seed code: shell + boot + core (rng/bus/flags) + stub world (peristalsis shader tube, 1 draw / 102k tris, contract-complete) + qa.sh (GREEN). Verified in-browser; evidence docs/shots/laneF/round0_stub_tube.png. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6.6 KiB
TECH — architecture, contracts, ownership
Stack: vanilla three.js r175 (vendored, web/vendor/), ES modules + import map, zero
build step, python3 -m http.server to run. No physics engine, no framework. This is house
style (proven in PROCITY) — keep it.
Module layout & ownership
web/
index.html F shell + import map + flags table wiring
js/boot.js F creates renderer/scene/loop, wires lane factories, window.DBG
js/core/rng.js F mulberry32 seeded RNG (THE only randomness source)
js/core/bus.js F event bus (on/off/emit)
js/core/flags.js F URL flag parsing
js/core/assets.js D manifest loader + `depot:`/local resolution + fallback law
js/stub/world_stub.js F straight-tube world implementing the world contract
js/world/** A createWorld() — spline, tube chunks, arenas, biome shaders
js/flight/** B createPlayer() — controller, camera rig, collision response
js/combat/** B weapons, damage, enemy framework + behaviors
js/levels/*.json C level data (schema below)
js/levels/index.js C level registry + loader + schema selfcheck
js/ui/** E HUD, menus, gut-map, screens
js/audio/** E WebAudio engine (plays D's pack; procedural fallback)
assets/** D manifest.json + generated webp/glb/ogg/m4a + thumbs
pipeline/** D generation scripts (run on the m3ultra box)
tools/qa.sh F the gate
docs/** all (own lane's files only)
Never edit outside your column. Shell wiring requests → your NOTES (### → Lane F), with
the exact snippet to paste (PROCITY house style).
Lane factory convention
Every lane exports pure factories, constructed by boot, no side effects at import time:
export function createX({ scene, world, bus, rng, flags, assets }) {
return { update(dt, ctx){}, dispose(){}, /* lane-specific API */ };
}
Default-off behind flags until F wires them on. Dispose must actually free GPU resources (geometries, materials, textures) — enter/exit cycles are leak-tested.
THE WORLD CONTRACT (A implements; stub implements it today; frozen after round-1 sign-off)
Spline-space: s = arclength along the canal centreline (units), θ = angle in the
cross-section (rad), ρ = radial distance from centreline. Units are metres in three.js
terms; fiction scale ≈ 1 unit : 1 mm. +Y up, right-handed.
const world = await createWorld(levelData, { rng, quality });
world.length // total arclength
world.group // THREE.Group (boot adds to scene)
world.sample(s) // → { pos, tan, nor, bin, radius } (parallel-transport frame)
world.project(pos) // → { s, theta, rho } (world → spline space, player-local fast path)
world.wallRho(s, theta) // → max safe ρ (radius minus conservative displacement amplitude)
world.collide(pos, r) // → null | { push:Vector3, kind:'wall'|'hazard', biome }
world.biomeAt(s) // → { id, palette, fog, flow, coatDrain } (see biomes.js registry)
world.modeAt(s) // → 'tube' | 'arena'
world.arenaAt(s) // → null | { center:Vector3, radius } (bounds for 6DOF clamp)
world.update(dt, playerS) // stream chunks, advance shader time
Determinism law: same levelData.seed ⇒ byte-identical geometry. A maintains a golden
hash (world.hash() over sampled frames) checked by qa.
Level data schema v0 (C owns; F drafted; sign-off in round 1)
{
"id": "L2_esophagus", "name": "Esophagus", "seed": 20260716,
"segments": [ // concatenated along s
{ "biome": "esophagus", "length": 600,
"radius": { "base": 10, "wobble": 0.25 }, // wobble: seeded radius variation 0..1
"curviness": 0.6, // 0 straight .. 1 writhing
"flow": 14 } // base current, units/s
],
"arenas": [ { "at": 580, "radius": 90, "biome": "stomach" } ],
"events": [ // consumed by B (spawns) & E (telegraphs) via bus
{ "s": 40, "type": "spawn", "enemy": "bolus_chunk", "count": 6 },
{ "s": 120, "type": "hazard", "kind": "reflux_surge", "speed": 22 },
{ "s": 300, "type": "checkpoint" },
{ "s": 560, "type": "boss", "id": "pyloric_guardian" }
]
}
Bus events (append here when adding; F referees name collisions)
player:spawn {s} · player:damage {amount, kind} · player:death · player:boost ·
enemy:spawn {id,type} · enemy:die {id,type,s} · pickup {kind} · level:event {…} (raw
C events as player crosses their s) · level:checkpoint {s} · level:complete {stats} ·
boss:start {id} / boss:phase / boss:end · combo {n} · audio:cue {name}.
Asset manifest contract (D owns assets.js + manifest.json)
{ "textures": { "wall_esophagus_a": { "url": "gen/wall_esophagus_a.webp",
"normal": "gen/wall_esophagus_a_n.webp", "tile": [3, 8] } },
"matcaps": { "tissue_wet": { "url": "gen/matcap_tissue.webp" } },
"models": { "macrophage": { "url": "models/macrophage.glb", "tris": 4200 } },
"audio": { "beds": { "stomach": {"ogg": "...", "m4a": "..."} }, "sfx": { … } } }
Law: every entry is optional at runtime. assets.get('textures','x') returns null on
miss and the consumer must have a procedural fallback (flat palette color, primitive mesh,
synthesized beep). The game must be fully playable with an empty assets/gen/.
House GLB law (from PROCITY): metres, +Y up, sensible origin, ≤5k tris, WebP textures
≤1024, no Draco. ?localassets=0 simulates empty-manifest boot for fallback testing.
Flags (F owns this table; lanes request rows via NOTES)
| flag | owner | effect |
|---|---|---|
?stub=1 |
F | world stub instead of Lane A world |
?lvl=<id> |
C | boot straight into a level |
?seed=<n> |
F | override level seed |
?fly=1 |
A | noclip fly camera (no player) |
?dbg=1 |
F | perf HUD + window.DBG helpers |
?mute=1 |
E | no audio |
?shots=1 |
F | hide HUD/debug for clean screenshots |
?localassets=0 |
D | boot with empty manifest (fallback test) |
Perf & QA budgets
≤300 draw calls worst view · ≤500k tris in frustum · 60 fps M-series & mid laptops ·
level load <2 s · zero console errors · dispose-clean on level exit (renderer.info deltas).
window.DBG (F provides): {draws, tris, fps, shot(name)} — cite it when claiming numbers.
tools/qa.sh gates: node --check every js file · no Math.random outside core/rng.js ·
level JSONs parse + required keys · vendor present · (grows per round).