---
name: record-video
description: >
  How to record deterministic video or PNG sequences from the luncosim —
  windowed or fully windowless. Trigger whenever the user asks to "record a
  video", "capture an episode", "render a cinematic", "make a recording of the
  scene/tutorial", to record without opening a window / on a headless box, or
  when a scenario needs frame-exact capture. Covers the one-command CLI takes
  (`--record-offline`, `--offscreen`), the video-vs-PNG destination rule, the
  rhai shot sequencer, and how to check progress and diagnose a black or
  stalled take.
---

# Record a deterministic video

The recorder advances virtual time exactly `1/fps` per captured frame — output
is a function of frame index, never machine speed. Full design:
[`docs/offline-recording.md`](../../docs/offline-recording.md).

## The one-command take (start here)

```sh
# Windowless: no window/egui, renders offscreen, EXITS BY ITSELF when done
"$LUNCOSIM_BIN" --api 4101 \
  --offscreen --render-quality high \
  --record-offline ~/.cache/take.mp4 --record-fps 30 --record-frames 300

# Windowed variant (records the live window; you stop it, or pass --record-frames)
"$LUNCOSIM_BIN" --api 4101 --record-offline ~/.cache/take.mp4 --record-fps 30
```

- Recording starts **after the scene-visuals readiness gate**, not at process
  start — don't be surprised by a few seconds of warm-up before frame 0. For a
  USD scene this gate includes the stage's awaiting prims, queued visual
  projections, asynchronously generated meshes, and the selected image camera;
  a Rhai verdict or one early `Mesh3d` is not sufficient evidence that the render
  view has participants.
- **Destination picks the format**: `.mp4`/`.mkv`/`.mov` streams into `ffmpeg`
  (one small file); any other path is a directory of `frame_%06d.png`. No
  `ffmpeg` installed ⇒ loud warn + PNG fallback in `<name>.frames/`.
- `--record-size WxH` sets the offscreen resolution (default 1280x720, the
  windowed default). `--scene PATH` picks the scene, as usual.
- **Output goes under `~/.cache/...`**, never `/tmp` (root partition) and never
  the repo.

## Offscreen mode: what to know

- `--offscreen` is GPU-full windowless — NOT `--no-ui` (which drops the GPU and
  cannot capture anything).
- `--render-quality high` selects the highest shipped renderer budget for the
  process: shadows, sky cubemap, lunar terrain LOD, and tessellation. USD still
  owns the authored terrain/sky shader sources; this flag does not swap them.
- No workbench exists, so the recorder activates the scene's first **authored**
  `SceneCamera` (e.g. the luncosim `WideShot`). A scene with no authored camera
  records black and logs a warning — author a camera, don't fight the picker.
- The target-born offscreen camera mirrors the selected authored camera's transform,
  projection, exposure, tonemapping, MSAA, and camera-owned environment views
  (`Skybox` plus generated image-based-lighting intent), so PNG/video output uses the
  same authored grade and DomeLight background as the windowed presentation.
- `--offscreen --api PORT` (without `--record-offline`) gives a windowless
  interactive instance: `StartOfflineRecording` / `CaptureScreenshot` work over
  HTTP and read the offscreen target.
- Terrain appearance fixtures keep the DEM's authored elevation datum. Author
  the presentation camera near that datum; a camera aimed at elevation zero can
  validly cull every terrain tile. The terrain graphics gate also checks the
  full PNG and far/middle/near bands for real relief, not just file creation.

## Scripted episodes (rhai)

Multi-shot episodes run on the shot sequencer — `shot_begin`/`shot_frame`/
`shot_end` from `prelude/recording.rhai`, sequenced by `lib/shots.rhai`. Shot
timing MUST use the recorder's `frame_index` (never a tick counter), and beats
freeze via a PHYSICS hold (never a clock pause). Both traps + the sequencer
contract: [`docs/offline-recording.md`](../../docs/offline-recording.md) §5–§9.

## Check progress / diagnose

```rhai
query("GetOfflineRecordingStatus")
// #{ active, frame_index, video, outstanding_captures, pending_saves, … }
```

- **Stalled?** `outstanding_captures`/`pending_saves` pinned at their caps means
  back-pressure (slow disk/encoder) — the clock pauses advancing by design and
  resumes when the pipeline drains.
- **Black video?** Almost always "no rendering camera": offscreen without an
  authored `SceneCamera`, or every camera `is_active: false`. Check the log for
  the offscreen camera warning. Alpha-0 frames (white in PNG viewers, black in
  video) mean the camera never rendered at all.
- **Take aborted?** A failed frame save aborts loudly and names the frame; the
  sequence up to it is intact. Disk-full is the usual cause.
- A killed run may leave `.tmp` files next to frames — litter, not corruption;
  writes are atomic.

## Rules

- NEVER "fix" slowness by freezing the clock for readback or writing
  `TimeUpdateStrategy`/`WinitSettings` anywhere new — each knob has exactly one
  writer (doc §2).
- `--no-vsync` does NOT speed up recording (measured slower: presenting
  flat-out starves the save workers).
- Frame pacing, capture, and saving live in `lunco-capture`; the workbench and
  offscreen host install the same capability, while the CLI only arms the
  recorder state the commands use.
