# LANE A — WORLD — NOTES ## Round 2 (2026-07-16) — the law-compliant look, and what shape a stomach is Context for the reader: the original round-2 session was cut off 15:08 before NOTES; F committed the orphaned work as `f0982e7` (its commit message is the NOTES for that half) and a resuming session finished the lane. This section covers both halves; trust the commit message for the mechanics of the first. ### What landed First half (`f0982e7`, per its message): `crestSpeed(s)` + CREST_FACTOR 1.6 with selfcheck asserts (ruling #1) · radius blend widened ±12 → ±25 + no-cliff selfcheck (C's #4 dependency — the taper C assumed now exists) · per-segment `wave.amp` override baked as per-vertex `aWaveA` (ruling #8) · colorspace law in the wall shader (ruling #2) · TBN normal maps + matcap + dual detail layers, D's `perturb()` with the trap documented in-shader · `sample(s, out)` v1.2 · slug map shrunk to the two real mismatches. Second half (this commit): 1. **Biome-tint re-eyeball under the colorspace law (task #4, second half). Verdict: all six ART_BIBLE tints stand — `biomes.js` is untouched.** The wash was never in the palette; it was the shader's tuned constants, all eyeballed round 1 on the *raw* framebuffer. Once `` encodes the output, every midtone gains roughly a stop, and the fresnel rim — which an inside-a-tube camera sees at grazing angles almost everywhere — flooded the frame (the esophagus shot was effectively rim colour wall-to-wall; the oral fallback was near white-out). Retuned in `wall_material.js`, where the round-1 numbers were authored: `uRimPow` 2.2 → 3.0 (confines the rim to fold edges, where the TBN normals bend), `uRimGain` 0.9 → 0.35, textured base `(0.35 + d·0.95)·0.9` → `0.12 + d·0.60`, procedural base `d·0.8` → `d²·0.65` (the sine bands only span ~0.4..1, and post-law that reads flat — squaring restores the dark troughs), crest sheen 0.10 → 0.04, matcap gain 0.22 → 0.14. 2. **The six-biome contact sheet** that the eyeball ran on, kept as infrastructure: `?lvl=biomes` in the harness loads `SIX_BIOMES` (`_fixture.js`) — one straight segment per registry biome. Whenever tints, textures, colorspace or TBN change again, one fly-through re-checks everything. Evidence: `docs/shots/laneA/round2_tint_.png` ×6, plus `round2_L2_hiatus_retuned.png` (C's calm-wave override, real L2) and `round2_arena_interior_retuned.png` (the BackSide/arena material path). 3. **Ruling #5 executed**: the harness moved to `web/dev/laneA_world.html` (it stays — it is the lane's instrument, not a boot workaround: `pose`/`poseAt` deterministic evidence frames, the leak `sweep`, the contact sheet). It gained `DBG.post(name)` which POSTs the canvas to D's `shot_sink` — the house evidence tool — instead of the round-1 private sink. `_fixture.js` stays too, and now says why: the spline selfcheck (a qa gate) asserts against FIXTURE's exact shape, headless. It is not waiting on C's levels after all. 4. **Stomach-arena shape read for C (task #7)** — recommendation + sketch, § → Lane C below. ### Measured / verified this half qa.sh GREEN end-to-end (ESM, determinism, levels 3/3, world selfcheck, manifest). Spline selfcheck green after every edit — the `_fixture.js` changes are additive (new export), and FIXTURE itself is untouched. Contact sheet: 6 draws / 61k tris per posed frame at 1280×720, consistent with round 1's budget headroom. fps still not claimed, same reason as round 1. ### Deliberately not done - **Triplanar arena shells** ("if time", task #7's tail): skipped, and not only for time — if C takes the swept-room proposal below, the stomach stops being an icosphere and triplanar stops being needed for the game's biggest room. The remaining icospheres (fundus dome, boss lairs, r ≲ 70) have modest pole pinch. Decide the shape first, then spend the shader work where it still matters. Revisit in round 3 with the L3 build. - **Slug-map deletion** (ruling #3): waiting on D's rename ping, as sequenced. The map is already down to the two real mismatches (`smallint`, `colon`). ## → Lane C (round 2): what shape is the acid sea? **Recommendation: don't grow the sphere — sweep the room along the spline you already authored.** Your L3 `segments[]` (fundus r 40, body r 45, antrum 25, + wobble, + curviness) already *describe* the stomach's bag: my proposal is that the "arena" becomes a **mode over an s-span**, not a separate geometry — the tube pipeline extrudes those segments at room radii exactly as it does corridors, and gameplay flags the span "open" (B's 6DOF, no disc clamp). The J-bend comes from `curviness`, the lobes from `wobble` (plus a low-frequency bulge term I add to the radius law if you want asymmetry — greater vs lesser curvature), and the two ends cap with hemispheres at the cardia and pylorus sphincters. What this buys, concretely: collision is the *existing* `project()`/`wallRho` path (varying radius is already supported and selfchecked — no union-of-spheres code); UVs stay `(theta, s)` so D's gastric pits tile like every other wall with **no pole pinch** (the round-1 starburst limitation dies rather than getting a triplanar bandage); the acid plane is a horizontal chord of the swept section (height driven by your events, `#c8ff3a`, round 3); and your mucus shallows are an `(s, theta)` band hugging the greater-curvature side — expressible in level data. The **fundus dome stays a welded icosphere** (it's a dead-end bulge *off* the centreline, which a sweep can't express) — the existing primitive, existing `arenaAt`. Chained overlapping spheres were the alternative and I sketched why not: every intersection of two displaced shells is a visible crease ring, and union `collide()`/`project()` is new math for a worse look. Cost on my side, stated honestly: radial resolution must scale with radius (64 segments at r 45 is an 8-unit facet; same ~3u solve the arena already does), cap geometry at the span ends, and modest streaming cost since room chunks are bigger. Schema ask, if you take it: `segments[].mode: "open"` (or an `arenas: [{from, to}]` span form — your call which reads better as data), spec'd by you in schema v3 early round 3, implemented by me the same round, before you author the acid sea proper. Sketch: `docs/shots/laneA/round2_stomach_arena_sketch.svg`. `arenaAt`/`modeAt` keep their contract for B either way (bounding `{center, radius}` per open span). ## → Lane D (round 2) - **Your measured detail curve changed — deliberately, and it's the law's fault, not your measurement's.** `(0.35 + d·0.95)·0.9` was right on the raw framebuffer; under `` its pedestal alone displays as ~60% grey. Now `0.12 + d·0.60` (rationale in-shader). Your mean-0.50/std-0.19 normalisation is exactly what made retuning a one-liner — the pack didn't move, the curve did. `normalScale` 0.6 unchanged and still right. - When your mouth + colon walls land, the eyeball harness is `web/dev/laneA_world.html?dbg=1&assets=1&lvl=biomes` — all six biomes, one URL. I'll re-run the contact sheet then; expect no curve change (your normalisation again). - Slug rename: still on, map deletion is a same-day promise once your NOTES ping lands. ## → Lane B (round 2) - **Nothing gameplay-moving changed this half.** The retune is fragment-shader only: `wallRho`, wave amplitudes, `crestSpeed`, collision — all untouched since `f0982e7`. Your speed-lock and envelope numbers stand. - The crest sheen is dimmer (0.10 → 0.04) but the crest still reads — it keeps its geometry, normal tilt, and rim pop. If playtest says the boost tell got too subtle, the knob is one constant in `wall_material.js` and I'll trade sheen against rim gain, not against the law. ## → Lane E (round 2) - `biomeAt(s).palette` hexes are unchanged (and now display *as written* end-to-end, which they never quite did before the law) — safe to key HUD accents off `palette.rim`. ## → Lane F (round 2) - Ruling #5 executed as above; `web/dev/` now holds D's texview and my world harness. - Housekeeping: `launch.json` gained `guts-a` on **8145** (8140/8141 were other sessions'). Shot sink on **8144** was reused as the instructions suggested and left running. - The two pre-retune shots `f0982e7` committed (`round2_L2_hiatus.png`, `round2_L2_tbn_wall.png`) show the washed-out pre-eyeball state — superseded by the `*_retuned` set, kept as the before/after record of what ruling #2 predicted. ### Round 3 (mine) Swept-room implementation if C specs it (radial-res solve + caps + open-span mode) · acid plane with C's height events · villi band when a small-intestine level exists · sphincter joint geometry at biome seams · slug-map deletion on D's ping · stomach-asymmetry bulge term if C wants authored greater/lesser curvature. --- # Round 1 NOTES (kept verbatim below) World v0 is in and **the real game boots it**: `http://localhost:8140/?dbg=1` runs Lane A's canal on C's `L2_esophagus` (3600 units, hash `cefc4f83`), zero console errors. No boot.js change was needed — C's `levels/index.js` landing is what unblocked `pickWorld()`. ## What landed `web/js/world/` — nothing outside it was touched. | file | what | |---|---| | `spline.js` | the math: centreline, arclength LUTs, parallel-transport frames, radius law, wave phase, `project()`, `hash()`, `stats()`. **Imports no three.js** → `node web/js/world/spline.js --selfcheck` runs headless (14 assertions). | | `biomes.js` | palette/param registry (ART_BIBLE values). C ratified this as the biome-id source of truth. | | `wall_material.js` | the synthetic-scanner wall: tint × detail + fresnel rim + fog, peristalsis in the vertex shader. | | `tube.js` | chunked extrusion + streaming window. | | `arena.js` | displaced-icosphere shells (the stretch goal), v0. | | `flycam.js` | `?fly=1` noclip. **Not wired** — snippet for F below. | | `index.js` | `createWorld()` — THE WORLD CONTRACT, drop-in for the stub. | | `dev.html` | my harness. Dev-only; delete when boot covers it. | | `_fixture.js` | dev fixture level. Delete when C's levels cover every case (arenas: L3 does now). | Contract is implemented as written — same surface, units and semantics as the stub, so B's stub-built work should port unchanged. Extras beyond it: `flowPulse(s,t)`, `hash()`, `stats()`, `dispose()`, `spline`, and an **optional 2nd arg on `project(pos, hint)`** (see → Lane B). ## Measured (say how you measured it — so: how) All from `dev.html` on C's real `L2_esophagus` with D's real pack, 1920×1080, via `renderer.info` (`DBG.pose()` / the sweep in `DBG.sweep()`): | thing | measured | budget | |---|---|---| | draws, worst frame over a full-canal sweep | **8** | ≤150 (charter), ≤300 (TECH) | | tris, worst frame | **74,420** | ≤500k | | live chunks | 5–8 (+1 per arena) | — | | geometries after full sweep + return to s=0 | **6 → 6, no leak** | dispose-clean | | geometries after `dispose()` | **0** (textures left alone — D owns them) | — | | level load, C's 3600-unit L2 | **<120 ms** | <2 s | | pinch ratio (min turn radius ÷ max tube radius) | L2 **3.32**, L3 **1.89**, fixture 2.37 | >1.5 | **fps is NOT measured and I'm not claiming it.** Screenshot tooling drives the tab hidden, which pauses rAF; `gl.finish()` didn't sync either (it reported 0.12 ms/frame = 8219 fps, which is obviously junk). Draws/tris are reliable and are 20× under budget, so I expect fps to be fine — but someone with a real window has to confirm. → Lane F, at round end. Determinism: same seed ⇒ same hash, **and node and the browser agree** (`50a5b0ec` for the fixture in both). Different seed ⇒ different hash. Both asserted in the selfcheck. ## Decisions (mine to make; flagging the ones that touch you) 1. **`curviness` = fraction of the tightest bend this pipe can survive**, not an amplitude. Octave amplitudes are solved from a curvature budget `kappa = curviness / (2.2 × baseRadius)`. Why: authoring amplitudes in units let a wide, curvy segment fold the tube through itself — the selfcheck's pinch guard caught exactly that on its first run (turn radius 9.9 vs tube radius 17.2). Now a wide pipe straightens itself and **C never has to think about curvature**. C's 0.15–0.95 range works as documented; nothing to change. 2. **Peristalsis: global `OMEGA = 3.08 rad/s`, wavenumber `k(s) = OMEGA / flow(s)`, phase `K(s) = ∫k ds`** (baked per-vertex as `aPhase`). Consequence worth knowing: **a wave crest travels at exactly the local flow speed**, so "ride the crest for boost" is physically the same thing as "ride the current". Plain `k·s − ω·t` tears the wave at every flow change; `K(s)` doesn't. For a flow-14 esophagus this reproduces the stub's wave exactly (k = 0.22). 3. **Esophagus `wave.amp` 0.9 → 1.4** (`biomes.js`). The stub's 0.9 is a barely-legible ripple; GDD §2 makes riding a crest the level's signature, and B can't surf what the player can't see. 1.4 reads as distinct rings and still leaves ~78% of a radius-10 tube flyable. Measured in the harness against 0.9 and 3.2. **This moves `wallRho`** → Lane B. 4. **Chunk seams align to biome joins**; an arena owns its whole s-span and the tube is skipped there (otherwise the corridor renders *inside* the room and `collide()` disagrees with what you can see). Seam is chunk-quantised ±20u — fine until sphincter geometry exists. 5. **Arena fog is sized to the room**, not the biome: `min(biome.fog, 1.09/radius)`. Biome fog is tuned for ~100u corridor sightlines; reused in C's 360u-wide acid sea it renders a black rectangle (that's what my first arena shot literally was). Small lairs keep the murk. 6. **One `uv` attribute = (θ/2π, s in world units)**, no uv2 — tiling is derived in-shader from a `uTile` uniform, so a second UV set would be dead weight. Charter said uv2; this is the simplification, flagged rather than done silently. 7. **Colorspace: matched to the stub** — the wall writes its colour with no `` conversion. That's a whole-game decision (it moves every colour at once) so I'm not making it unilaterally → Lane F. ## Known limitations (v0, stated plainly) - **Arena pole pinch.** Arena UVs are spherical, so the fold pattern converges into a starburst at the poles — visible top-right in `round1_L3_arena_acid_sea.png`. A real texture will pinch there too. Round 2: triplanar projection for arena shells. - **Arena shape.** A sphere is the wrong shape for a stomach and C says so in their own `L3` note. Round 2 should discuss a capsule/lobed form; the `arenaAt` contract (centre+radius) survives either way for B's clamp. - **No acid plane** (GDD's animated `#c8ff3a` level) — round 2, needs C's event to drive height. - **No villi** (small-intestine InstancedMesh band) — round 2, no level needs it yet. - **No matcap / normal map.** D ships `normal` maps already; using them needs a TBN in the wall shader. Round 2. Untested code paths are worse than absent ones. - **`project()` in arena mode** is step-clamped rather than properly solved; it's accurate in tube mode (recovers s to <0.25, θ to <0.02 rad — asserted). Arena collision doesn't use it (sphere test), so this only matters if B calls `project` on a point way off the centreline. - Segment param blending lags the march by one 0.5u step. Deterministic, sub-step, harmless. --- ## → Lane F 1. **Pass `assets` into `createWorld`.** The game currently boots my world but **D's textures are loaded and then unused** — I verified every material comes up `USE_DETAIL`-less under `boot.js`. In `pickWorld()`: ```js const mod = await import('./world/index.js'); const level = await loadLevel(flags.lvl); const assets = await (await import('./core/assets.js')).createAssets({ flags, renderer }); return mod.createWorld(level, { rng: createRng(flags.seed ?? level.seed), assets }); ``` (`createWorld` already ignores a missing/failed `assets` — the fallback law holds either way.) 2. **Add the world selfcheck to the qa gate** — it has caught two real defects already: ```bash # 4b. Lane A world math (headless; no three.js needed) node web/js/world/spline.js --selfcheck >/dev/null 2>&1 && green "world selfcheck" || red "world selfcheck failed" ``` 3. **Screenshot tooling traps**, which will bite you at round-end boot-matrix time. The pane drives tabs **hidden**: rAF never fires (so the loop isn't running when you shoot) and `innerWidth` is **0** (so the canvas is 0×0 and `toDataURL()` returns `"data:,"`). The pane screenshot still looks right because grabbing it fronts the tab. `DBG.shot()`'s anchor-click download also silently no-ops here. My harness works around all three in `DBG.size/pose/poseAt` — worth lifting into `boot.js`'s DBG. I got PNGs out by having the page `fetch(PUT)` them to a 30-line local sink (in my scratchpad, happy to promote it to `tools/` if you want it). 4. **Dev port**: 8140 was already held by another session's server, and `launch.json` is yours, so I left it alone and reused the running one. If lanes are meant to run concurrently on one box this needs a call. 5. Contract freeze: I'd like `project(pos, hint)` and `flowPulse/hash/stats/dispose` folded into TECH.md's world contract, and `assets.texture()` added to the asset contract (see → Lane D). ## → Lane B - **`wallRho` shrank**: it subtracts `wave.amp + wave.breathe + 0.6` and esophagus `wave.amp` is now 1.4 (was 0.9). Playable radius in a base-10 tube ≈ **7.85**. Re-check your graze distances. It's time-independent on purpose — a collision surface that breathed would have you fighting the wall. - **`project(pos, hint)`** — pass your previous `s` as `hint` and it's near-free and exact. With no hint it seeds from `-pos.z` and still converges (6 fixed-point steps), but the hint is strictly better in your per-frame path. Both are asserted in the selfcheck. - **Crest speed == `biomeAt(s).flow`**, exactly, by construction. `world.flowPulse(s, t)` is the *same* function the vertex shader runs (0 = trough, 1 = crest), so pulse ≈ 1 means the wall is at its narrowest AND the crest is moving at flow speed right there. Boost window. - `biomeAt(s).flow` is **blended across segment joins**, not stepped — safe to read per-frame. - Arena mode: `modeAt(s)` / `arenaAt(s)` → `{center, radius}` for your 6DOF clamp. `collide()` branches automatically (sphere test inside a room, radius test in a tube); you don't have to. - `sample()` allocates fresh Vector3s per call, same as the stub. If that shows up in your profile, ask and I'll add `sampleInto(s, out)`. ## → Lane C - Your schema v1 reads clean here — v1 is additive and my reader consumes L1/L2/L3 unchanged. Your ratified arena semantics (sphere spanning `[at-radius, at+radius]`) are exactly what's implemented, and `modeAt`/`arenaAt` agree with your notes on all three L3 arenas. - **Biome ids are the registry in `world/biomes.js`**: `oral · esophagus · stomach · small_intestine · large_intestine · appendix`. Unknown id ⇒ neutral fallback + one warning, never a crash. Thanks for writing the "biome = tissue family, not anatomy" rule into TECH — that's exactly right and it's why `name` carries the anatomy. - `curviness` behaves as you documented (0 straight … 1 writhing). Under the hood 1.0 now means "as curvy as this radius can safely be", so **you can't author a pinch** — L2 measures 3.32× clear, L3 1.89×. Author freely. - Free lever if you want it: `flow` is per-segment and it now sets the peristalsis wavelength (`k = 3.08/flow`). Low flow ⇒ tighter, slower rings. A "sluggish" segment reads sluggish. - Your L3 note that a sphere is the wrong shape for the stomach body: agreed, see limitations. ## → Lane D - **Naming mismatch, silent by construction.** Your keys are `wall_smallint_a` / (future) colon; my biome ids are `small_intestine` / `large_intestine`. `wall_${biomeId}_a` therefore misses, and because assets are optional it falls back to procedural **forever with no error**. I've added an explicit slug map in `index.js` so it works today. Proposal for round 2: standardise on the biome ids (`wall_small_intestine_a`) and delete the map — one name, one place. Your call, just let's not leave it implicit. - **Your `tile` semantics and mine converged independently** — `[repeats around theta, units of s per repeat]`, both read off the stub's uv. Ratified from my side; `[4,16]` looks right in engine (`round1_L2_textured.png`). - **`texture()` is the useful entry point, not `get()`.** TECH names `get(category,name)` as THE contract, but it returns raw JSON a shader can't eat. I prefer `assets.texture(name)` and keep `get` duck-typed as the documented floor. Suggest F promotes `texture()` into TECH. - FYI: tiling is applied **in-shader** from `tile`. A raw ShaderMaterial has no `uvTransform`, so the `repeat` you pre-apply on the texture is inert here. Not a problem — just don't count on it. - `wall_esophagus_a` looks genuinely good in engine. The `_b` variants and matcap aren't wired yet (round 2, with the TBN work). ## → Lane E - `world.flowPulse(s, t)` (0..1, crest = 1) is yours to pulse audio/HUD with; it's exact, cheap, and matches what the wall is visibly doing. - `world.biomeAt(s)` → `{id, palette, fog, flow, coatDrain}`. `palette.tint/rim/void` are the ART_BIBLE hexes — use `rim` if you want HUD accents that match the biome you're in. ## Round 2 (mine) Triplanar arena shells + acid plane · villi band (InstancedMesh, small intestine) · normal-map TBN + matcap · sphincter joint geometry at biome seams · `sampleInto` if B needs it · delete `_fixture.js`/`dev.html` once boot + C's levels cover them.