clone

an endless marble-race generator in the browser; the url is the seed, so every track can be shared

M6 complete: checkered goal gate + abstract dev-map biome

Dami committed 5930a67
Goal gate: the banner mesh is a single colormap primitive, so the finish
flag is a thin checkered slab (generated CanvasTexture) wrapping the cloth
band between charcoal-tinted posts.

Abstract biome (weight 0.6): rare dev-map islands - saturated orange
blocking with hash-picked grey courses, no caps or vegetation; the
renderer swaps its per-instance color scheme for the biome. TF2-orange
homage, zero new assets.
commit
5930a67e1b1a438734a32edb877dbe8e6eb17fab
parent
b25973a9e910339929a2a73fcb2ee39b05d23d39
author
Dami <dami.etoile@gmail.com>
4 files changed, +100 -7
Mprogress.md20+++++++--
Msrc/client/islandRender.ts14+++++++
Msrc/client/track.ts62+++++++++++++++++++++++++++++-
Msrc/gen/endless/islands.ts11+++++
M progress.md +15 -5
@@ -509,11 +509,21 @@ individually (base matrices in userData.animInstances). Scene nodes 3.4k → ~30
509509 whole frame ~35 draw calls. FOUND + FIXED en route: Kenney GLTFs omit510510 metallicFactor → glTF default metallic=1.0 → GLTFLoader builds full-metal511511 materials that render BLACK off-sun (no envmap); assets.ts now normalizes512-metalness=0 on load — this also visibly fixed loop-mode nature decor. REMAINING513-in M6: checkered goal-gate texture, prototype-texture "abstract" biome row514-evaluation, per-biome fog/light tints.] Placements animate IN as the stream515-builds (transform-based rise/scale settle, staggered per placement by distance516-from segment entry; island blocks bottom-up, decor after) and animate OUT before512+metalness=0 on load — this also visibly fixed loop-mode nature decor. GOALS 3–5513+DONE, M6 COMPLETE: (3) generative ATMOSPHERE family (src/client/atmosphere.ts) —514+mood table (midnight/ember/violet/abyss/ snowdrift/goldhour) × param jitter ×515+weather draw (none/snow/dust/embers), one combo per 5-segment ZONE via516+`atmosphere/zone/${z}` labels; zone crossings crossfade sky/fog/sun517+color+intensity+direction/hemisphere/ starfield over 4s; weather = camera-local518+wrapping particle volumes with fade swaps. Supersedes "per-biome fog tints". (4)519+checkered goal gate — the banner is one colormap primitive (no cloth submesh),520+so a generated- CanvasTexture checker slab wraps the cloth band (posts stay521+charcoal). (5) "abstract" biome row (gen, weight 0.6) — dev-map islands: orange522+masses with grey courses, no caps/vegetation, renderer swaps the color scheme.523+KNOWN COSMETIC: follow cam can clip through island masses it trails past524+(pre-existing; polish).] Placements animate IN as the stream builds525+(transform-based rise/scale settle, staggered per placement by distance from526+segment entry; island blocks bottom-up, decor after) and animate OUT before517527 disposal (segment lingers one window while exiting). Visual-only wall-clock518528 easing — generation untouched, determinism unaffected. Textures: biome-tinted519529 cliff materials (shared material clone PER BIOME, not per block), per-theme
M src/client/islandRender.ts +14 -0
M src/client/track.ts +60 -2
@@ -43,8 +43,8 @@ export function instantiatePlacements(
4343 obj.scale.setScalar(s);4444 if (p.mirrored) obj.scale.x = -s;4545 if (p.goal) {46- // Lap-line gate: dark charcoal restyle so it reads as the finish47- // marker against any palette.46+ // Lap-line gate: charcoal posts + a checkered cloth slab — a finish47+ // flag against any palette.4848 obj.traverse((child: THREE.Object3D) => {4949 const mesh = child as THREE.Mesh;5050 if (!mesh.isMesh) return;
@@ -59,6 +59,11 @@ export function instantiatePlacements(
5959 });6060 mesh.material = Array.isArray(mesh.material) ? tinted : tinted[0];6161 });62+ const checker = makeGoalChecker();63+ if (checker) {64+ obj.add(checker.mesh);65+ goalMaterials.push(checker.material);66+ }6267 }6368 group.add(obj);6469
@@ -94,6 +99,59 @@ export interface BuiltTrack {
9499 /** Ground overshoot, in grid units — wide enough for the fog to eat its edge. */95100 const GROUND_MARGIN = 90;96101 102+// ---------------------------------------------------------------------------103+// Goal gate checker — the lap line reads as a finish flag, not a tinted104+// banner. The banner mesh is one primitive on the shared colormap (no cloth105+// submesh to retexture), so a thin checkered slab wraps the cloth band:106+// banner-high posts sit at ±0.425 with the cloth spanning ~y 0.75–0.95.107+// ---------------------------------------------------------------------------108+109+let checkerTexture: THREE.CanvasTexture | null = null;110+const checkerGeometry = new THREE.BoxGeometry(0.86, 0.2, 0.095);111+112+function getCheckerTexture(): THREE.CanvasTexture | null {113+ if (checkerTexture) return checkerTexture;114+ const canvas = document.createElement("canvas");115+ canvas.width = 256;116+ canvas.height = 64;117+ const g = canvas.getContext("2d");118+ if (!g) return null;119+ const COLS = 8;120+ const ROWS = 2;121+ for (let cx = 0; cx < COLS; cx++) {122+ for (let cy = 0; cy < ROWS; cy++) {123+ g.fillStyle = (cx + cy) % 2 === 0 ? "#f2f2ee" : "#1c1c22";124+ g.fillRect(125+ (cx * canvas.width) / COLS,126+ (cy * canvas.height) / ROWS,127+ canvas.width / COLS,128+ canvas.height / ROWS,129+ );130+ }131+ }132+ checkerTexture = new THREE.CanvasTexture(canvas);133+ checkerTexture.colorSpace = THREE.SRGBColorSpace;134+ return checkerTexture;135+}136+137+/** Checkered cloth slab, positioned in the banner's local frame. The caller138+ * owns the material's disposal (geometry + texture are shared). */139+function makeGoalChecker():140+ | { mesh: THREE.Mesh; material: THREE.Material }141+ | null {142+ const tex = getCheckerTexture();143+ if (!tex) return null;144+ const material = new THREE.MeshStandardMaterial({145+ map: tex,146+ roughness: 0.9,147+ metalness: 0,148+ });149+ const mesh = new THREE.Mesh(checkerGeometry, material);150+ mesh.position.set(0, 0.85, 0);151+ mesh.castShadow = true;152+ return { mesh, material };153+}154+97155 export function buildTrack(98156 track: Track,99157 assets: Assets,
M src/gen/endless/islands.ts +11 -0