Skip to Content
This documentation is provided with the HEAT environment and is relevant for this HEAT instance only.
RunnersCore UtilsSimulation Frame Merge Node

simulation-frame-merge (Transform Node)

The Simulation Frame Merge node takes a JSON payload containing long-format SimulationFrame rows (often one row per component per tick) and merges them into wide, CSV-friendly rows.

This is typically used after a hydrate step that emits SimulationFrame-like JSON where each record contains envelope fields plus a component_data object.


Configuration Schema

PropertyTypeRequiredDescription
inputMode"all" | "latest"NoInput selection policy. all enumerates all available parent outputs (can duplicate rows if parents emit cumulative snapshots). latest uses only the most recent parent output. Defaults to all.
rootstringNoJSON object key whose value is an array of long-format rows. Defaults to "simulation_frame".
groupByarray<string>NoFields to group by before merging long-format rows into one wide row. Defaults to ["simulation_tick","entity_id"].

Multi-origin capture (origin_uuid)

Long-format rows from more than one HEAT capture source can share the same simulation_tick and entity_id (for example client and server both observing the same vehicle). If you only group by simulation_tick and entity_id, those rows are merged into a single wide row. Envelope fields such as origin_uuid then use first non-empty wins (see implementation), so attribution to one source is easy to get wrong and timelines can look truncated when a downstream step keys off origin_uuid.

Recommendation: when multiple origin_uuid values appear in the hydrate output for the same entity, add origin_uuid to groupBy so each (simulation_tick, entity_id, origin_uuid) becomes its own merged row:

"groupBy": ["simulation_tick", "entity_id", "origin_uuid"]

Tradeoff: In overlap regions you get up to one wide row per origin per tick (higher row count). Downstream nodes (for example tabular-remap) can then filter rows by origin_uuid if you need only one station (driver vs instructor) for a dashboard.

Note: origin_uuid identifies the capture client, not automatically a KB role (Driver vs Instructor). Dropping “instructor” rows still requires a deployment-specific mapping from role to origin_uuid (allowlist, or discardRowsExpr on known UUIDs). See dev-notes/origin-uuid-policy.md at the repository root for a policy template.


Behaviour

  • Input selection (inputMode):
    • Default (all): the node enumerates all available upstream outputs and selects the first payload in that enumeration.
    • latest: the node uses only the latest upstream output.
    • When to use latest: when the parent node outputs cumulative snapshots over time. Using latest avoids reprocessing earlier partial snapshots and reduces duplicated CSV rows.
  • Note on multi-output parents: inputMode: "all" enumerates outputs and some runtime paths return a list of payloads for a single parent (one per upstream output). In that case, this node selects the first payload in that list.
  • The node parses the upstream payload as JSON and looks for root (an array).
  • Rows are grouped by groupBy. For each group:
    • envelope fields are filled from the first non-empty value seen
    • component_data is flattened into component_data_* keys and unioned into the merged row
  • Output is a CSV string with headers inferred from the union of keys across merged rows.

Example configuration

Default (single origin or you accept merged envelopes):

{ "inputMode": "latest", "root": "simulation_frame", "groupBy": ["simulation_tick", "entity_id"] }

Multi-origin (separate wide rows per capture source):

{ "inputMode": "latest", "root": "simulation_frame", "groupBy": ["simulation_tick", "entity_id", "origin_uuid"] }