guts/docs/TECH.md
jing 34f84ab162 [lane F] Round 0 scaffold: docs, contracts, lane charters, bootable stub world
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>
2026-07-16 01:28:35 +10:00

135 lines
6.6 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.

# 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:
```js
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.
```js
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)
```jsonc
{
"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`)
```jsonc
{ "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).