Skip to content

Cameras

The camera decides how the shot is framed and whether it moves — where it sits, what it looks at, how close it is, and from what angle. You set it with one line; agentwood works out the actual position from the subject and the shot length when the block closes.

shot.camera(name, **kwargs) — resolved at shot exit against the final actor positions and shot length. There are nine camera types in three families.

Universal kwargs

kwarg Meaning
mode framing: closeup / medium / wide
R distance in subject radii (× actor_scale)
theta horizontal orbit, degrees (0 = front, 90 = profile)
phi vertical angle, degrees (− = low/looking up, + = high)
subject None → the actor or group midpoint; "group" → midpoint; a Character → that actor

Parameter reference & tuning

Every shot.camera() dial — what it physically changes and how to push it toward a deliberate frame. Defaults shown are the DSL defaults dispatched by shot.camera() (they differ from the raw dsl/camera.py function defaults).

Framing & position (subject-relative cameras)

Dial Type / unit Default What it does
subject None · "group" · Character None What the lens frames & aims at. None = the lone actor, or the group midpoint if >1; "group" = always the midpoint; a Character = lock onto that one actor (e.g. push on one speaker in a two-hander).
mode closeup·medium·wide medium Sets only the look-at height = factor × subject height (≈ 0.81 / 0.77 / 0.60) — where on the body the camera aims (face → full body). It does not set distance.
R distance × actor_scale 2.5 Camera distance from the subject. Smaller = tighter/intimate; larger = wider. ×actor_scale, so the same value ports across sets.
theta degrees 0 Horizontal orbit, added to the actor's facing. 0 = front (their face); ±20–35 = ¾; 90 = profile; 180 = behind.
phi degrees 0 Vertical angle. 0 = eye level; negative = low angle (looks up → powerful/heroic); positive = high angle (looks down → small/vulnerable).

mode is aim, R is distance — they're independent

A common surprise: mode="closeup" with the default R=2.5 is not a tight shot — it just aims at the face from 2.5 away. For an actual close-up, lower R (≈ 0.8–1.5) (the legacy close_up alias felt tight because it also set R=1.2). Think R = how close, mode = how high it aims.

theta / phi rotate around the actor's facing, not the world

Both are added to the subject's yaw, so theta=20 is a ¾-front view whatever direction the actor faces or wherever they stand — the composition stays consistent. Use this instead of guessing world angles.

Working ranges: R ≈ 0.8–1.5 intimate · 2–3 conversation · 6–12 establishing · theta ≈ ±15–35 flattering ¾ · 90 profile · 160–200 behind/OTS · phi ≈ −10…−20 hero · +20…+40 diminish · ±5 just to break a flat dead-on frame.

Movement (per-frame cameras)

Dial Used by Default What it does
R1R2 push, dolly_zoom 4.0→2.0 / 5.0→2.0 Start/end distance of the move. R1>R2 = push in (subject grows); R1<R2 = pull out. Linear over the shot.
arc arc 90 Total orbit degrees — 90 or 180 only (#7). Sweeps −arc/2 → +arc/2 at distance R.
phi_startphi_end tilt −20 → 10 Camera position is fixed; the look-at sweeps vertically between these angles (reveal head-to-toe, or let the gaze fall).
sample_every tracking_* 6 Frames between samples of the actor's path. Lower (1–4) = smoother but slower; higher (8–10) = rougher/faster.

Lens & world-space cameras

Dial Used by Default What it does
f global_static, zoom, dolly_zoom 24 Focal length (mm). Smaller = wider FOV & deeper space (18–35); larger = telephoto, compressed & flattering (50–135). In dolly_zoom it's auto-derived from R (f = 50·R/R1) — don't set it there.
anchor global_static, tracking_static first camera point A named camera point from set.json used as the fixed camera position (world-space).
lookat global_static default subject What the fixed world camera aims at — a Character / subject, or None for the default subject. Pair with anchor to fully hand-author an establishing composition.

Putting these together into looks: see Production-Quality Tuning.

Static — framing & angles

shot.camera("static", mode="closeup", R=1.2, theta=15, phi=-10)
closeup medium wide
closeup medium wide

theta swings around the subject; phi raises/lowers the camera:

low angle (φ−20) high angle (φ30)
low high

Motion cameras

The camera generates one setup per frame, so the shot must last several frames.

name Move Key kwargs
push dolly straight in/out R1, R2
arc orbit ±arc/2 arc (⚠️ only 90 or 180)
tilt look-at sweeps vertically phi_start, phi_end
rotating full 360° orbit R, phi
dolly_zoom "Vertigo" effect R1, R2
shot.camera("push", R1=5.0, R2=1.6)   # dolly in: subject grows
push — start (far) push — end (close)
push start push end

See it in motion (fast proxy):

Tracking cameras

Follow a moving actor; the fixed variants read from the set's camera points.

name Behaviour
tracking_moving camera travels with the actor at a fixed offset
tracking_static fixed camera rotates to keep the actor framed
global_static fixed world camera framing the area
tracking_moving — start tracking_moving — end
track start track end

Legacy aliases

Old names still work: close_up, close_up_low_angle, medium_shot, medium_two_shot, wide_establishing, tracking.

Not in the public API

pan, jitter_static, jitter_tracking, and zoom exist in dsl/camera.py but aren't wired into shot.camera(). pan/jitter are exercised by the comparison scripts below; zoom (a focal-length sweep) currently isn't called by anything.


Lower-level primitives & comparison galleries

Under the friendly shot.camera() API sit the raw camera-math functions in dsl/camera.py (static_camera, push, arc_shot, pan, tracking_*, jitter_*). The repo ships comparison galleries in tests/ that drive those functions directly and render full-resolution PNG sweeps — the visual reference layer, and the only place pan/jitter and the full angle matrix are exercised.

Script What it sweeps Output
render_camera_compare.py 13 camera moves (static, push, arc, tilt, rotating, dolly-zoom, tracking, pan, jitter) renders/camera_compare/
render_camera_compare2.py a 24-shot angle/lens matrix across 3 actor groupings renders/camera_compare2/
render_camera_compare_fullscene.py the 13-shot gallery inside the set renders/camera_compare_fullscene/
render_camera_compare2_fullscene.py the 24-shot matrix inside the set renders/camera_compare2_fullscene/

Every camera primitive, with a sample frame

The functions in dsl/camera.py, each shown with one frame from render_camera_compare.py. Click any row to expand the sample.

Static & global

static_camera(subject, mode, R, theta, phi) — the fixed workhorse shot

A stationary camera. mode sets framing (closeup / medium / wide), theta orbits, phi tilts.

static

global_camera(anchor, lookat, mode, f) — fixed world-space camera

Pinned at one of the set's camera-points, framing the area at focal length f.

global

Motion (one camera setup per frame)

push(R1, R2) — dolly straight in / out

Travels from distance R1 to R2; the subject grows (push-in) or shrinks (push-out).

push

arc_shot(R, arc) — orbit ±arc/2 around the subject (90 or 180 only)

arc

tilt(phi_start, phi_end) — fixed position, look-at sweeps vertically

tilt

rotating_shot(R, phi) — a full 360° orbit

rotating

dolly_zoom(R1, R2) — the 'Vertigo' effect (dolly in while zooming)

dolly_zoom

Tracking & handheld

tracking_static(motion, lookfrom) — fixed camera rotates to follow a walker

tracking_static

tracking_moving(motion, R, theta, phi) — camera travels with the actor

tracking_moving

pan(lookfrom, A, B) — fixed camera pans its look-at from A to B (not in shot.camera())

pan

jitter_static_sequence(...) — handheld shake on a static shot (not in shot.camera())

jitter_static

jitter_tracking(...) — handheld shake while tracking (not in shot.camera())

jitter_tracking

In the real set — the _fullscene galleries run the same shots inside canal_bridge, with real geometry and actor_scale:

fullscene

Two layers

The functions above are the low-level math layer; shot.camera() is the public layer most scenes use. Full shot-by-shot catalogue (13- and 24-shot galleries, bare and in-set): Testing → Existing tests.