/** * Dynamic register — how the camera's body behaves (composed / elevated / * kinetic / violent) and the beat-locked strobe block. Composes with * `cutsClause` in `cinematography.ts`, which covers edit count and timing. * PURE and deterministic: no I/O, no Date, no Math.random. Extracted verbatim * from `cinematography.ts` (roadmap Phase 3a), which re-exports every public * name, so importers are unchanged. */ import type { DetailLevel } from './cinematography.js'; /** * Dynamic register — the energy dial (Joey 3.0 Cinema Director). * * Cinema mode says what KIND of scene this is; the dynamic register says how * hot the camera runs inside it. It is a separate axis because the two are * genuinely independent: a grief scene can be shot locked-off or with the * camera tearing around the subject, and both are correct choices for * different films. * * The tier binds three things that must agree or the shot reads incoherent — * cant range, camera physicality, and how much of the frame is allowed to be * still. Setting a violent cant with a composed cut rate produces a prompt * arguing with itself, so they travel together as one register rather than as * three flags. * * {@link cutsClause} already covers edit COUNT and timing; this covers the * camera's body. They compose. */ export const DYNAMIC_REGISTER_IDS = ['composed', 'elevated', 'kinetic', 'violent'] as const; export type DynamicRegisterId = (typeof DYNAMIC_REGISTER_IDS)[number]; export interface DynamicRegister { id: DynamicRegisterId; /** Dutch-cant swing in degrees; `[0, 0]` is locked-off. */ cantDegrees: readonly [number, number]; /** Camera-body prose — the physicality ladder rung for this tier. */ physicality: string; /** How much of the frame is allowed to settle. */ stillness: string; } const DYNAMIC_REGISTERS: Readonly> = { composed: { id: 'composed', cantDegrees: [0, 0], physicality: 'locked off on a tripod, or an extremely slow push or pull with the weight of a fluid head under it', stillness: 'frames are held long and allowed to settle completely — the stillness is the subject', }, elevated: { id: 'elevated', cantDegrees: [3, 10], physicality: 'gentle handheld with the operator\'s breath and float visible in the frame, or slow deliberate dolly and crane ' + 'moves; angles are unusual but calm — high overhead, low tabletop, tight profile', stillness: 'frames settle and hold for a beat before the camera moves on', }, kinetic: { id: 'kinetic', cantDegrees: [12, 25], physicality: 'heavy handheld with the operator\'s weight readable in every correction — tracking, orbiting, and pushing, ' + 'the framing slipping and being hauled back', stillness: 'every frame is mid-move, but the eye can still find and land on the subject', }, violent: { id: 'violent', cantDegrees: [25, 45], physicality: 'violent handheld — punching in and ripping back, hard fast surges, violent corrections, with a high-frequency ' + 'vibration running underneath', stillness: 'nothing settles and the frame never lands', }, }; export function dynamicRegister(id: DynamicRegisterId): DynamicRegister { return DYNAMIC_REGISTERS[id]; } /** * The camera-body clause for a dynamic register. * * Every tier except `composed` closes with the never-stabilized run AND the * smooth-in-its-own-travel clause. That last clause is load-bearing: without * it, "violent handheld" is read as permission to return broken footage rather * than energetic footage — the camera is meant to be moving hard, not the * frames to be dropping. */ export function dynamicRegisterClause(id: DynamicRegisterId): string { const reg = DYNAMIC_REGISTERS[id]; if (id === 'composed') { return `Camera body: ${reg.physicality}; the frame is level and square throughout, with no cant. ${reg.stillness}.`; } const [lo, hi] = reg.cantDegrees; return ( `Camera body: ${reg.physicality}. The horizon swings through a ${lo}-${hi} degree Dutch cant, never passing ` + `through level and never settling square. ${reg.stillness}. Never locked, never stabilized, never mechanically ` + 'smooth, never a gimbal glide — every frame mid-move, but always smooth and continuous in its own travel.' ); } /** * THE STROBE — the flashing-light directive. * * Ships with two companions that are not optional. The **secondary glow** keeps * forms readable through the black intervals, or the subject disappears for * half the runtime. The **continuous-motion clause** says the bodies never * stop, only the light does — without it the model freezes the performers * between flashes and the take reads as a slideshow. * * The third companion lives elsewhere by necessity: pair this with * `captureCadenceBlock(..., { strobeQuarantine: true })`, which moves the * stepped quality onto the light and off the footage. Strobe without that * quarantine returns genuinely broken frames. * * Note for lipsync: hard flash-to-black eats roughly half the lip seals. When * both are wanted, soften the strobe on the singer so their face never drops * fully to black while background bodies keep the full treatment. */ export function strobeBlock(bpm: number, d: DetailLevel): string { const core = `THE STROBE IS THE DEFINING FEATURE OF THIS SEQUENCE: the space is lit by hard white strobe flashes firing on a ` + `${bpm} beat-per-minute pulse. The rhythm is flash, black, flash, black — hard on, hard off, with occasional ` + 'double and triple stutter runs. Each flash is instantaneous and brilliant, revealing the scene crisply frozen ' + 'mid-motion; each black interval drops the frame to near-total darkness. There is no fade in and no fade out — ' + 'every transition is a hard snap.'; if (d === 'terse') { return `${core} A dim constant secondary glow keeps forms readable through the black. Bodies move continuously throughout — only the light stops.`; } return ( `${core} Because the bodies move continuously but are only visible during the flashes, every figure appears to ` + 'jump between discrete frozen positions. A dim constant secondary source holds a low glow between hits so forms ' + 'stay readable in the black, and nothing ever sits at a comfortable normal exposure at any point. Nothing is ' + 'ever frozen, held, or static in any performance between flashes — every body is in continuous motion at all ' + 'times, and it is only the light that stops them.' ); }