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>
328 lines
22 KiB
Markdown
328 lines
22 KiB
Markdown
# 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
|
||
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
|
||
`<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
|
||
`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.
|