glytch/fktry/src/render/era.ts
type-two 5e8e49ef0e [render] v4: ghost mode, locked-build ghost, cooler auras, lab visuals
Round 4 orders 1-5:

- ghost keys demolition off setGhost's `mode` arg; the '__remove' sentinel is
  ignored and REMOVE_DEF is gone from this lane.
- locked-build ghost: padlock-amber (not occupied-red — "not yet" and "not
  here" are different sentences) + a padlock tinted by the gating era. Needed
  a new ERA_TELL palette: ERA_SKIN.tint is a multiplier tuned to leave DATA's
  chassis alone, so it can't carry a signal (stream is #ffffff).
- aura.ts: cooling coverage as a dashed RECTANGLE. coolRadius is chebyshev, so
  the cooled region is the footprint grown by r per side; a circle would look
  better and lie about the corners.
- lab placeholder: bench + the "bottled artifact triptych" (§2); consumes
  MachineDef.accent when present, derivation stays the fallback.

Also:
- buffer fill reads bandwidth.capacity (v4) instead of re-summing bufferCap —
  a second copy of the sim's arithmetic is what the scram latch warned about.
- lock rule matched to LANE-UI's reading: absent research means nothing has
  completed, so gated machines padlock. Mine said the opposite; theirs is the
  literal contract text, and the build bar and ghost disagreeing on the same
  screen is worse than either answer.
- showroom fitted to BOTH axes. Round 3's depth-only fit collapsed the item
  grid to one 206-tile row once the machine hall grew and ate the depth.

Verified against DATA's real v4 data and UI's migrated flow: 6x6 aura on
asic-cooler, reel/broadcast era tells, lab accent #ffc24a from data, 29
machines + 60 items in the showroom, 500 entities + 2,000 items at 3.6ms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:12:04 +10:00

74 lines
3.0 KiB
TypeScript

/**
* 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<Era, EraSkin> = {
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<string, Era> {
const out = new Map<string, Era>();
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<Era, number> = {
reel: 0xd8a24a, // brass
broadcast: 0x5fd08a, // phosphor
disc: 0x6fd7ff, // iridescent
stream: 0xdfe6f2, // flat plastic white
fortress: 0xf2f7ff, // blinding
};