// Default settings for the screensaver subsystem.
// Additive merge into legacy DEFAULT_SETTINGS via main.ts.
import type { StoryMemory } from '../terminal/arc';
export interface ScreensaverSettings {
defaultScene: 'terrain' | 'city' | 'rift' | 'tunnel' | 'void' | 'wreckage' | 'matrix' | 'metro';
speed: 'slow' | 'norm' | 'fast';
cityAltitude: 'low' | 'mid' | 'high';
tunnelAutoBoost: boolean;
bankStrength: number; // flight banking intensity (0 = level, 2 = aggressive)
seedLock: number | null;
renderScale: number; // 0.25..1 — multiplies the devicePixelRatio cap (perf knob)
kioskMode: boolean; // UI-less operation inside a native saver host (no control bar / cursor / fullscreen request)
colorMode: 'kuro-auto' | 'kuro-preset' | 'custom';
colorPreset: string;
colorCustom: string;
/**
* v1.1 — Live-Test 2026-05-11. Determines how the screensaver picks its
* primary palette tone.
*
* 'matchAspect' (default)
* The screensaver reads `html[data-aspect]` on open and maps the
* aspect to its KSP signal preset (shugo → phosphor, gunshi →
* spectre, kantoku → crimson, sensei → ember). Idle mode feels
* like a natural extension of the active Vault persona.
*
* 'pickFixed'
* Use the aspect stored in `aspectPaletteFixed` regardless of which
* aspect is active in the vault.
*
* 'inherit' (v1.0 behaviour)
* Defer to `colorMode` / `colorPreset` / `colorCustom`. Kept so
* users who explicitly liked the old behaviour can opt back in.
*/
aspectPaletteMode: 'matchAspect' | 'pickFixed' | 'inherit';
aspectPaletteFixed: 'shugo' | 'gunshi' | 'kantoku' | 'sensei';
fx: {
bloom: { on: boolean; strength: number };
trails: { on: boolean; damp: number };
matrix: { on: boolean; density: number };
scan: { on: boolean; opacity: number };
glitch: { on: boolean; freq: number }; // deprecated — superseded by crtSim
vignette: { on: boolean; strength: number };
scanlineDrift: { on: boolean; speed: number };
radarPing: { on: boolean; interval: number };
burnDecay: { on: boolean; strength: number };
chromaticAberration: { on: boolean; offset: number };
noiseBursts: { on: boolean; freq: number };
powerOn: { on: boolean };
// Analog-tube CRT pass (ported from the native Metal renderer):
curvature: { on: boolean; amount: number }; // barrel screen-warp + bezel
aperture: { on: boolean; strength: number }; // RGB phosphor grille mask
ntsc: { on: boolean; amount: number }; // composite dot-crawl shimmer
halation: { on: boolean; amount: number }; // warm phosphor glow bleed
};
/**
* CRT simulation: aggregates all signal-degradation artifacts that a real CRT
* monitor produces — h-sync tear, v-roll attempts, brightness flicker, black
* frame drops, rolling brightness bands (VHS-style), chromatic aberration
* spikes, wave distortion. Single intensity controls both frequency and
* amplitude. Replaces the old `fx.glitch` toggle.
*/
crtSim: {
on: boolean;
intensity: number; // 0..1
};
fxInheritFromTheme: boolean;
hudPreset: 'minimal' | 'tactical' | 'full' | 'off' | 'custom';
hud: {
left: boolean; right: boolean; radar: boolean; terminal: boolean;
crosshair: boolean; vaultKanji: boolean; realtime: boolean;
controlBar: boolean;
};
bootEnabled: boolean;
bootSpeed: 'fast' | 'normal' | 'cinematic';
loreIntensity: 'none' | 'subtle' | 'full';
subliminalFlashes: boolean;
flashFrequencyMean: number;
easterEggAlerts: boolean;
sound: {
master: boolean; volume: number;
hum: boolean; scanlineWhine: boolean; sceneSwitch: boolean;
bootSounds: boolean; boost: boolean; radarPing: boolean;
synthSoundscape: 'off' | 'carpenter' | 'bach';
};
autoCycle: { on: boolean; intervalMin: number };
idleLaunch: { on: boolean; minutes: 5 | 10 | 15 | 30 };
embed: { on: boolean; opacity: number; hudHidden: boolean };
parallax: boolean;
liveHotkeysEnabled: boolean;
escapeOnlyExit: boolean;
controlBarAutoHideSec: number; // 0 = never auto-hide; >0 = seconds before hide
/**
* Narrative terminal: when ON, the bottom terminal renders an unfolding shift
* by a CORP operator (typing, hesitations, intrusions, panic, reset) instead
* of a passive scroll of atmospheric lines. Recommended ON.
*/
narrativeTerminal: boolean;
narrativeReactiveWorld: boolean; // the 3D world reacts to the shift phase (fog/CRT/camera/storm)
/**
* Terminal layout mode:
* bottom-strip — original mode: terminal hugs the bottom of the screen.
* center-window — Apple-Lisa-inspired CORP OS window centered front + center,
* semi-transparent so the 3D scene shows through.
*/
terminalLayout: 'bottom-strip' | 'center-window';
dayNightCycle: { on: boolean; periodMin: number };
/**
* Fog / view-distance control (control-bar FOG button):
* 'auto' — day/night cycle breathes the fog density (default)
* 'clear' — fixed low density → maximum view distance, no dark phases
* 'dense' — fixed high density → moody, short view distance
*/
fogMode: 'auto' | 'clear' | 'dense';
weather: 'light-fog' | 'heavy-fog' | 'storm' | 'dust' | 'clear';
perfAdapt: boolean;
stats: {
totalUptimeMs: number;
scenesLoaded: number;
perScene: Record<string, number>;
};
// Cross-session story memory (Slice 3). Forward-safe via deepMerge on load.
story: StoryMemory;
}
export const DEFAULT_SCREENSAVER: ScreensaverSettings = {
defaultScene: 'city',
speed: 'norm',
cityAltitude: 'low',
tunnelAutoBoost: true,
bankStrength: 1,
seedLock: null,
renderScale: 1,
kioskMode: false,
colorMode: 'kuro-auto',
colorPreset: 'toxic-haze',
colorCustom: '#00ff41',
aspectPaletteMode: 'matchAspect',
aspectPaletteFixed: 'shugo',
fx: {
bloom: { on: true, strength: 1.4 },
trails: { on: false, damp: 0.84 },
matrix: { on: false, density: 0.5 },
scan: { on: true, opacity: 0.11 },
glitch: { on: false, freq: 0.3 },
vignette: { on: true, strength: 0.35 },
scanlineDrift: { on: true, speed: 0.6 },
radarPing: { on: true, interval: 6 },
burnDecay: { on: false, strength: 0.4 },
chromaticAberration: { on: false, offset: 1.5 },
noiseBursts: { on: false, freq: 0.5 },
powerOn: { on: true },
curvature: { on: true, amount: 0.012 },
aperture: { on: true, strength: 0.22 },
ntsc: { on: false, amount: 0.0 },
halation: { on: true, amount: 0.15 },
},
crtSim: { on: true, intensity: 0.35 },
fxInheritFromTheme: true,
hudPreset: 'tactical',
hud: {
left: true, right: true, radar: true, terminal: true,
crosshair: true, vaultKanji: true, realtime: true,
controlBar: true,
},
bootEnabled: true,
bootSpeed: 'normal',
loreIntensity: 'subtle',
subliminalFlashes: true,
flashFrequencyMean: 90,
easterEggAlerts: true,
sound: {
master: true, volume: 0.4,
hum: true, scanlineWhine: false, sceneSwitch: true,
bootSounds: true, boost: true, radarPing: true,
synthSoundscape: 'carpenter',
},
autoCycle: { on: false, intervalMin: 5 },
idleLaunch: { on: false, minutes: 10 },
embed: { on: false, opacity: 0.7, hudHidden: true },
parallax: true,
liveHotkeysEnabled: true,
escapeOnlyExit: false,
controlBarAutoHideSec: 30,
narrativeTerminal: true,
narrativeReactiveWorld: true,
terminalLayout: 'bottom-strip',
dayNightCycle: { on: true, periodMin: 4 },
fogMode: 'auto',
weather: 'light-fog',
perfAdapt: true,
stats: { totalUptimeMs: 0, scenesLoaded: 0, perScene: {} },
story: { schema: 1, layerCounter: 0, arcsCompleted: [], endingsReached: {} },
};
export type SceneId = ScreensaverSettings['defaultScene'];
export const SCENES: SceneId[] = ['terrain', 'city', 'rift', 'tunnel', 'void', 'wreckage', 'matrix', 'metro'];
// Display labels — used by settings dropdown and control bar.
// (Internal id stays short for storage / commands; label is presentation.)
export const SCENE_LABELS: Record<SceneId, string> = {
terrain: 'TERRAIN',
city: 'CITY',
rift: 'THE RIFT',
tunnel: 'TUNNEL',
void: 'VOID',
wreckage: 'WRECKAGE',
matrix: 'MATRIX',
metro: 'METRO',
};
export const SPEED_VALUES = { slow: 0.32, norm: 1, fast: 2.8 } as const;
export const ALT_VALUES = { low: 4, mid: 11, high: 24 } as const;
export const HUD_PRESETS: Record<string, Partial<ScreensaverSettings['hud']>> = {
minimal: { left: false, right: false, radar: false, terminal: false, crosshair: true, vaultKanji: false, realtime: true, controlBar: false },
tactical: { left: true, right: true, radar: true, terminal: true, crosshair: true, vaultKanji: true, realtime: true, controlBar: true },
full: { left: true, right: true, radar: true, terminal: true, crosshair: true, vaultKanji: true, realtime: true, controlBar: true },
off: { left: false, right: false, radar: false, terminal: false, crosshair: false, vaultKanji: false, realtime: false, controlBar: false },
};