Skip to content

clips

Manage dashcam clips and analyze recorded gameplay frame by frame.

The clips tool interfaces with the dashcam system. The dashcam continuously buffers spatial data in memory. When you press F9 or the agent calls add_marker, the buffer is flushed to a clip file (SQLite). Clips can then be queried frame by frame.

How clips are created

Clips are saved in four ways:

TriggerSourceDescription
Press F9 or click in-game buttonHumanSaves dashcam buffer as a "human" clip
clips { "action": "add_marker" }AgentSaves dashcam buffer as an "agent" clip
StageRuntime.marker() in GDScriptCodeSaves dashcam buffer from game code
clips { "action": "save" }AgentForce-flush buffer immediately

Each clip captures ring buffer contents (up to 60 seconds before the trigger) plus approximately 30 seconds of post-capture.

Parameters

ParameterTypeRequiredDescription
actionany
"add_marker" | "save" | "status" | "list" | "delete" | "markers" | "snapshot_at" | "trajectory" | "query_range" | "diff_frames" | "find_event" | "screenshot_at" | "screenshots"
requiredAction to perform.
at_framenumber optional Frame number for snapshot_at.
at_time_msnumber optional Timestamp (ms) for snapshot_at. Finds nearest frame.
clip_idstring optional Clip to operate on (from list response). Defaults to most recent clip if omitted.
conditionany optional Condition for query_range. Object with "type" key. Types: "moved" (threshold), "proximity" (target, threshold), "velocity_spike" (threshold), "property_change" (property), "state_transition" (property), "signal_emitted" (signal), "entered_area", "collision". Example: {"type": "proximity", "target": "walls/*", "threshold": 0.5}
detailstring optional Detail level for snapshot_at: "summary", "standard", "full".
event_filterstring optional Event filter for find_event (substring match).
event_typestring optional Event type for find_event.
frame_anumber optional Frame A for diff_frames.
frame_bnumber optional Frame B for diff_frames.
from_framenumber optional Start of frame range for query_range / find_event.
marker_framenumber optional Frame to attach marker to (add_marker). Defaults to current.
marker_labelstring optional Marker label (add_marker, save).
nodestring optional Node path for query_range.
propertiesstring[] optional Properties to sample in trajectory. Default: ["position"]. Options: position, rotation_deg, velocity, speed, or any state property name.
sample_intervalnumber optional Sample every Nth frame for trajectory. Default: 1.
to_framenumber optional End of frame range for query_range / find_event.
token_budgetnumber optional Soft token budget.

Actions

Clip management

add_marker

Mark the current moment and trigger a clip capture. This is what the F9 key triggers from the in-game flag button.

json
{
  "action": "add_marker",
  "marker_label": "player_clips_wall"
}
ParameterTypeDescription
marker_labelstringOptional label for the marker
marker_frameintegerFrame to mark (defaults to current frame)

Response:

json
{
  "action": "add_marker",
  "clip_id": "clip_1741987200",
  "marker_id": "m_a1b2c3",
  "marker_label": "player_clips_wall",
  "result": "ok"
}

save

Force-save the dashcam buffer as a clip immediately, without adding a marker.

json
{
  "action": "save"
}

Response:

json
{
  "action": "save",
  "clip_id": "clip_1741987200",
  "result": "ok",
  "frame_count": 512
}

status

Get dashcam buffer state, buffer size, and configuration.

json
{
  "action": "status"
}

Response:

json
{
  "action": "status",
  "state": "buffering",
  "buffer_frames": 1247,
  "pre_window_deliberate_sec": 60,
  "byte_cap_mb": 1024
}

States: buffering (running normally), post_capture (saving post-trigger window), disabled (dashcam off).

list

List all available clips.

json
{
  "action": "list"
}

Response:

json
{
  "clips": [
    {
      "clip_id": "chase_bug_01",
      "frame_count": 512,
      "duration_ms": 8533,
      "created_at": "2026-03-12T14:30:00Z",
      "markers": [
        { "marker_id": "m_a1b2c3", "frame": 337, "marker_label": "player_clips_wall" }
      ]
    }
  ]
}

delete

Remove a clip by clip_id.

json
{
  "action": "delete",
  "clip_id": "chase_bug_01"
}

Response:

json
{
  "action": "delete",
  "clip_id": "chase_bug_01",
  "result": "ok"
}

markers

List all markers in a saved clip.

json
{
  "action": "markers",
  "clip_id": "chase_bug_01"
}

Response:

json
{
  "clip_id": "chase_bug_01",
  "markers": [
    { "marker_id": "m_a1b2c3", "frame": 337, "marker_label": "player_clips_wall", "source": "human" }
  ]
}

Clip analysis

snapshot_at

Get the spatial state at a specific frame.

json
{
  "action": "snapshot_at",
  "clip_id": "chase_bug_01",
  "at_frame": 337,
  "detail": "full"
}
ParameterTypeDescription
clip_idstringWhich clip to query
at_frameintegerFrame number (use this or at_time_ms)
at_time_msintegerTimestamp in ms (use this or at_frame)
detailstring"summary" or "full"

Response:

json
{
  "clip_id": "chase_bug_01",
  "at_frame": 337,
  "timestamp_ms": 5617,
  "nodes": {
    "Player": {
      "class": "CharacterBody3D",
      "global_position": [8.92, 0.0, -3.14],
      "velocity": [45.3, 0.0, 0.0]
    },
    "Wall_East": {
      "class": "StaticBody3D",
      "global_position": [9.0, 0.0, -3.0]
    }
  }
}

trajectory

Get position/property timeseries across a frame range.

json
{
  "action": "trajectory",
  "clip_id": "chase_bug_01",
  "node": "Player",
  "from_frame": 325,
  "to_frame": 350,
  "properties": ["position", "velocity"],
  "sample_interval": 1
}
ParameterTypeDefaultDescription
clip_idstringrequiredWhich clip to query
nodestringrequiredNode to track
from_frameintegerrequiredFirst frame (inclusive)
to_frameintegerrequiredLast frame (inclusive)
propertiesstring[]["position"]Properties to track
sample_intervalinteger1Sample every N frames

query_range

Search frames for spatial conditions.

json
{
  "action": "query_range",
  "clip_id": "chase_bug_01",
  "from_frame": 325,
  "to_frame": 350,
  "node": "Player",
  "condition": {
    "type": "velocity_spike"
  }
}
ParameterTypeDescription
clip_idstringWhich clip to query
from_frameintegerFirst frame (inclusive)
to_frameintegerLast frame (inclusive)
nodestringNode to filter on (optional)
conditionobjectCondition filter

Condition types:

TypeDescription
movedFrames where the node moved more than a threshold
proximityFrames where two nodes are within a distance
velocity_spikeFrames with a sudden velocity increase
property_changeFrames where a property changed value
state_transitionFrames where an AnimationTree/FSM state changed
signal_emittedFrames where a signal was emitted
entered_areaFrames where a body entered an Area3D
collisionFrames with a collision event

diff_frames

Compare two frames to see what changed.

json
{
  "action": "diff_frames",
  "clip_id": "chase_bug_01",
  "frame_a": 336,
  "frame_b": 337
}

find_event

Search for a specific event type within a frame range.

json
{
  "action": "find_event",
  "clip_id": "chase_bug_01",
  "event_type": "signal_emitted",
  "event_filter": { "signal": "body_entered" },
  "from_frame": 300,
  "to_frame": 400
}

screenshot_at

Get the viewport screenshot nearest to a frame or timestamp.

json
{
  "action": "screenshot_at",
  "clip_id": "chase_bug_01",
  "at_frame": 337
}
ParameterTypeDescription
clip_idstringWhich clip to query
at_frameintegerFrame number (use this or at_time_ms)
at_time_msintegerTimestamp in ms (use this or at_frame)

screenshots

List screenshot metadata in a clip (frame numbers, timestamps).

json
{
  "action": "screenshots",
  "clip_id": "chase_bug_01"
}

Marker sources

SourceTrigger
humanF9 key or in-game flag button
agentclips { "action": "add_marker" }
codeStageRuntime.marker() in GDScript

Example conversation

I recorded the tunneling bug. The marker was at frame 337. What was happening?
Found clip chase_bug_01 with 512 frames and 1 marker at frame 337 labeled player_clips_wall. Querying the frames around the marker.
Clear velocity spike at frame 337: Player velocity jumps from 14.2 m/s at frame 330 to 45.3 m/s at frame 337 — a 3x increase in one frame (16ms). At that velocity, the CharacterBody3D travels 0.75 meters per tick, which is wider than the Wall_East CollisionShape3D. The move_and_slide continuous collision detection did not catch it because the shape is thinner than the per-tick displacement. Fix: enable CCD in the CharacterBody3D or thicken the wall collision shape to at least 1.0 unit.

Tips

Always specify node in trajectory. Even small clips with all nodes can be enormous. Filter to the 2-3 nodes relevant to the bug.

Use sample_interval: 5 for long clips. Sample every 5th frame for a quick overview, then use snapshot_at to drill into specific moments.

Use query_range with conditions to filter. The proximity condition finds frames where two nodes are within a distance — exactly when collision bugs occur.

Markers are saved automatically with F9. Pressing F9 (or clicking the in-game ⚑ flag button) saves a dashcam clip — the full buffer including ~60 seconds of history before the trigger. The clip is immediately available for analysis via list and snapshot_at.