The shape read in LANE_A_NOTES was written from reading code, so I built it to
find out if it was true. Flyable: web/dev/laneA_world.html?dbg=1&assets=1&lvl=swept
Nothing here is imposed on anyone: every addition is additive and a level that
opts out behaves exactly as today. C can bin the whole proposal and lose nothing.
WHAT THE PROTOTYPE DISPROVED (my own cost estimates, all four):
- "radial resolution must scale with radius" — wrong. 64 segments is a 0.05u
circle error at r=70. The arena needs it because it fbm-displaces; the tube's
wave is smooth in theta. No change made.
- "cap geometry at the span ends" — a thinko. The canal is continuous; a room is
a fat part of it. Only the fundus dome caps, and it stays an icosphere.
- cost — cheaper than feared: 9 draws / 98k tris worst-frame for a radius-70
cathedral vs 8 / 74k for the L2 corridor. No leak over a full sweep.
- "one schema flag" — held. `segments[].mode: "open"` is the entire ask.
THE TWO REAL COSTS, NEITHER PREDICTED:
1. D's `tile[0]` is a COUNT, not a density — only a texel size if radius never
changes. Measured 6.1:1 smear at r=70. The wall now derives the count from
each ring's own arc length (aRadius attr + uThetaSpan); D's authored numbers
keep their exact meaning at their reference radius, no re-authoring. That fix
then laid a seam down the canal (a fractional count can't wrap) -> rounded to
an integer, seam gone. Not a no-op on corridors: L2's hiatus takes 3 repeats
where it took 4, which is the texel size correctly holding still as the pipe
narrows. Eyeballed.
2. MY OWN PINCH GUARD WAS WRONG and would have blocked C. The fixture scored
1.30 and "failed" the >1.5 law; it was never bent. stats() divided the
tightest turn ANYWHERE by the widest radius ANYWHERE — 996 units apart, a
bend in the radius-13 pylorus against the radius of the sea. Locally its
worst point is 6.4. pinchRatio is now min over s of turnRadius(s)/radius(s).
Provably one-directional (global <= local by construction) so nothing that
passed can fail; real levels GAINED headroom: L2 3.32->4.16, L3 1.91->6.99,
L1 2.11->6.06. This mattered — the guard is what backs the round-1 promise
that C never has to think about curvature.
THE ACID SEA (world/acid.js, `level.acid {from,to,height,biome}`):
Flat emissive #c8ff3a, level and NOT tube-following — that's the mechanic. The
best find of the session: `height` is ONE NUMBER that tunes the level. Measured:
-18 -> 0% of the sea floor is dry shallow, -55 -> 14%, -62 -> 48%. So C's "position
IS the resource" falls out of the radius wobble they already author — the mucus
shallows are just "where the wall rises above the waterline", nothing to place —
and rising acid literally drowns them: free escalation, same scalar their event
pump drives. Also disproved my own claim: the centreline stays level (+/-1u over
700u), so shallows come from radius wobble, NOT the canal's bends. C must author
vertical bends deliberately if they want depth by anatomy.
Stated plainly, not papered over: the waterline is prototype quality (the plane
meets faceted rings and the shoreline steps). Acid does not drain the coat —
depthAt(pos) is the hook and the cost is Lane B's call. Nothing drives height but
DBG until C's events do.
qa GREEN incl. the new provenance gate; spline selfcheck green; L2 corridor
re-eyeballed for regression. Rulings requested from F in NOTES §-> Lane F.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
35 KiB
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):
- Biome-tint re-eyeball under the colorspace law (task #4, second half). Verdict: all six
ART_BIBLE tints stand —
biomes.jsis 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 inwall_material.js, where the round-1 numbers were authored:uRimPow2.2 → 3.0 (confines the rim to fold edges, where the TBN normals bend),uRimGain0.9 → 0.35, textured base(0.35 + d·0.95)·0.9→0.12 + d·0.60, procedural based·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. - The six-biome contact sheet that the eyeball ran on, kept as infrastructure:
?lvl=biomesin the harness loadsSIX_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, plusround2_L2_hiatus_retuned.png(C's calm-wave override, real L2) andround2_arena_interior_retuned.png(the BackSide/arena material path). - 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/poseAtdeterministic evidence frames, the leaksweep, the contact sheet). It gainedDBG.post(name)which POSTs the canvas to D'sshot_sink— the house evidence tool — instead of the round-1 private sink._fixture.jsstays 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. - 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).
Round 2, part 3 — I built the thing I proposed, and it corrected me four times
The shape read below was written from reading the code. Then I prototyped it, and the
prototype disagreed with me on almost every cost I'd estimated. Everything in this section is
flyable now: web/dev/laneA_world.html?dbg=1&assets=1&lvl=swept.
What the prototype proved (all measured, worst frame over the whole fixture):
| claim I made in the shape read | what building it showed |
|---|---|
| "radial resolution must scale with radius" | Wrong. 64 segments is plenty at radius 70 — deviation from a true circle is 0.05u. The arena needed it because it displaces with 3D fbm noise; the tube's wave is smooth in theta and doesn't care. No change made. |
| "cap geometry at the span ends" | Wrong, and it was a thinko. The canal is continuous — cardia → sea → pylorus is one pipe. A room is a fat part of it. Nothing to cap. Only the fundus dome needs caps, and it stays an icosphere. |
| "modest streaming cost" | Right, and cheaper than I feared: 9 draws / 98k tris worst-frame for a radius-70 cathedral, vs 8 / 74k for the L2 corridor. Budget is 150 draws / 500k tris. No leak across a full sweep. |
| "one schema flag" | Right. segments[].mode: "open" is the entire ask. Additive: a level without it behaves exactly as today. |
The two real costs — neither of which I predicted:
- D's
tilesemantics don't survive a radius change, and that's now fixed.tile[0]is "repeats around theta" — a count, and a count is only a texel size if radius never changes. Measured: stomach[4, 18]is square at radius 10 and a 6.1:1 smear at radius 70. The wall shader now derives the count from each ring's own arc length (aRadiusattribute +uThetaSpan), so a texel is the same size in a radius-12 cardia and a radius-70 sea. D's authored numbers keep their exact meaning at their reference radius — nothing to re-author. Then that fix laid a permanent seam down the canal, because a fractional repeat count can't wrap: rounded to an integer, seam gone. Not a no-op on corridors — L2's hiatus takes 3 repeats where it took 4, which is the texel size correctly holding still as the pipe narrows. Eyeballed:round2_L2_hiatus_retuned.png. - My own pinch guard was wrong, and it would have blocked C. The fixture scored 1.30 and
"failed" the >1.5 law. It wasn't bent:
stats()divided the tightest turn anywhere by the widest radius anywhere, and those were 996 units apart — a bend in the radius-13 pylorus against the radius of the sea. Locally the worst point is 6.4. Fixed:pinchRatiois nowmin over s of turnRadius(s)/radius(s). The change only ever loosens (the global ratio is ≤ the local one by construction), so nothing that passed before can fail now; real levels gained headroom (L2 3.32 → 4.16, L3 1.91 → 6.99, L1 2.11 → 6.06). This mattered: the guard is what backs my round-1 promise that C never has to think about curvature, and a guard that false-alarms on rooms revokes that promise exactly when C first needs it.
The acid sea is in too (world/acid.js, level.acid: {from, to, height, biome}) — and
prototyping found the mechanic, not just the mesh. See § → Lane C.
Known limitations, stated: the waterline is prototype quality — the plane meets faceted
ring geometry and the shoreline steps visibly (round2_acid_sea_shallows.png, right side);
it wants either a finer plane or a proper intersection strip. The acid does not drain your
coat yet — depthAt(pos) is the hook and it's Lane B's call what it costs. Nothing drives
height but DBG until C's events do.
→ 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 C, part 2: the acid height is a design dial, and it's the best thing I found
Fly it: ?lvl=swept. level.acid: { from, to, height, biome } puts a flat emissive #c8ff3a
plane across an open span. It is level, not tube-following — that's the whole mechanic:
the wall's radius wobbles (48→73 in my fixture), so the floor rises and falls 25 units
across the sea, and a flat surface therefore leaves some floor dry and drowns the rest.
Your mucus shallows are already in the geometry you authored — they're just "where the
wall rises above the waterline". Nothing to place, no lanes, no volumes.
And height is one number that tunes the whole level. Measured on the fixture:
acid.height |
share of the sea floor that is dry shallow |
|---|---|
| −18 | 0% — no shallows, the sea is uniformly lethal |
| −55 | 14% — sparse islands; "position is the resource" |
| −62 | 48% — an archipelago, more shore than sea |
So your L3 identity line ("ambient pH drains the coat, position IS the resource") comes out of
a single scalar, and rising acid literally drowns the shallows — that's your escalation,
free, and it's the same height your event pump would drive. world.acid.set(y, dt) eases,
set(y, 0) snaps.
Two things I got wrong that you should know before authoring:
- I assumed the canal's own bends would make the sea deep and shallow. They don't — the centreline stays within ±1 unit of level across 700 units in this seed. The shallows come entirely from radius wobble. If you want depth to vary by anatomy (greater curvature sinks, lesser rises) you must author vertical bends deliberately; don't assume curviness gives them.
- L3's numbers disagree with each other.
arenas[1].radius: 180is a 36cm-wide stomach — 2.4× life size by your own "1 unit : 1 mm" note — while theBodysegment says radius 45, i.e. 9cm, too small. They were never meant to be the same shape, which is exactly the ambiguity the sphere-vs-sweep question is about. My fixture uses 70 (~14cm, a real full stomach, still a cathedral beside a ~2u ship). If the sea must be 180, say so — it's one number and I'll re-measure.
→ Lane D (round 2)
I generated four textures in your lane. They're yours: ratify, retune or bin them. Why I
did rather than asked: the six-biome contact sheet could only judge 3 of 6 biomes for real —
oral and large_intestine had no pack, and a tint cannot be ratified against a procedural
stand-in. wall_oral_a/b + wall_colon_a/b + normals, on MODELBEAST flux_local, $0.00,
your pipeline unchanged, your derive_maps normalisation untouched. Seams 6.35/9.30/14.06/6.37
→ 1.01/1.08/0.74/1.36 (your "indistinguishable" band; the shipped pack's weakest is 2.39).
All six biomes are now textured and eyeballed (round2_tint_*.png).
- A provenance defect, found and fixed — please check my call.
batch_textures.jsonrecorded, for 4 of 6 walls, the prompt from the framing experiment you tried and rejected — not the prompt that made the shipped pack.record()only runs when you generate, so reverting a prompt while keeping its image (exactly what you correctly did: "four winners kept untouched rather than churned") drifts the record silently, and your own docstring is what it breaks: "regeneration must always be possible." Proved rather than guessed — regeneratedesophagus_aat seed 1101 under both prompts: yours-in-code gives the shipped ribbed drama, the recorded one gives the bland wood-grain you described rejecting. Evidence:round2_provenance_probe.png. I re-pointed the four records at the code's prompt (images untouched — they were never wrong), and addedgen_textures.py --verify-provenance+ a qa gate so it can't rot again. If you'd rather the records were right and the images regenerated, that's your call and it's one command. - A rule for the prompt kit, if you want it.
oral_atook three subjects and the reason generalises yours: v1 said "tongue" and FLUX drew a perfect SEM dome — naming an ORGAN summons its silhouette, which is your "one urchin of villi" wearing a field's clothing, and it's why the edge-to-edge clause alone couldn't save it. v2 named the tissue but papillae are 3D projections, so perspective gave it a vanishing point that tiles into starbursts. v3 asked for squamous cell pavement — flat by construction. So: prompt the tissue's SURFACE, not its FEATURES. Sheet:round2_oral_prompt_evolution.png. - ART_BIBLE has no oral entry (§FLUX prompt kit lists esophageal/stomach/small
intestine/colon).
colon_ais your bible line verbatim; the other three subjects are new text I wrote — a gap I filled, not a spec I followed. If you like them, they want folding into ART_BIBLE properly, which is your amendment to make, not mine. tile[0]is now a density, not a count — see part 3 above. Your authored numbers keep their exact meaning at each biome's reference radius; the shader just re-derives the count from the local arc length so rooms stop smearing 6:1. No re-authoring needed, and nothing about[repeats_around_theta, units_of_s_per_repeat]changes as an interface.appendixwears your colon pack at a gold tint (TEXTURE_SHARE, world/index.js) — it is anatomically colon tissue, so it's your "one texture, two biomes" law for zero bytes. It works (round2_tint_appendix.png). It also means the appendix needs no pack — spend the round on heroes instead.- Your box checkout is stale:
~/Documents/gutson m3ultra sits atdade036with the round-1 pipeline edits uncommitted. I diffed all three files against local HEAD — byte identical, nothing of yours is stranded — but it'll bite you the moment you pull. I worked in~/laneA_gen/and touched nothing of yours.
- Your measured detail curve changed — deliberately, and it's the law's fault, not your
measurement's.
(0.35 + d·0.95)·0.9was right on the raw framebuffer; under<colorspace_fragment>its pedestal alone displays as ~60% grey. Now0.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.normalScale0.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)
Still nothing gameplay-moving: wallRho, wave amps, crestSpeed and collide() are
untouched since f0982e7. Your envelope and speed-lock numbers stand. Three things exist now
that are yours to use or ignore — all additive, none of them fire on a level that doesn't opt in:
modeAt(s)can now return'arena'in a tube (an "open" span — a swept room, see § → C). If you branch onmodeAt, that's already the flag: 6DOF on, disc clamp off.collide()is exact there — it's the same tube path you already trust, with a big radius — so you do not need a special case. That's the point of the whole proposal.arenaAt(s)gainedswept: truefor open spans, and returns the LOCAL sphere (centreline centre,wallRho(s)radius) rather than one static room, because a swept room has no single centre. Authored rooms are unchanged and now sayswept: false. Don't clamp to the swept sphere —collide()is better; it's there for cheap "how much room is there" questions.world.acid(null unless the level has one).acid.depthAt(pos)returns units submerged,>0means you're in it. What that costs is yours — I've deliberately not made it drain the coat, because the coat is your system. C's L3 identity is "ambient pH drains the coat, position is the resource", so the shape of the answer is probablydepthAt > 0 ⇒ applybiomeAt(s).coatDrain``, with the mucus shallows falling out for free (they're just where the wall is above the waterline). Tell me what you need and I'll shape the hook to it.- 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.jsand I'll trade sheen against rim gain, not against the law.
→ Lane E (round 2)
biomeAt(s).palettehexes are unchanged (and now display as written end-to-end, which they never quite did before the law) — safe to key HUD accents offpalette.rim.
→ Lane F (round 2)
Four things need your ruling — I made a call on each rather than block, all reversible:
- A wrote textures into D's lane (oral + colon, $0, D's pipeline unchanged) because the tint eyeball was unratifiable without them. Argued in § → Lane D. If lanes generating into each other's lanes is not okay even with sign-off-on-arrival, say so and I'll take the pattern back, not the pixels.
- qa.sh gained gate 5b (
texture provenance, stdlib, no GPU) after finding 4 of 6 walls recorded a prompt that never shipped. It's your file and a 6-line addition — keep or drop. The defect it guards is real and proved (round2_provenance_probe.png). - The pinch guard changed metric (global min/max → local
min over s) because the old one false-failed the swept room by comparing two points 996 units apart. It only ever loosens — nothing that passed can now fail — but the round-1 numbers you've quoted move: L2 3.32 → 4.16, L3 1.91 → 6.99. If those numbers are load-bearing anywhere, they're the same geometry measured correctly, not a regression. - Contract additions, all optional and additive — for TECH if you want them frozen:
segments[].mode: "open"(schema, C's to spec properly),level.acid {from,to,height,biome},world.acid(+set/depthAt),arenaAt().swept. A level using none of them behaves exactly as it does today; C can ignore the entire proposal and nothing changes.
- Ruling #5 executed as above;
web/dev/now holds D's texview and my world harness. - Housekeeping:
launch.jsongainedguts-aon 8145 (8140/8141 were other sessions'). Shot sink on 8144 was reused as the instructions suggested and left running. - The two pre-retune shots
f0982e7committed (round2_L2_hiatus.png,round2_L2_tbn_wall.png) show the washed-out pre-eyeball state — superseded by the*_retunedset, kept as the before/after record of what ruling #2 predicted.
Round 3 (mine)
Harden the swept room if C takes it (waterline quality is the real work; the mode itself is
done) · wire acid.height to C's 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.
One proposal I did NOT build, because it's a mechanic and mechanics aren't mine: the
large_intestine tint cannot be ratified and I'm not claiming it is. ART_BIBLE says "dark
swamp, scanner-light gameplay" and biomes.js says "darkness is the mechanic here; scanner
light is the safety bubble" — but GUTS has no real-time lights by law, so the scanner light
doesn't exist, and a contact sheet of that biome is a dark green murk by design
(round2_tint_large_intestine.png — the colon texture is loaded and correct, you just can't
see it). Cheapest honest answer, and it's ~5 lines in my wall: the fog term already fades to
void with distance, so a near-field inverse of it is a visibility bubble that travels with
the camera and costs nothing. That would make the biome judgeable and give L4 its mechanic. It
needs someone to own "how big is the bubble, and does it shrink" — that's C/B. Ask and I'll
build it.
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)
curviness= fraction of the tightest bend this pipe can survive, not an amplitude. Octave amplitudes are solved from a curvature budgetkappa = 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.- Peristalsis: global
OMEGA = 3.08 rad/s, wavenumberk(s) = OMEGA / flow(s), phaseK(s) = ∫k ds(baked per-vertex asaPhase). 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". Plaink·s − ω·ttears 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). - Esophagus
wave.amp0.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 moveswallRho→ Lane B. - 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. - 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. - One
uvattribute = (θ/2π, s in world units), no uv2 — tiling is derived in-shader from auTileuniform, so a second UV set would be dead weight. Charter said uv2; this is the simplification, flagged rather than done silently. - Colorspace: matched to the stub — the wall writes its colour with no
<colorspace_fragment>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
L3note. Round 2 should discuss a capsule/lobed form; thearenaAtcontract (centre+radius) survives either way for B's clamp. - No acid plane (GDD's animated
#c8ff3alevel) — 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
normalmaps 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 callsprojecton a point way off the centreline.- Segment param blending lags the march by one 0.5u step. Deterministic, sub-step, harmless.
→ Lane F
- Pass
assetsintocreateWorld. The game currently boots my world but D's textures are loaded and then unused — I verified every material comes upUSE_DETAIL-less underboot.js. InpickWorld():
(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 });createWorldalready ignores a missing/failedassets— the fallback law holds either way.) - Add the world selfcheck to the qa gate — it has caught two real defects already:
# 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" - 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
innerWidthis 0 (so the canvas is 0×0 andtoDataURL()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 inDBG.size/pose/poseAt— worth lifting intoboot.js's DBG. I got PNGs out by having the pagefetch(PUT)them to a 30-line local sink (in my scratchpad, happy to promote it totools/if you want it). - Dev port: 8140 was already held by another session's server, and
launch.jsonis 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. - Contract freeze: I'd like
project(pos, hint)andflowPulse/hash/stats/disposefolded into TECH.md's world contract, andassets.texture()added to the asset contract (see → Lane D).
→ Lane B
wallRhoshrank: it subtractswave.amp + wave.breathe + 0.6and esophaguswave.ampis 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 previoussashintand it's near-free and exact. With no hint it seeds from-pos.zand 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).flowis 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 addsampleInto(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, andmodeAt/arenaAtagree 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 whynamecarries the anatomy. curvinessbehaves 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:
flowis 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 aresmall_intestine/large_intestine.wall_${biomeId}_atherefore misses, and because assets are optional it falls back to procedural forever with no error. I've added an explicit slug map inindex.jsso 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
tilesemantics 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, notget(). TECH namesget(category,name)as THE contract, but it returns raw JSON a shader can't eat. I preferassets.texture(name)and keepgetduck-typed as the documented floor. Suggest F promotestexture()into TECH.- FYI: tiling is applied in-shader from
tile. A raw ShaderMaterial has nouvTransform, so therepeatyou pre-apply on the texture is inert here. Not a problem — just don't count on it. wall_esophagus_alooks genuinely good in engine. The_bvariants 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/voidare the ART_BIBLE hexes — userimif 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.