[lane A] Round 2 close-out: colorspace re-eyeball, stomach shape read, ruling #5

Finishes the lane A round-2 list that f0982e7 (the rescued first half) left open:

- Biome-tint re-eyeball under the colorspace law (#4 second half). Verdict: all
  six ART_BIBLE tints stand, biomes.js untouched — the wash was wall_material's
  round-1 constants, tuned on the raw framebuffer. Retuned: rim pow 2.2->3.0,
  rim gain 0.9->0.35, textured curve (0.35+d*.95)*.9 -> 0.12+d*0.60, procedural
  d*.8 -> d^2*.65, sheen .10->.04, matcap .22->.14. Rationale documented
  in-shader. Verified on a new six-biome contact sheet (?lvl=biomes, SIX_BIOMES
  fixture — kept as permanent kit), the real L2 hiatus, and an arena interior.
  Evidence: round2_tint_*.png x6 + two *_retuned frames. qa GREEN.

- Stomach-arena shape read for C (#7): recommend the spline-swept room (arena =
  "open" mode over an s-span; fundus dome stays a welded icosphere; chained
  spheres rejected — crease rings + union collision for a worse look). Full
  argument NOTES §-> Lane C + round2_stomach_arena_sketch.svg. Triplanar
  deliberately deferred: the swept room obsoletes it where it mattered.

- Ruling #5: harness -> web/dev/laneA_world.html (+DBG.post to the house shot
  sink); _fixture.js stays — the spline selfcheck asserts against it, now
  documented in-file. launch.json: guts-a on 8145.

- NOTES + progress written for both halves; ROUND2 mid-round box updated: lane
  A's round-2 list is closed, only round-3 items remain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jing 2026-07-16 17:14:30 +10:00
parent 29cca53675
commit ecf43940ec
16 changed files with 315 additions and 30 deletions

View File

@ -12,6 +12,12 @@
"runtimeExecutable": "python3",
"runtimeArgs": ["-m", "http.server", "8141", "--directory", "web"],
"port": 8141
},
{
"name": "guts-a",
"runtimeExecutable": "python3",
"runtimeArgs": ["-m", "http.server", "8145", "--directory", "web"],
"port": 8145
}
]
}

View File

@ -1,4 +1,138 @@
# LANE A — NOTES (round 1)
# 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
`<colorspace_fragment>` 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_<biome>.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
`<colorspace_fragment>` 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

View File

@ -17,6 +17,10 @@
> your proposed pickup values vs C's economy, arena 6DOF stretch, NOTES.
> - **A**: nothing of yours changed. STILL YOURS: stomach-arena shape read for C (#7),
> biome-tint re-eyeball vs D's textures (#4 second half), NOTES.
> **[A, later 2026-07-16: all three done — lane A's round-2 list is closed.** Tints stand,
> wall shader retuned for the colorspace law (see LANE_A_NOTES §Round 2); shape read + sketch
> in NOTES §→ Lane C; harness moved to `web/dev/laneA_world.html` per ruling #5. Only
> round-3 items remain for A.]
> - **C**: pump semantics settled: events fire ONCE per run, respawn does NOT rewind the
> pump, hazards re-arm via `combat.reset(fromS)`. `boot.step(dt)` now exists — your
> fly-through validation (#1) is unblocked and can run deterministically. STILL YOURS:

View File

@ -1,5 +1,32 @@
# Lane A — progress log
## 2026-07-16 · Round 2 — crest law, TBN walls, the colorspace re-eyeball, stomach shape read
Two halves: the original session (cut off pre-NOTES; work rescued by F as `f0982e7` — crest
speed law + selfchecks, ±25 radius taper, schema-v2 `wave.amp` as per-vertex `aWaveA`,
colorspace law in the wall, TBN + matcap + dual detail, `sample(s,out)`) and this resuming
session, which closed out the lane's round-2 list:
- **Tint re-eyeball under the colorspace law: all six ART_BIBLE tints stand, `biomes.js`
untouched.** The wash was the shader's round-1 constants, tuned on the raw framebuffer;
post-law the rim flooded every grazing angle. Retuned in `wall_material.js` (rim pow/gain,
both detail curves, sheen, matcap — values + rationale in-shader). Verified on a new
six-biome contact sheet (`?lvl=biomes`, kept as permanent kit), the real L2 hiatus, and an
arena interior; qa GREEN throughout. Evidence: `docs/shots/laneA/round2_tint_*.png` + two
`*_retuned` frames.
- **Ruling #5 executed**: harness → `web/dev/laneA_world.html`, + `DBG.post()` to the house
shot sink. `_fixture.js` stays — the spline selfcheck (qa gate) asserts against it; now
documented in-file.
- **Stomach-arena shape read → C (task #7)**: recommend the spline-swept room (arena = "open"
mode over an s-span, tube pipeline at room radii; fundus dome stays a welded icosphere;
chained spheres rejected — crease rings + union collision math). Full argument in
LANE_A_NOTES §→ Lane C; sketch `docs/shots/laneA/round2_stomach_arena_sketch.svg`. Triplanar
deliberately skipped: the swept room would obsolete it where it matters.
- Housekeeping: `guts-a` dev server on 8145 in launch.json; sink 8144 reused.
Still open for A (round 3): swept-room implementation if C specs it, acid plane, villi band,
sphincter geometry, slug-map deletion on D's rename ping.
## 2026-07-16 · Round 1 — the canal, v0
Built `web/js/world/`: spline + parallel-transport frames + arclength LUTs, chunked streaming

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -0,0 +1,72 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 720" font-family="ui-monospace, Menlo, monospace">
<!-- Lane A round 2, task #7: stomach-arena shape read for C (L3 acid sea, round 3).
Companion paragraph: LANE_A_NOTES.md §-> Lane C. Palette = ART_BIBLE stomach. -->
<rect width="1200" height="720" fill="#0b0d10"/>
<text x="600" y="40" fill="#e8f4f0" font-size="22" text-anchor="middle">L3 stomach body — what shape is the acid sea? (Lane A → Lane C, round 2)</text>
<!-- ============ PANEL A: spline-swept room (recommended) ============ -->
<text x="330" y="74" fill="#5affd2" font-size="17" text-anchor="middle">A. SPLINE-SWEPT ROOM — recommended</text>
<g transform="translate(60,90)">
<!-- centreline: cardia top -> body sweeps down-left -> curls right -> antrum climbs to pylorus -->
<path d="M 240 40 C 200 130, 180 220, 210 300 C 240 380, 330 420, 400 400 C 450 385, 480 350, 500 320"
fill="none" stroke="#5affd2" stroke-width="2" stroke-dasharray="7 6" opacity="0.9"/>
<!-- swept rings along the same centreline: the tube pipeline at room radii -->
<ellipse cx="240" cy="40" rx="34" ry="26" fill="none" stroke="#b0571e" stroke-width="2" opacity="0.75"/>
<ellipse cx="205" cy="130" rx="62" ry="47" fill="none" stroke="#b0571e" stroke-width="2" opacity="0.75"/>
<ellipse cx="186" cy="215" rx="80" ry="60" fill="none" stroke="#b0571e" stroke-width="2" opacity="0.75"/>
<ellipse cx="210" cy="300" rx="84" ry="62" fill="none" stroke="#b0571e" stroke-width="2" opacity="0.75"/>
<ellipse cx="268" cy="365" rx="76" ry="55" fill="none" stroke="#b0571e" stroke-width="2" opacity="0.75"/>
<ellipse cx="340" cy="405" rx="60" ry="44" fill="none" stroke="#b0571e" stroke-width="2" opacity="0.75"/>
<ellipse cx="410" cy="398" rx="44" ry="33" fill="none" stroke="#b0571e" stroke-width="2" opacity="0.75"/>
<ellipse cx="465" cy="360" rx="30" ry="23" fill="none" stroke="#b0571e" stroke-width="2" opacity="0.75"/>
<ellipse cx="500" cy="320" rx="17" ry="13" fill="none" stroke="#ffb13a" stroke-width="2.5"/>
<!-- fundus dome: the one welded icosphere that stays -->
<circle cx="150" cy="55" r="58" fill="none" stroke="#ffb13a" stroke-width="2" stroke-dasharray="4 5"/>
<text x="-46" y="14" fill="#ffb13a" font-size="13">fundus dome — stays a</text>
<text x="-46" y="30" fill="#ffb13a" font-size="13">welded icosphere</text>
<text x="-46" y="46" fill="#ffb13a" font-size="13">(existing primitive)</text>
<!-- acid plane -->
<line x1="96" y1="330" x2="410" y2="330" stroke="#c8ff3a" stroke-width="3"/>
<path d="M 100 338 l 300 0 M 112 348 l 276 0 M 128 358 l 244 0" stroke="#c8ff3a" stroke-width="1" opacity="0.35"/>
<text x="420" y="334" fill="#c8ff3a" font-size="13">acid plane: horizontal chord,</text>
<text x="420" y="350" fill="#c8ff3a" font-size="13">height ← C's level events</text>
<!-- mucus shallows band along greater curvature -->
<path d="M 112 250 C 108 300, 130 350, 172 388 C 210 420, 260 442, 310 450"
fill="none" stroke="#5affd2" stroke-width="12" opacity="0.28" stroke-linecap="round"/>
<text x="30" y="470" fill="#5affd2" font-size="13">mucus shallows hug the greater curvature —</text>
<text x="30" y="486" fill="#5affd2" font-size="13">authored as a (s, theta) band, drain pauses there</text>
<!-- labels -->
<text x="292" y="40" fill="#e8f4f0" font-size="13">cardia (r≈14, teal — the reveal)</text>
<text x="292" y="215" fill="#e8f4f0" font-size="13">body: C's segments AS AUTHORED</text>
<text x="292" y="231" fill="#e8f4f0" font-size="13">(r 4045 base + wobble = lobes,</text>
<text x="292" y="247" fill="#e8f4f0" font-size="13">curviness = the J-bend)</text>
<text x="520" y="300" fill="#e8f4f0" font-size="13">pylorus gate</text>
</g>
<!-- ============ PANEL B: chained spheres (not recommended) ============ -->
<text x="900" y="74" fill="#ff6a6a" font-size="17" text-anchor="middle">B. CHAINED SPHERES — not recommended</text>
<g transform="translate(660,90)">
<path d="M 240 40 C 200 130, 180 220, 210 300 C 240 380, 330 420, 400 400 C 450 385, 480 350, 500 320"
fill="none" stroke="#5affd2" stroke-width="1.5" stroke-dasharray="7 6" opacity="0.5"/>
<circle cx="215" cy="120" r="78" fill="none" stroke="#b0571e" stroke-width="2.5"/>
<circle cx="200" cy="255" r="95" fill="none" stroke="#b0571e" stroke-width="2.5"/>
<circle cx="290" cy="370" r="85" fill="none" stroke="#b0571e" stroke-width="2.5"/>
<circle cx="430" cy="380" r="55" fill="none" stroke="#b0571e" stroke-width="2.5"/>
<!-- crease seams at the lens intersections -->
<path d="M 148 205 C 190 190, 240 190, 272 208" fill="none" stroke="#ff6a6a" stroke-width="3"/>
<path d="M 205 340 C 235 320, 262 312, 292 318" fill="none" stroke="#ff6a6a" stroke-width="3"/>
<path d="M 352 340 C 372 352, 384 368, 390 388" fill="none" stroke="#ff6a6a" stroke-width="3"/>
<text x="40" y="420" fill="#ff6a6a" font-size="13">✕ visible crease ring where displaced shells intersect (×3)</text>
<text x="40" y="438" fill="#ff6a6a" font-size="13">✕ union collide() + union project() = new code, new selfchecks</text>
<text x="40" y="456" fill="#ff6a6a" font-size="13">✕ spherical UVs: pole pinch ×4, tile scale jumps per sphere</text>
<text x="40" y="474" fill="#e8f4f0" font-size="13">✓ zero schema change (arenas[] already spheres)</text>
</g>
<!-- ============ caption ============ -->
<g transform="translate(60,628)">
<text x="0" y="0" fill="#e8f4f0" font-size="14">A = the tube pipeline itself at room radii over an s-span, gameplay flagged 6DOF ("open"): collision is the existing</text>
<text x="0" y="20" fill="#e8f4f0" font-size="14">project()/wallRho path (varying radius already supported), UVs stay (theta, s) so D's gastric pits tile with no pole</text>
<text x="0" y="40" fill="#e8f4f0" font-size="14">pinch, and C's L3 segments already author it (radius 4045 + wobble). Cost: radius-scaled radial resolution + cap</text>
<text x="0" y="60" fill="#e8f4f0" font-size="14">geometry + one schema flag. Full argument + schema sketch: LANE_A_NOTES.md §→ Lane C.</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1001 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@ -4,20 +4,23 @@
<meta charset="utf-8">
<title>GUTS — Lane A world harness</title>
<!--
Lane A's own harness. NOT the game: js/boot.js is the game, F owns it.
Lane A's world harness (web/dev/ per ruling #5 — dev-only, per-file lane ownership, never
shipped). NOT the game: js/boot.js is the game, F owns it. Boot shows the real world now;
this survives because it is the world lane's *instrument*: deterministic pose/poseAt evidence
frames, the streaming-leak sweep, and the six-biome tint contact sheet — none of which belong
in boot.
This exists because boot.js can't reach js/world/index.js yet — pickWorld() imports Lane C's
js/levels/index.js in the same try block, and that module doesn't exist, so every non-stub
boot falls back to the stub regardless of my work landing. Patch offered in LANE_A_NOTES.md
§-> Lane F. Until it's applied, this is how the canal gets eyeballed. Delete it once boot can
show the real world.
http://localhost:8140/js/world/dev.html?dbg=1 rail cam down the fixture canal
http://localhost:8140/dev/laneA_world.html?dbg=1 rail cam down the fixture canal
...?dbg=1&fly=1 noclip (WASD/QE, drag to look)
...?dbg=1&s=430 park the rail cam at s=430
...?dbg=1&fakeassets=1 exercise the Lane D texture path
...?lvl=L2_esophagus C's level, when it lands
...?dbg=1&fakeassets=1 synthetic texture stand-in
...?dbg=1&assets=1 Lane D's real pack
...?lvl=L2_esophagus C's level (?lvl=biomes = the
six-biome eyeball canal)
...?shots=1 hide the overlay
Evidence flow: DBG.pose(s, t) then DBG.post('name') — POSTs the canvas to D's shot_sink
(pipeline/shot_sink.py, house tool per ruling #5). DBG.shot()'s download no-ops in the pane.
-->
<style>
html, body { margin: 0; height: 100%; overflow: hidden; background: #02100d; }
@ -27,18 +30,18 @@
pointer-events: none; white-space: pre; }
</style>
<script type="importmap">
{ "imports": { "three": "../../vendor/three.module.js",
"three/addons/": "../../vendor/addons/" } }
{ "imports": { "three": "../vendor/three.module.js",
"three/addons/": "../vendor/addons/" } }
</script>
</head>
<body>
<div id="dbg"></div>
<script type="module">
import * as THREE from 'three';
import { createRng } from '../core/rng.js';
import { createWorld } from './index.js';
import { createFlyCam } from './flycam.js';
import { FIXTURE } from './_fixture.js';
import { createRng } from '../js/core/rng.js';
import { createWorld } from '../js/world/index.js';
import { createFlyCam } from '../js/world/flycam.js';
import { FIXTURE, SIX_BIOMES } from '../js/world/_fixture.js';
const p = new URLSearchParams(location.search);
const on = (k) => p.has(k) && p.get(k) !== '0';
@ -46,9 +49,11 @@ const num = (k, d) => (p.has(k) ? parseFloat(p.get(k)) : d);
// --- level: C's if it's landed, fixture otherwise ---------------------------------------
let level = FIXTURE;
if (p.has('lvl')) {
if (p.get('lvl') === 'biomes') {
level = SIX_BIOMES; // the tint contact sheet — one segment per registry biome
} else if (p.has('lvl')) {
try {
const mod = await import('../levels/index.js');
const mod = await import('../js/levels/index.js');
level = await mod.getLevel(p.get('lvl'));
} catch (e) {
console.warn('[devharness] no Lane C registry yet, using fixture —', e.message);
@ -93,7 +98,7 @@ let assets = null;
if (on('fakeassets')) assets = fakeAssets();
else if (on('assets')) {
try {
const m = await import('../core/assets.js');
const m = await import('../js/core/assets.js');
assets = await m.createAssets({ flags: { localassets: p.get('localassets') !== '0' } });
} catch (e) {
console.warn('[devharness] Lane D assets.js unavailable, procedural walls —', e.message);
@ -137,6 +142,17 @@ window.DBG = {
a.href = renderer.domElement.toDataURL('image/png');
a.click();
},
/** POST the current canvas to D's shot_sink (the house evidence tool — ruling #5).
* Run pose()/poseAt() first; the sink names and lands the file, no download dance. */
post(name, port = 8144) {
return new Promise((res, rej) => renderer.domElement.toBlob((b) => {
if (!b || b.size < 8) return rej(new Error('empty canvas did you pose() first?'));
fetch(`http://localhost:${port}/shot/${name}.png`, { method: 'POST', body: b })
.then((r) => (r.ok ? res(`${name}.png ${(b.size / 1024) | 0} KB`) : rej(new Error(`sink ${r.status}`))))
.catch(rej);
}, 'image/png'));
},
/**
* Fix the drawing buffer to an explicit size for evidence frames. Two traps, both learned
* the hard way, both of which will bite anyone shooting boot.js (see LANE_A_NOTES §-> Lane F):

View File

@ -1,7 +1,7 @@
// world/_fixture.js (Lane A) — DEV FIXTURE, not shipping content.
// Lane C owns real levels (js/levels/*.json). This exists so spline.js's headless selfcheck
// and dev.html can exercise the world before/without C's data: multi-segment, curviness
// sweep, a biome change, and an arena. Delete the day C's L2 + registry land.
// world/_fixture.js (Lane A) — DEV FIXTURES, not shipping content.
// Lane C owns real levels (js/levels/*.json). FIXTURE survives C's levels landing because the
// spline selfcheck (a qa gate) asserts against its exact shape — headless, no three.js, no
// levels registry. Do not edit FIXTURE's segments without re-reading spline.js --selfcheck.
export const FIXTURE = {
id: 'A_FIXTURE',
@ -15,3 +15,17 @@ export const FIXTURE = {
arenas: [{ at: 620, radius: 55, biome: 'stomach' }],
events: [],
};
// The tint contact sheet (?lvl=biomes in the harness): one straight-ish segment per registry
// biome, in registry order, generous radius so the wall — not the curve — is what you judge.
// Exists for the eyeball law: every biome tint gets re-checked against D's current pack in one
// fly-through whenever either side changes (tints, textures, colorspace, TBN…).
export const SIX_BIOMES = {
id: 'A_SIX_BIOMES',
name: 'Lane A biome contact sheet',
seed: 20260716,
segments: ['oral', 'esophagus', 'stomach', 'small_intestine', 'large_intestine', 'appendix']
.map((biome) => ({ biome, length: 200, radius: { base: 10, wobble: 0.2 }, curviness: 0.25, flow: 8 })),
arenas: [],
events: [],
};

View File

@ -32,7 +32,7 @@ export function createWallMaterial({
repeat = null, // [repeats around theta, repeats per unit of s] (= D's .repeat)
repeatB = null,
normalScale = 0.6, // D: "0.6 looks right; 0 = off, 1 = full relief"
matcapGain = 0.22,
matcapGain = 0.14,
radiusHint = 10, // used only to pick a square-ish default tiling
side = THREE.FrontSide,
}) {
@ -63,8 +63,14 @@ export function createWallMaterial({
uFog: { value: fog },
uBreatheA: { value: breatheAmp },
uOmega: { value: omega },
uRimPow: { value: 2.2 },
uRimGain: { value: 0.9 },
// Retuned for the colorspace law (round 2). The round-1 values (pow 2.2, gain 0.9)
// were eyeballed on the raw framebuffer; with <colorspace_fragment> encoding the
// output, every midtone gained ~a stop and the rim — which the whole tube sees at
// grazing angles — flooded the frame mint. Tighter pow confines it to fold edges
// (where the TBN normals vary), lower gain returns the wall to "dark tissue, rim
// light" (ART_BIBLE). Re-eyeballed on the six-biome contact sheet, ?lvl=biomes.
uRimPow: { value: 3.0 },
uRimGain: { value: 0.35 },
uDetail: { value: detail },
uDetailB: { value: detailB },
uNormal: { value: normalMap },
@ -149,13 +155,19 @@ export function createWallMaterial({
float db = texture2D(uDetailB, vUv * uRepeatB).r;
detail = mix(detail, detail * (0.55 + 0.9 * db), 0.6);
#endif
vec3 base = uTint * (0.35 + detail * 0.95) * 0.9; // D's measured curve
// D's round-1 curve was (0.35 + d*0.95)*0.9, measured on the raw framebuffer.
// Post-colorspace that pedestal alone displays as ~60% grey; re-measured on the
// contact sheet so a mid-grey texel lands near the tint's own luminance.
vec3 base = uTint * (0.12 + detail * 0.60);
#else
// Assets-optional law: no texture is the *shipping* look until D lands, not an error
// state. Ridged folds around theta + striation along s = a passable SEM stand-in.
float folds = 0.55 + 0.45 * sin(vUv.x * 6.2831853 * 9.0 + sin(vUv.y * 0.9) * 2.0);
float detail = folds * (0.85 + 0.15 * sin(vUv.y * 2.2));
vec3 base = uTint * detail * 0.8;
// detail² not detail: the sine bands only span ~0.4..1.0, and post-colorspace that
// narrow range reads as a flat bright wall (the oral biome was near white-out).
// Squaring restores the dark troughs the SEM look needs; still fallback art.
vec3 base = uTint * detail * detail * 0.65;
#endif
vec3 N = normalize(vN);
@ -169,7 +181,7 @@ export function createWallMaterial({
float fres = pow(1.0 - abs(dot(N, V)), uRimPow);
// crest sheen: the wave is a gameplay tell (ride it for boost), so it gets a little
// help beyond what its own geometry earns from the rim term
vec3 col = base + uRim * fres * uRimGain + uRim * vPulse * 0.10;
vec3 col = base + uRim * fres * uRimGain + uRim * vPulse * 0.04;
#ifdef USE_MATCAP
// wet specular. Keyed off the perturbed normal so the sheen follows the folds, and