Skip to content

Causal trace

Causal trace

Kind Engine infrastructure (not a system, not toggleable, not in the graph)
Project NextIteration.Engine/Trace/ (TraceRecorder, TraceStore, TraceQueries)
Exposes ITraceStore; Explain, History, WhyChanged, Events, Decisions, Intents, TickDigest
Consumes applied/rejected intents, decisions, events, propagations, destroyed nodes, player actions
Consumed by the viewer (Explain this, timelines), the InterestScorer, the narrator (Phase 5), the harness and milestone tests
Status designed · Phase 1 (#31)
PLAN §13.5, §13.3, §10.1, §4.2
Player Explain this

The graph is the present; the trace is the past. A trace record cannot affect the next tick — delete the whole trace and every subsequent tick is identical — so it is derived data, kept outside the graph, read by no system, never copied with a snapshot, not in the save file, and it outlives destroyed nodes. An NPC's memory of an event is a fact in the graph (and can be wrong); the trace is ground truth.

Records (per tick, in resolution order)

Record Fields
intent applied tick, ordinal, intent (type, target, key, value/delta), Inputs if set, produced event ids, source
intent rejected / conflict as above + reason, rival ordinal
decision tick, actor, winning Option, top-3 runners-up, inputs, personality weights
event id (tick << 20) \| ordinal, type, source, location, data, producing ordinal
propagation source node, channel, per-hop (node, intensity)
node destroyed kind, template, name, components at death
player action player id, the action, the ordinals it became
rule fired (Phase 3) · oracle call (Phase 5)

Full field lists on Trace records. Inputs on intents and options are what give a chain its next hop: FireSystem's ignition intent carrying smithy.temperature = 262, inn.ignition_point = 250 is why Explain can say because the smithy's heat crossed 250°.

Queries

Explain(eventId, depth = 6, breadth = 3)    chain: event → intent → source → inputs → the intents that wrote them → … (max depth 20)
Explain(entityId)                            latest decision: winner, runners-up, inputs and each input's provenance
History(entityId, from, to)                  timeline: decisions, intents applied to it, events naming it, in world-time order
WhyChanged(nodeId, attribute, generation)    the intent that wrote this value, and its source
Events(from, to, filter)   Decisions(from, to, actor?, need?)   Intents(tick, source?)
TickDigest(tick)                             stable hash over the tick's applied intents — the determinism signature

Endpoints: GET /worlds/{id}/explain/event/{eventId}, …/explain/entity/{nodeId}, …/history/{nodeId}?from&to, …/explain/change/{nodeId}/{attr}/{generation}.

What it does not answer: the state of the world at time T. "Was the inn on fire at 14:00" is a fold over the inn's history; "who was where" is a fold over containment changes. Full state at T is the ring buffer or a replay.

Storage tiers

Tier What Phase
Hot in-memory buffer, trace_buffer_ticks (default 2,000 = ten village days); the harness sizes it to its run; indexed by id, (node, attribute, generation), entity, source, time 1
Warm append-only JSONL segments under saves/{universeId}/trace/, one per tick range, each with an index; Explain/History fall through; the hot buffer rebuilds from the newest segments on load; behind ITraceStore 1
Compaction beyond trace_retention keep events and decision winners, drop intent detail and runners-up; aligned with LOD Scale
Cold replay: seed + player-action log + oracle log regenerate any window from the nearest save Scale

Not a time-series database: every query is an id, entity or window lookup over an append-only log. Volume at village scale is a few hundred records a tick, tens of megabytes per ten days.

Four consumers

  1. Viewer — Explain rendered as prose per record kind; the character card's timeline is History.
  2. InterestScorer — chain depth and breadth of this tick's events, hot buffer only; replaces the fixed weight table.
  3. Narrator (Phase 5) — the chain is the RAG retrieval query.
  4. Milestone tests — "gathered at the campfire" is a fold over containment history; "fled" is a decision whose winning need is safety; "the bucket race" is an intent_conflict on a Vessel. If a story cannot be asserted from the trace, the trace is what needs fixing.

History

Phase Change
1 Introduced: records with ids and Inputs, the five queries and the digest, hot + warm storage, the "graph is the present" boundary.

See also

From perception to Explain this · Trace records · Harness · Determinism