Capture identity lanes
Multi-user simulation capture sends frames from several capture instances (origin_uuid) and several simulation entities (crew, vehicles, roles). UUIDs and entity IDs differ on every session, so a session template cannot hard-code them.
system-protobuf-extract maintains a session-scoped captureIdentity registry in session metadata and supports filters so parallel DAG branches (lanes) always process the same logical participant within a session.
The problem
Without stable indices:
- Lane “process pilot events” cannot be authored with a fixed
entity_idororigin_uuid. - Downstream Arbex must scan all entities every run to find names.
- Multi-user SIM templates become fragile copy-paste graphs.
The solution
- One registry node observes all origins and entities and writes
captureIdentityto session metadata. - Lane nodes use
filterEntityIndex/filterOriginIndex(or exact uuid/id/name) so each branch emits only one participant’s records. - Indices are first-seen: the first origin or entity observed in the session gets index
0, the next new identity gets index1, and indices never change for that session.
Session metadata shape
After extract runs, session metadata includes:
{
"captureIdentity": {
"version": 1,
"ordering": "first_seen",
"updatedAtUtc": "2026-07-10T12:00:00.000Z",
"origins": [
{ "index": 0, "originUuid": "…", "firstSeenAtMs": 1726000000000 }
],
"entities": [
{ "index": 0, "entityId": 42, "name": "Pilot-1", "firstSeenAtMs": 1726000000001 }
]
}
}Inspect this in Cluster Manager session metadata or via Core API session GET. Lane nodes resolve filterEntityIndex: 0 to entityId 42 for the rest of the session.
Template recipe
| Node | Config highlights | Role |
|---|---|---|
extract-identity | extractionTarget: EntityState, updateSessionCaptureIdentity: true | Single writer for captureIdentity |
extract-lane-0 | extractionTarget: EntityDataEvent, filterEntityIndex: 0, updateSessionCaptureIdentity: false | Lane for first entity |
extract-lane-1 | filterEntityIndex: 1, updateSessionCaptureIdentity: false | Lane for second entity (empty if session has one entity) |
Rules:
- Only one node per session should set
updateSessionCaptureIdentity: true(session metadata updates are last-write-wins). - Lane nodes default
updateSessionCaptureIdentity: falsewhen using index filters. - Fixed lane count in the template (e.g. four crew stations). Unused lanes emit zero records; that is normal.
Shipped example: preset heat-capture-mainline-scoped.
Filter reference
All filters are optional; multiple filters combine with AND semantics.
| Config | Matches |
|---|---|
filterOriginUuid | Exact packet origin_uuid |
filterOriginIndex | captureIdentity.origins[index] |
filterEntityId | Simulation entity_id on the record |
filterEntityIndex | captureIdentity.entities[index] (lane index) |
filterEntityName | Exact entity name (case-sensitive) |
If a lane runs before the registry has assigned an index, filterEntityIndex / filterOriginIndex match nothing until a later incremental run (lanes warm up during live capture).
Record fields
Extract records include enrichment for downstream binding:
| Field | When present |
|---|---|
origin_uuid | Always (packet envelope) |
origin_index | After origin observed in registry |
entity_id | EntityState / EntityDataEvent rows |
entity_name | When known from frame or registry |
entity_index | After entity observed in registry |
vs capture-discovery
capture-discovery | captureIdentity | |
|---|---|---|
| Purpose | Operator debug dashboard (what arrived?) | Mainline lane routing |
| Ordering | Ranked by observation count | First-seen stable index |
| Output | $heat-dataservice artefact | Session metadata JSON |
| Blocks mainline? | No (leaf branch) | No (metadata side effect) |
Use both: discovery as a leaf, identity registry on the mainline.