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>
| M | progress.md | 20 | +++++++-- |
| M | src/ | 14 | +++++++ |
| M | src/ | 62 | +++++++++++++++++++++++++++++- |
| M | src/ | 11 | +++++ |
@@ -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@@ -105,6 +105,20 @@ function blockColor(
105105 y: number,106106 z: number,107107 ): THREE.Color {108+ if (biome === "abstract") {109+ // Dev-map blocking: saturated orange masses with grey courses — the110+ // TF2-orange homage. Parity + hash decide which blocks go grey.111+ const grey = hash01(x, y, z, 97) < 0.28;112+ if (grey) {113+ return tmpC.setHSL(0.08, 0.04, 0.72 + (hash01(x, y, z, 101) - 0.5) * 0.1)114+ .clone();115+ }116+ return tmpC.setHSL(117+ 0.066 + (hash01(x, y, z, 103) - 0.5) * 0.015,118+ 0.85,119+ 0.55 + (hash01(x, y, z, 107) - 0.5) * 0.08,120+ ).clone();121+ }108122 const band = Math.max(0, Math.floor(depthBelowTop));109123 let h = 0.075 + band * 0.012 + (hash01(x, y, z, 11) - 0.5) * 0.05;110124 let s = 0.06 + band * 0.02;@@ -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,@@ -85,6 +85,17 @@ export const BIOMES: readonly BiomeSpec[] = [
8585 setting: "rocky",8686 decorDensity: 0.15,8787 },88+ // Rare novelty: dev-map island — bare geometry in TF2-orange/grey blocking89+ // colors (the renderer swaps its color scheme; no caps, no vegetation).90+ {91+ name: "abstract",92+ weight: 0.6,93+ material: "rock",94+ capId: null,95+ capChance: 0,96+ setting: null,97+ decorDensity: 0,98+ },8899 ];89100 90101 export interface Island {