/** * LANE-RENDER — era skins (style guide §8). * * "Reel Beds = sepia/brass/celluloid; Broadcast = phosphor green + wood-grain TV; * Disc = rainbow-on-black gloss; Stream = flat white plastic + LED; Fortress = * blinding matte white." * * Plumbing only this round. A machine's era is DERIVED from which tech unlocks it, so * when LANE-DATA adds reel/broadcast techs those machines re-skin with zero renderer * work. Machines no tech unlocks are starting equipment and take the default. * * The skin touches the CHASSIS only — the emissive accent stays the colour of what the * machine makes, because that's the resource, not the decade. * * `stream` is deliberately the identity skin (same roughness/metalness as the base body * material, no tint), so today's look is byte-for-byte unchanged until data says otherwise. */ import * as THREE from 'three'; import type { GameData, TechDef } from '../contracts'; export type Era = TechDef['era']; export const DEFAULT_ERA: Era = 'stream'; export interface EraSkin { /** Multiplied into the chassis colour — 0xffffff leaves DATA's value alone. */ tint: number; roughness: number; metalness: number; } export const ERA_SKIN: Record = { reel: { tint: 0xffd2a0, roughness: 0.65, metalness: 0.45 }, // sepia/brass/celluloid broadcast: { tint: 0xc4e8c4, roughness: 0.8, metalness: 0.1 }, // phosphor + woodgrain disc: { tint: 0x9aa0b8, roughness: 0.2, metalness: 0.65 }, // rainbow-on-black gloss stream: { tint: 0xffffff, roughness: 0.85, metalness: 0.15 }, // identity — the default fortress: { tint: 0xffffff, roughness: 0.95, metalness: 0.0 }, // blinding matte white }; /** * machine id -> era, via the tech that unlocks it. Anything unmapped is starting * equipment (no tech gates it) and takes DEFAULT_ERA. */ export function eraIndex(data: GameData): Map { const out = new Map(); const machines = new Set(data.machines.map((m) => m.id)); for (const t of data.tech) { for (const u of t.unlocks) if (machines.has(u)) out.set(u, t.era); } return out; } /** Skin a chassis material in place. Accents must never be passed here. */ export function applyEraSkin(mat: THREE.MeshStandardMaterial, era: Era): void { const skin = ERA_SKIN[era] ?? ERA_SKIN[DEFAULT_ERA]; mat.color.multiply(new THREE.Color(skin.tint)); mat.roughness = skin.roughness; mat.metalness = skin.metalness; } /** * Era read as a SIGNAL rather than a surface — used by the locked-build ghost to say * *which* era is holding a machine back. `ERA_SKIN.tint` can't do this job: it's a * multiplier tuned to leave DATA's chassis alone (stream is literally white), so three * of the five eras would tell you nothing. These are chosen to be told apart at a * glance, in codex order. */ export const ERA_TELL: Record = { reel: 0xd8a24a, // brass broadcast: 0x5fd08a, // phosphor disc: 0x6fd7ff, // iridescent stream: 0xdfe6f2, // flat plastic white fortress: 0xf2f7ff, // blinding };