SCENEGOD/scenegod/web/grammar.js
type-two f20a8d5d4e [m11-A] camera moves, + angle, fitAnchor in the inspector
MOVES grammar + solveMove(): push in/pull out (one rung along the shot ladder,
along the aim ray), truck L/R (lateral, parallel, no re-aim), crane up/down
(vertical then re-aim, floor-clamped), orbit L/R (constant distance, +/-25deg),
zoom in/out (focal only - pos and rot returned identical). Amounts are
frame-relative so a move reads the same on a CU as a WS. Emits a new 'move' op
on scenegod:direct with explicit from/to states, pre-clamped duration and ease;
the START is applied to the live camera immediately.

'+ angle' creates a NEW camera from the current shot settings instead of
reframing the active one - the friction hit while building a 4-camera scene by
hand. 🎥 frame now prefers the live camera so it adjusts what you just made
rather than silently editing camera 1. fitAnchor exposed in the backdrop
inspector (it was inert from the UI).

Verified live: push_in 1.975->1.588m fov fixed; zoom_in travel 0.000 with
pos/rot identical, fov 37.85->26.99; orbit_L constant distance; truck_R
parallel. 4 suites green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 18:58:10 +10:00

89 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// grammar.js — classic cinema vocabulary as PURE DATA (Lane A owns; every lane reads).
// No imports, no DOM, no three — safe under plain node. Presets/LLM ops both resolve here,
// so the button path and the /director path can never drift apart.
// Shot sizes: vertical span the framing should cover, as a fraction of subject height,
// @ where the frame centers on the body (fraction of subject height from the feet).
export const SHOTS = {
ECU: { span: 0.12, center: 0.93, label: 'extreme close-up' },
CU: { span: 0.25, center: 0.87, label: 'close-up' },
MCU: { span: 0.45, center: 0.75, label: 'medium close-up' },
MS: { span: 0.65, center: 0.65, label: 'medium shot' },
MWS: { span: 0.85, center: 0.55, label: 'medium wide' },
WS: { span: 1.2, center: 0.5, label: 'wide shot' },
EWS: { span: 2.5, center: 0.5, label: 'extreme wide' },
};
// Camera attitude relative to the aim point. elev = degrees above (+) / below () the
// aim; roll = dutch tilt in degrees; ots = over-the-shoulder of the OTHER subject.
export const ANGLES = {
eye: { elev: 0, roll: 0 },
low: { elev: -18, roll: 0 },
high: { elev: 22, roll: 0 },
dutch: { elev: 0, roll: 8 },
ots_L: { elev: 0, roll: 0, ots: 'L' },
ots_R: { elev: 0, roll: 0, ots: 'R' },
};
// Full-frame focal lengths (mm). Vertical fov = 2·atan(12/f) — 24mm-tall sensor.
export const FOCALS = [18, 24, 35, 50, 85];
// Lighting looks: key ("sun") color/intensity + boom direction (degrees), hemisphere
// ambient, and a background clear color. Values are what applyLight writes verbatim.
export const LIGHTS = {
day: { sun: { color: '#fff6e4', intensity: 3.0, elev: 55, azim: 35 },
ambient: { color: '#dfe9ff', intensity: 1.15 }, bg: '#8fb0d6' },
golden_hour: { sun: { color: '#ffb469', intensity: 2.6, elev: 9, azim: -55 },
ambient: { color: '#ffd9b0', intensity: 0.55 }, bg: '#d98a52' },
night: { sun: { color: '#7e93d6', intensity: 0.6, elev: 40, azim: 150 },
ambient: { color: '#31405e', intensity: 0.4 }, bg: '#0a0e1a' },
noir: { sun: { color: '#ffffff', intensity: 3.4, elev: 52, azim: 75 },
ambient: { color: '#1c1c24', intensity: 0.15 }, bg: '#08080a' },
horror: { sun: { color: '#b9d6a4', intensity: 1.7, elev: -30, azim: 10 }, // underlight
ambient: { color: '#25302b', intensity: 0.25 }, bg: '#05070a' },
neon: { sun: { color: '#ff4fd8', intensity: 1.9, elev: 25, azim: 120 },
ambient: { color: '#2de2e6', intensity: 0.9 }, bg: '#150420' },
overcast: { sun: { color: '#dfe4ea', intensity: 1.2, elev: 70, azim: 0 },
ambient: { color: '#c7cdd6', intensity: 1.4 }, bg: '#9aa3ad' },
};
// Camera MOVES (M11): each one solves to a START and an END camera state off the same subject.
// `kind` selects the solver in presets.js. Amounts are FRAME-RELATIVE (multiples of the framed
// half-height / half-width, which is span·H/2 by definition) so a move reads the same on a
// close-up and a wide, on an 18mm and an 85mm — never a magic metre count.
// dolly push/pull along the camera's own view axis; distance of one shot size (rot+fov fixed)
// truck lateral, camera stays PARALLEL (rot fixed); amount = fraction of the half-frame WIDTH
// crane vertical, re-aims at the subject; amount = fraction of the half-frame HEIGHT
// orbit arc around the subject at constant distance; degrees of sweep
// zoom focal length ONLY — pos+rot identical, fov changes (a zoom is not a dolly)
// dir: 1 = camera-left / down, +1 = camera-right / up. step: rungs along SHOT_ORDER / FOCALS.
export const MOVES = {
push_in: { kind:'dolly', step: 1, label:'push in' },
pull_out: { kind:'dolly', step: -1, label:'pull out' },
truck_L: { kind:'truck', dir: -1, amount: 0.55, label:'truck left' },
truck_R: { kind:'truck', dir: 1, amount: 0.55, label:'truck right' },
crane_up: { kind:'crane', dir: 1, amount: 1.2, label:'crane up' },
crane_down: { kind:'crane', dir: -1, amount: 1.2, label:'crane down' },
orbit_L: { kind:'orbit', dir: -1, degrees: 25, label:'orbit left' },
orbit_R: { kind:'orbit', dir: 1, degrees: 25, label:'orbit right' },
zoom_in: { kind:'zoom', step: 1, label:'zoom in' },
zoom_out: { kind:'zoom', step: -1, label:'zoom out' },
};
// Shot ladder, wide → tight. push_in/pull_out step one rung along it; ends clamp (see DOLLY_CLAMP).
export const SHOT_ORDER = ['EWS', 'WS', 'MWS', 'MS', 'MCU', 'CU', 'ECU'];
// Move timing: `inout` for anything with a human on the wheel, `linear` for mechanical/motion-control.
export const EASES = ['inout', 'linear'];
export const MOVE_MAX_DUR = 6; // seconds — default duration is the scene's remainder, capped here
export const DOLLY_CLAMP = 1.4; // already at ECU/EWS → dolly this factor closer (÷) / farther (×)
// Blocking marks, stage-relative. pos marks move (keep current y); face marks rotate.
export const MARKS = {
center: { pos: [0, 0, 0] },
two_shot_L: { pos: [-0.7, 0, 0] },
two_shot_R: { pos: [0.7, 0, 0] },
face_camera: { face: 'camera' },
face_other: { face: 'other' },
};