/** * Optical-techniques register — named lens-and-camera patterns that produce * specific recognizable looks (Joey 3.0 Worldbuilder). PURE and deterministic: * no I/O, no Date, no Math.random. * * Each technique is a complete optical recipe (vantage + lens + the physical * ingredient that sells the look) plus the constraint that keeps it from * collapsing — the same shape as the VFX register (`vfx-register.ts`), applied * to glass instead of effects. FOV degrees follow the discrete anchor ladder * in `cinematography.ts` (`FOV_ANCHORS`). */ export interface OpticalTechnique { id: string; label: string; /** The prompt-ready optical recipe (vantage → lens → ingredient). */ phrase: string; /** The constraint that keeps the technique from breaking. */ constraint: string; } const TECHNIQUES: OpticalTechnique[] = [ { id: 'voyeur', label: 'Voyeur / long-lens observation', phrase: 'framed from a distant anchored vantage on an 8° (350mm) extreme long lens, a foreground obstruction — ' + 'a wall edge, a pillar, a branch, a curtain — thrown fully out of focus and covering roughly a quarter of the ' + 'frame, suspended atmosphere stacked in the air between camera and subject', constraint: 'the vantage stays anchored across the sequence — never zoom in on a voyeur shot; only the obstruction may change', }, { id: 'press-box', label: 'Broadcast press-box', phrase: 'an 8° (350mm) tele lens from a fixed distant vantage, handheld with a small one-to-two-centimeter hunting ' + 'tremor, the operator hunting the action like a live sports broadcast', constraint: 'the vantage never moves; only the hunt does', }, { id: 'foreground-wide', label: 'Foreground-loaded wide (macro-in-a-wide)', phrase: 'an 84° (22mm) wide lens from a low angle inches from the hero object, the camera almost touching it — the ' + 'object rendered huge in the foreground while the background pushes back into deep space', constraint: 'keep the hero object tack-sharp and anchored; the depth does the drama, not the distortion', }, { id: 'wide-portrait', label: 'Wide portrait', phrase: 'a close face rendered on a 63° (32mm) wide FOV at a normal working distance — the face stays the anchor of ' + 'the frame while the room around it remains legible and in focus', constraint: 'no distortion-stretch on the features; the width is for the world, not the face', }, { id: 'atmosphere-column', label: 'Compressed atmosphere column', phrase: 'a 12° (190mm) long lens stacking the air on itself — a thick column of suspended particulate visible between ' + 'the operator and the subject, compressed into a wall of atmosphere the subject reads through', constraint: 'name the particulate and tie it to the scene (dust, heat waver, mist); the column only reads when the air is real', }, ]; export const OPTICAL_TECHNIQUES: Record = Object.fromEntries( TECHNIQUES.map((technique) => [technique.id, technique]), ); export const OPTICAL_TECHNIQUE_IDS: string[] = TECHNIQUES.map((technique) => technique.id); /** * The optical-technique line for `id`: the recipe + its holding constraint. * Throws on an unknown id (no silent fallback). */ export function opticalTechniqueLine(id: string): string { const technique = OPTICAL_TECHNIQUES[id]; if (!technique) { throw new Error(`unknown optical technique: ${id}. Expected one of ${OPTICAL_TECHNIQUE_IDS.join(', ')}.`); } return `${technique.phrase}. ${capitalize(technique.constraint)}.`; } function capitalize(text: string): string { return text.length === 0 ? text : `${text[0]!.toUpperCase()}${text.slice(1)}`; }