/** * Realism register — the capture-realism clause bank (specular kill, * subsurface, strand hair, contrast curve, moisture, flattering skin), the * volumetric-haze emitters (single density and three-plane), the phone-capture * block and the background plate. 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'; /** * Anti-plastic physics clauses (banana-pro-director). Each is a standalone * exported string helper so callers can compose them individually before the * full captureRealismBlock lands. Per-zone specular naming is required — * "matte skin" alone is too weak and gets overridden by the model default. */ export function specularKillClause(): string { return 'all specular highlights surgically removed from skin — zero shine on forehead, nose bridge, cheekbones, temples, and chin, no oily T-zone, skin matte and velvety'; } export function subsurfaceScatteringClause(): string { return 'subsurface scattering at ear edges, nostrils, and around the eye sockets with warm undertone bleed, reading as semi-translucent biology never opaque plastic'; } export function strandHairClause(): string { return 'hair rendered strand by strand with flyaways and baby hairs at the hairline, hair physics responding to the actual environment, matte by default never glossy'; } export function contrastCurveClause(): string { return 'shadows lifted gently, highlights rolled off, nothing clipping or crushing — a low-contrast slightly-desaturated grade with warmth preserved'; } export function moistureMatteClause(): string { return 'damp not beaded, wet not glossy — moisture mutes and saturates the surface without a single specular hotspot'; } export function flatteringRealismClause(): string { return 'no acne, no blemishes, no enlarged or rough pores, no harsh clinical texture — fine flattering even skin'; } export type HazeDensity = 'thin' | 'light' | 'heavy'; const HAZE_WORDS: Record = { thin: 'a faint trace of atmosphere', light: 'light atmospheric haze', heavy: 'heavy volumetric haze and visible air density', }; /** * Volumetric depth ("lighting the air") — the single biggest anti-plastic * depth lever. Exposed standalone; previously reachable only inside the * `atmospheric` cinema mode's filtration field. */ export function volumetricHaze(density: HazeDensity, d: DetailLevel): string { const words = HAZE_WORDS[density]; if (d === 'terse') { return `${words} between camera, subject, and background`; } const core = `${words} between the camera, subject, and background — distant planes rendered softer, ` + 'desaturated, and lower-contrast than the foreground'; if (d === 'standard') { return core; } return `${core}; real volumetric atmosphere, never a flat backdrop`; } export interface CaptureRealismOpts { /** Emit the moisture-matte clause (skipped when false/omitted). */ wet?: boolean; /** Haze density for the depth clause (default 'light'). */ haze?: HazeDensity; /** Film-grain stock descriptor (default '35mm'). */ grainStock?: string; /** * Drop the haze clause entirely. Set when a dedicated ATMOSPHERE block is * carrying the haze instead: every fact belongs to exactly one block, and a * packet that states its atmosphere twice reads long without reading * specific — the duplicate dilutes the original rather than reinforcing it. */ omitHaze?: boolean; } /** * The keystone anti-AI-look block: physics-vs-hardware separation that does not * exist anywhere else in the codebase. Composes per-zone specular kill, * subsurface scattering, strand hair, contrast-curve-three-ways, volumetric * haze, optional moisture, the flattering-realism ceiling, and film grain. * Pure and deterministic; density scales with DetailLevel. */ export function captureRealismBlock(opts: CaptureRealismOpts, d: DetailLevel): string { const grain = opts.grainStock ?? '35mm'; const haze = opts.omitHaze ? '' : volumetricHaze(opts.haze ?? 'light', d); // terse: condensed summary (full clause composition only on standard/rich) if (d === 'terse') { return haze ? `Matte anti-plastic skin, soft ${grain} grain, ${haze}.` : `Matte anti-plastic skin, soft ${grain} grain.`; } const parts = [ specularKillClause(), subsurfaceScatteringClause(), strandHairClause(), contrastCurveClause(), haze, flatteringRealismClause(), ].filter(Boolean); if (opts.wet) { parts.push(moistureMatteClause()); } parts.push(`soft natural ${grain} film grain, photographed not generated`); return `Capture realism: ${parts.join('; ')}.`; } export interface PhoneCaptureOpts { /** Emit the flattering-skin clause (default true; set false to drop it). */ flatteringSkin?: boolean; } /** * The amateur / anti-AI phone-capture register — the UGC sibling of * {@link captureRealismBlock}. Where captureRealismBlock describes high-end * cinema hardware (anamorphic glass, diffusion, film grain), THIS register * deliberately strips all of that and reads as an unstaged smartphone clip: * available light, computational-HDR flatness, slight phone-lens softness, and * the casual imperfection that signals "not an ad." This is a NEW sibling * register — the phone/UGC voice is NOT part of Joey's 2.0 skills (those are * cinema-only); it is inspired by Joey's anti-AI / "avoid commercial gloss" * philosophy rather than lifted from his wording. * * A flattering-skin clause is kept by default (good UGC still flatters the * subject); that clause IS from banana-pro-director-2.0. The cinema gear — film * grain, anamorphic, diffusion bloom — is dropped. Pure and deterministic; * density scales with DetailLevel. */ export function phoneCaptureBlock(opts: PhoneCaptureOpts, d: DetailLevel): string { const flattering = opts.flatteringSkin !== false; const skin = flattering ? 'fine flattering even skin, no acne, no blemishes, no harsh clinical texture' : ''; if (d === 'terse') { const head = 'shot on a modern smartphone camera, natural available light, the casual imperfection of an unstaged phone clip — never cinematic, never an ad'; return skin ? `${head}; ${skin}.` : `${head}.`; } const parts = [ 'shot on a modern smartphone camera', 'natural available light', 'mild auto-exposure drift', 'slightly soft phone-lens focus', 'flat computational-HDR tonality', 'no film grain', 'no anamorphic', 'no diffusion bloom', 'the casual imperfection of an unstaged phone clip — never color-graded, never cinematic, never an ad', ]; if (skin) { parts.push(skin); } return `Phone capture: ${parts.join('; ')}.`; } export interface ThreePlaneHazeOpts { /** Haze density (default 'light'). */ density?: HazeDensity; /** Foreground plane label — what sits sharpest/most-saturated nearest camera. */ foreground?: string; /** Midground plane label — the softening transitional plane. */ midground?: string; /** Background plane label — softest, most desaturated, lowest-contrast. */ background?: string; } /** * Three-plane volumetric haze: the depth-staging extension of * {@link volumetricHaze}. When given foreground / midground / background plane * labels it emits the explicit three-plane relationship — the foreground sharp * and saturated, the midground softening, the background softest, most * desaturated, and lowest-contrast — so the haze reads as real staged depth * rather than a flat wash. * * Backward-compatible: with NO plane labels it returns exactly today's * single-register {@link volumetricHaze} string. Carries no Kelvin / degrees / * ratio numerals. Pure and deterministic. */ export function volumetricHazeThreePlane(opts: ThreePlaneHazeOpts, d: DetailLevel): string { const density = opts.density ?? 'light'; const { foreground, midground, background } = opts; // No plane labels => identical to the single-register haze (backward compatible). if (!foreground && !midground && !background) { return volumetricHaze(density, d); } const words = HAZE_WORDS[density]; const fg = foreground ?? 'the foreground'; const mg = midground ?? 'the midground'; const bg = background ?? 'the background'; const relationship = `${fg} held sharp and fully saturated nearest camera, ` + `${mg} softening and stepping back through the haze, ` + `${bg} the softest, most desaturated, and lowest-contrast plane of all`; if (d === 'terse') { return `${words} staged across three planes: ${relationship}`; } const core = `${words} layered across three depth planes — ${relationship} — ` + 'so the air itself carves the depth between them'; if (d === 'standard') { return core; } return `${core}; real volumetric atmosphere staged front-to-back, never a flat backdrop`; } export type PlateKind = 'mid-gray' | 'white' | 'black'; const PLATE_WORDS: Record = { 'mid-gray': 'even neutral mid-gray seamless background, no seam line, no gradient, no falloff to black or white', white: 'clean white seamless background, evenly lit, no gradient', black: 'deep matte black seamless background, no spill, no falloff edge', }; /** * Backdrop plate spec. Mid-gray is the locked default for ALL character work — * it lowers subject-to-background contrast so downstream video inherits cleaner * edges. White/black are explicit opt-ins. */ export function backgroundPlate(kind: PlateKind, d: DetailLevel): string { const words = PLATE_WORDS[kind]; if (d === 'terse') { return words; } if (kind === 'mid-gray') { // standard: plate words only; rich: add the true-natural-tone elaboration return d === 'rich' ? `${words}; subject and wardrobe rendered at their true natural tone against the neutral gray` : words; } return words; }