glytch/fktry/src/screen/eras.ts
type-two d8111feb12 [screen] the substrate ages: era-skinned clean programming
The base feed now wears the highest ratified era -- sepia film with sprocket
holes, phosphor broadcast with interlace, rainbow-on-black disc gloss, the
classic SMPTE stream, blinding fortress white. Corruption stays era-agnostic:
same shader, same params, only what they are painted onto changes.

Boot era is reel, so the world starts in the deepest stratum and climbs to the
surface. The old SMPTE look is now mid-game.

The chyron generalises from an idle-only placard to a line of text, which is
what lets the radio caption the sky in a later commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 13:51:03 +10:00

105 lines
3.9 KiB
TypeScript

/**
* Era skins for the clean programming (round 5 Part A).
*
* THE SCREEN's SUBSTRATE ages with the highest ratified era; the corruption on top stays
* era-agnostic. Media history is geology (codex §1), so the test pattern itself is a fossil
* of whatever format the player has climbed to. Codex §8 era skins drive the palette.
*
* Pure and data-only so it is trivially unit-testable and carries no DOM — the base feed
* consumes an EraSkin, it does not decide one.
*/
export type Era = 'reel' | 'broadcast' | 'disc' | 'stream' | 'fortress';
/** Deepest → shallowest, i.e. oldest → newest. "Highest ratified" = last of these unlocked. */
export const ERA_ORDER: readonly Era[] = ['reel', 'broadcast', 'disc', 'stream', 'fortress'];
export interface EraSkin {
/** the seven SMPTE bars, era-recoloured */
bars: string[];
/** the castellation strip under the bars */
casts: string[];
/** backdrop behind the pattern (matters where bars don't cover) */
bg: string;
/** overlay text + timecode colour */
ink: string;
/** rec-dot / accent colour */
accent: string;
/**
* A full-canvas post-treatment applied after the pattern is drawn. Kept as a tagged union
* (not a callback) so it stays pure data — the base feed interprets it.
*/
treatment:
| { kind: 'none' }
| { kind: 'sepia'; sprockets: true } // reel: warm film, sprocket holes
| { kind: 'phosphor' } // broadcast: green cast + interlace
| { kind: 'gloss' } // disc: rainbow-on-black sheen
| { kind: 'bleach' }; // fortress: blinding matte white
}
const STREAM_BARS = ['#c0c0c0', '#c0c000', '#00c0c0', '#00c000', '#c000c0', '#c00000', '#0000c0'];
const STREAM_CASTS = ['#0000c0', '#131313', '#c000c0', '#131313', '#00c0c0', '#131313', '#c0c0c0'];
const SKINS: Record<Era, EraSkin> = {
reel: {
bars: ['#b9a67c', '#c7b06a', '#9c9a72', '#8f9a63', '#a98c6e', '#a06a4e', '#6e5c46'],
casts: ['#5a4a34', '#241d14', '#6e5240', '#241d14', '#5c5a44', '#241d14', '#b9a67c'],
bg: '#181008',
ink: '#f0e0c0',
accent: '#d8a24a',
treatment: { kind: 'sepia', sprockets: true },
},
broadcast: {
// NTSC bars, but everything sunk into a phosphor-green cathode glow.
bars: ['#8fce8f', '#b6c664', '#4fb7b7', '#4fb44f', '#8a5aa8', '#9a4a4a', '#3a4fb0'],
casts: ['#12300f', '#0a140a', '#2a3a20', '#0a140a', '#1a3a30', '#0a140a', '#8fce8f'],
bg: '#03120a',
ink: '#c8ffce',
accent: '#8fffa0',
treatment: { kind: 'phosphor' },
},
disc: {
// Rigid, glossy, rainbow-on-black. DVD menu energy.
bars: ['#d0d0d0', '#d8d800', '#00d8d8', '#00d800', '#d800d8', '#e00000', '#2020ff'],
casts: ['#2020ff', '#000000', '#d800d8', '#000000', '#00d8d8', '#000000', '#d0d0d0'],
bg: '#000000',
ink: '#ffffff',
accent: '#ff3fd4',
treatment: { kind: 'gloss' },
},
stream: {
// The surface world / the original prototype look — the game's "now".
bars: STREAM_BARS,
casts: STREAM_CASTS,
bg: '#101010',
ink: '#f4f4f4',
accent: '#ff4444',
treatment: { kind: 'none' },
},
fortress: {
// The DCP citadel: mathematically perfect, blinding matte white, near-invisible bars.
bars: ['#f7f7f7', '#f2f2e8', '#e8f2f2', '#e8f2e8', '#f2e8f2', '#f2e8e8', '#e8e8f4'],
casts: ['#e6e6ee', '#f4f4f4', '#eee6ee', '#f4f4f4', '#e6eeee', '#f4f4f4', '#f7f7f7'],
bg: '#fbfbfb',
ink: '#20242a',
accent: '#2f7d3f',
treatment: { kind: 'bleach' },
},
};
/** The highest ratified era from a set of unlocked tech, given a tech→era lookup. */
export function highestEra(unlocked: readonly string[], eraOf: (tech: string) => Era | undefined): Era {
let best = 0; // default: reel (the world starts in the deepest stratum you can reach)
for (const t of unlocked) {
const e = eraOf(t);
if (e === undefined) continue;
const idx = ERA_ORDER.indexOf(e);
if (idx > best) best = idx;
}
return ERA_ORDER[best];
}
export function eraSkin(era: Era): EraSkin {
return SKINS[era];
}