spatial_delta
Get only what changed since a specific frame.
spatial_delta is the efficient alternative to repeated spatial_snapshot calls. Instead of returning all tracked nodes, it returns only nodes whose tracked properties changed since the given frame. In a scene where most nodes are stationary, this can be 10-50x smaller than a full snapshot.
When to use it
- Polling for changes: "What moved in the last 2 seconds?"
- After a game event: "What changed after the enemy spawned?"
- Watch polling: Reading accumulated changes since your last check
- Debugging a transition: "What happened between frame 300 and frame 340?"
Do not use spatial_delta as your first call in a session — use spatial_snapshot first to get oriented. You need a reference frame number to compute a useful delta.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
class_filter | string[] | optional | Filter by node class. |
groups | string[] | optional | Filter by group membership. |
perspective | any"camera" | "node" | "point" | optional | Perspective to use for the snapshot. Default: camera. |
radius | number | optional default: 50 | Max distance from perspective. Default: 50.0. |
token_budget | number | optional | Soft token budget override. |
since_frame
The frame number from a previous spatial_snapshot, spatial_delta, or clips response. Everything that changed after this frame is included.
If since_frame is older than the ring buffer depth (default: 600 frames = 10 seconds), the server returns an error. Use a recent frame or start a clip recording if you need longer history.
min_distance_change
By default, position changes smaller than 0.01 meters are ignored as noise (floating-point jitter, micro-corrections). Increase this threshold to only report significant movement:
{
"since_frame": 400,
"min_distance_change": 0.5
}This only reports nodes that moved more than 0.5 meters since frame 400 — useful for tracking large movements during an animation or cutscene.
Response format
{
"from_frame": 400,
"to_frame": 450,
"elapsed_ms": 833,
"changed_node_count": 2,
"unchanged_node_count": 10,
"nodes": {
"Player": {
"class": "CharacterBody3D",
"global_position": [3.1, 0.0, -2.3],
"velocity": [2.0, 0.0, 0.0],
"on_floor": true
},
"Enemy_0": {
"class": "CharacterBody3D",
"global_position": [-1.5, 0.0, 4.2],
"velocity": [1.2, 0.0, 0.5]
}
}
}| Field | Description |
|---|---|
from_frame | The since_frame you requested |
to_frame | The current frame when the delta was computed |
elapsed_ms | Milliseconds between from_frame and to_frame |
changed_node_count | Number of nodes with changes |
unchanged_node_count | Number of nodes that did not change |
nodes | Map of node name → changed properties only |
Only changed properties are included in each node entry. If the player's position changed but velocity did not, only global_position appears in the player's entry.
Example conversation
Using delta in a watch loop
The typical watch pattern is:
- Call
spatial_snapshotto get the current frame number - Call
spatial_watchon nodes of interest - Periodically call
spatial_delta { "since_frame": last_frame }to check changes - Update
last_frameto theto_framefrom each delta response
Frame 100: snapshot (baseline)
... game runs ...
Frame 160: delta since 100 → player moved
Frame 220: delta since 160 → player and enemy_0 moved
Frame 280: delta since 220 → enemy_0 changed velocityEach delta response is small because it only includes actual changes.
Tips
Start with spatial_snapshot, then use deltas. You need a frame number to compute a delta. The snapshot gives you one.
Keep since_frame recent. The ring buffer holds ~10 seconds of frames by default. If your since_frame is older, you'll get an error. If you need longer history, use the clips tool to record to disk.
Use min_distance_change to filter noise. Physics simulation produces tiny floating-point perturbations even on "stationary" objects. The default 0.01m threshold handles most cases, but increase it if you're seeing a lot of stationary-looking objects in your delta.
Delta responses include only changed properties. If the player's position changed but rotation didn't, you only see global_position in the response. This is intentional — it keeps responses small and makes changes obvious.