Implements THE WORLD CONTRACT (TECH.md) in web/js/world/; drop-in for the stub. boot.js now runs this on C's L2_esophagus (3600u, hash cefc4f83), zero console errors. - spline.js: centreline + arclength LUTs + parallel-transport frames + radius law + peristalsis phase + project() + hash(). No three.js import, so qa can run its 14-assertion selfcheck headless. - curviness is solved from a curvature budget, not authored as an amplitude: the pinch guard caught the tube folding through itself on a wide+curvy join (turn radius 9.9 vs tube radius 17.2). C can no longer author a pinch. - peristalsis: global OMEGA, k(s) = OMEGA/flow(s), phase K(s) = integral k ds. A crest therefore travels at exactly the local flow speed, so "surf the crest" == "ride the current". Esophagus wave amp 0.9 -> 1.4 (measured): moves wallRho for Lane B. - tube.js: chunked streaming, seams aligned to biome joins, arenas own their s-span. - arena.js: displaced icosphere shells v0 (three's polyhedron detail is 20*(detail+1)^2, not 20*4^detail — 720 tris was far too coarse); fog sized to the room, since biome fog tuned for 100u corridors renders C's 360u acid sea as a black rectangle. - index.js: prefers assets.texture() over get() (get returns raw JSON a shader can't eat), plus an explicit slug map — D's wall_smallint_a vs biome id small_intestine would miss silently forever under the assets-optional law. Measured (C's real L2 + D's real pack, 1920x1080, renderer.info): 8 draws worst frame (budget 150), 74k tris (500k), no geometry leak across a full sweep, <120ms load, pinch ratio 3.32. fps not claimed — the screenshot pane runs tabs hidden, pausing rAF. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
86 lines
3.2 KiB
JavaScript
86 lines
3.2 KiB
JavaScript
// world/biomes.js (Lane A) — the biome registry. Palette/fog values are transcribed from
|
|
// ART_BIBLE.md §Biome palettes; gameplay defaults (flow, coatDrain) are sane seeds that
|
|
// Lane C overrides per-segment in level JSON (`segments[].flow`). C owns the tuning, this
|
|
// owns the look.
|
|
//
|
|
// Canonical ids — level JSON `segments[].biome` must be one of these:
|
|
// oral · esophagus · stomach · small_intestine · large_intestine · appendix
|
|
// Unknown id => NEUTRAL fallback + one console warning, never a crash.
|
|
//
|
|
// `wave.amp` is displacement in units, inward, at the crest — and it is a GAMEPLAY number, not
|
|
// just a look: world.wallRho() subtracts (amp + breathe) as its conservative margin, so raising
|
|
// it narrows Lane B's flight envelope. Esophagus 1.4 was measured in the harness, not guessed:
|
|
// the stub's 0.9 renders as a barely-legible ripple, and GDD §2 makes riding a crest for boost
|
|
// the level's signature mechanic — B cannot surf a crest the player can't see. 1.4 reads as
|
|
// distinct rings and still leaves ~78% of a radius-10 tube flyable. The other biomes are scaled
|
|
// in proportion and are UNVERIFIED until each one is actually built and eyeballed.
|
|
|
|
export const BIOMES = {
|
|
oral: {
|
|
id: 'oral',
|
|
palette: { tint: 0xa8c4d8, rim: 0xdff2ff, void: 0x06090e },
|
|
fog: 0.016, // clinical, bright, tutorial-readable => you can see across the cavity
|
|
flow: 5,
|
|
coatDrain: 0.15,
|
|
wave: { amp: 0.5, breathe: 0.10 }, // saliva tides, not real peristalsis
|
|
},
|
|
esophagus: {
|
|
id: 'esophagus',
|
|
palette: { tint: 0x1e6e64, rim: 0x5affd2, void: 0x02100d },
|
|
fog: 0.028,
|
|
flow: 14,
|
|
coatDrain: 0.5,
|
|
wave: { amp: 1.4, breathe: 0.15 }, // the signature: ribbed rings rushing past (measured)
|
|
},
|
|
stomach: {
|
|
id: 'stomach',
|
|
palette: { tint: 0xb0571e, rim: 0xffb13a, void: 0x120801 },
|
|
fog: 0.020,
|
|
flow: 4,
|
|
coatDrain: 2.2, // the acid sea: brutal in open acid (GDD §Player systems)
|
|
wave: { amp: 0.7, breathe: 0.25 }, // churn, not transit
|
|
},
|
|
small_intestine: {
|
|
id: 'small_intestine',
|
|
palette: { tint: 0x7a2a6e, rim: 0xff6ad5, void: 0x0d0312 },
|
|
fog: 0.050, // dense: villi forest occludes sightlines
|
|
flow: 9,
|
|
coatDrain: 0.8,
|
|
wave: { amp: 1.5, breathe: 0.18 },
|
|
},
|
|
large_intestine: {
|
|
id: 'large_intestine',
|
|
palette: { tint: 0x3a4a26, rim: 0x9dff4a, void: 0x050702 },
|
|
fog: 0.075, // darkness is the mechanic here; scanner light is the safety bubble
|
|
flow: 5,
|
|
coatDrain: 0.6,
|
|
wave: { amp: 1.2, breathe: 0.2 },
|
|
},
|
|
appendix: {
|
|
id: 'appendix',
|
|
palette: { tint: 0x8a7020, rim: 0xffe066, void: 0x0a0800 },
|
|
fog: 0.030,
|
|
flow: 2,
|
|
coatDrain: 0.3,
|
|
wave: { amp: 0.5, breathe: 0.12 },
|
|
},
|
|
};
|
|
|
|
const NEUTRAL = {
|
|
id: 'neutral',
|
|
palette: { tint: 0x557066, rim: 0x9fd8c8, void: 0x050a08 },
|
|
fog: 0.03, flow: 10, coatDrain: 0.4, wave: { amp: 0.9, breathe: 0.15 },
|
|
};
|
|
|
|
const warned = new Set();
|
|
|
|
export function biome(id) {
|
|
const b = BIOMES[id];
|
|
if (b) return b;
|
|
if (!warned.has(id)) {
|
|
warned.add(id);
|
|
console.warn(`[world] unknown biome "${id}" — falling back to neutral. Valid: ${Object.keys(BIOMES).join(', ')}`);
|
|
}
|
|
return NEUTRAL;
|
|
}
|