SPEC.md § Event (v1.0 stub)Replace the v1.0 Event stub with a complete event schema that supports temporal scheduling (validFrom / validTo), trigger chains (event A causes event B), reaction graphs (multiple entities respond to the same event), and deterministic replay. Enables the “Infinite zoom across time” pillar from the Sight Triad (see constitution.md § The Affirmative Mirror).
v1.0 ships event as a one-field stub:
{ "id": "<local-id>", "t": "<ISO-8601>", "type": "<event-type>", "entity": "<entity-id>", "metadata": {} }
This is enough to log that something happened; it is not enough to:
Without this RFC, “Infinite zoom across time / possibilities” remains aspirational in the spec. With it, shipping a replay-scrubber / counterfactual-branch UI becomes a straight implementation rather than a research project.
{
"id": "<local-id>",
"type": "<event-type>",
"t": "<ISO-8601 | null>",
"entity": "<entity-id> | null",
"validFrom": "<ISO-8601 | null>",
"validTo": "<ISO-8601 | null>",
"causedBy": ["<event-id>", "..."],
"causes": ["<event-id>", "..."],
"actors": ["<entity-id>", "..."],
"payload": { /* event-type-specific */ },
"branch": "<branch-id | null>",
"metadata": {}
}
Field semantics:
t — when the event fired (wall-clock). Nullable for scheduled-but-not-fired events.validFrom / validTo — activation window for recurring / scheduled events. When both are null, event is one-shot.causedBy / causes — explicit causal edges. Walk causedBy to explain an event; walk causes to simulate downstream effects.actors — one-to-many: when multiple entities react to one event, actors is the set that reacted (e.g. "audio_playback_started" actors: {cube_1, sphere_2} — both wire-bound to it).branch — counterfactual branch id. The main timeline is branch: null; a “what if the player did X instead” is a separate branch. Entities + relations can be shared across branches; events are branch-specific.v1.1 ships with a minimum viable vocabulary — not exhaustive, extensible via xrai:namespace:
| type | semantic |
|---|---|
scene.created |
a scene / subgraph came into existence |
scene.loaded |
a saved scene was re-realized |
entity.added |
a new entity was inserted |
entity.removed |
an entity was deleted |
entity.transformed |
position / scale / rotation changed |
stroke.added |
a paint stroke was committed |
audio.peak |
an audio-reactive source crossed a threshold |
voice.intent |
a voice command was parsed (confidence, transcript, action) |
user.presence |
user joined or left a shared space |
hologram.placed |
a hologram was anchored |
time.tick |
metronomic event for scheduled recurring payloads |
Extension: xrai:namespace:type for domain-specific events (medical:vitals:bp_reading, education:quiz:submitted).
A conforming v1.1 runtime that receives a scene with events MUST:
t then id).validFrom / validTo / causedBy / causes / branch if it doesn’t implement them (forward-compat).causedBy id references an event not present in the document, log + continue (partial graphs are valid).A conforming v1.1 runtime MAY:
causes forward-chains to predict downstream effects.Scheduled recurring event:
{
"id": "evt_remind_1",
"type": "voice.intent",
"t": null,
"validFrom": "2026-05-01T09:00:00Z",
"validTo": "2026-12-31T23:59:59Z",
"payload": {
"recurrence": "FREQ=WEEKLY;BYDAY=TU",
"intent": "remind",
"text": "Check on the garden"
}
}
Causal chain:
[
{"id": "evt_step_plate", "type": "entity.transformed", "t": "...", "entity": "player_1", "payload": {"collider": "plate_a"}},
{"id": "evt_door_open", "type": "entity.transformed", "t": "...", "entity": "door_1", "causedBy": ["evt_step_plate"], "payload": {"state": "open"}}
]
Counterfactual branch:
[
{"id": "evt_step_plate", "type": "entity.transformed", "t": "...", "branch": null, "entity": "player_1"},
{"id": "evt_step_plate_b", "type": "entity.transformed", "t": "...", "branch": "what_if_b", "entity": "player_1"},
{"id": "evt_door_open", "branch": null, "causedBy": ["evt_step_plate"]},
{"id": "evt_trapdoor_open", "branch": "what_if_b", "causedBy": ["evt_step_plate_b"]}
]
metadataLeast invasive, preserves back-compat trivially. Rejected because: it pushes the schema burden onto every runtime to agree on metadata shape, which is exactly what a spec is supposed to avoid. Metadata is for runtime-specific things; causal structure is not runtime-specific.
CloudEvents is battle-tested for server-to-server event pipelines. Rejected for the core schema because: (1) CloudEvents is designed for flat, non-spatial events; it has no concept of entity / actors / branch; (2) the specversion + datacontenttype ceremony adds parse cost without value in our use case. We can still serialize our events AS CloudEvents at the transport layer (when shipping over Kafka / EventBridge) — this RFC and CloudEvents are not mutually exclusive.
OTel spans have great causal modeling (parent-child traceId / spanId). Rejected because: OTel is designed for distributed tracing of software execution, not user-authored events. The conceptual fit is wrong; traceId doesn’t map cleanly to branch.
events array)Model each event as an entity with type: "event.*". Rejected because: events have fundamentally different lifecycle from scene entities (they fire once, they can’t be re-transformed, they don’t occupy space). Collapsing them into entities forces every renderer to special-case “which entities are events?” — worse than keeping them in their own array.
validFrom, validTo, causedBy, causes, actors, payload, branch) are all optional.SPEC.md § Conformance rule 3). Replay semantics are lost but data survives.SPEC.md § Event replaced with the full schema above.SceneComposer event recording + replay-scrubber stub.xrai.event_emit (optional — may defer to v1.2).xrai-website/examples/05-scheduled.xrai.json, 06-causal-chain.xrai.json, 07-counterfactual-branch.xrai.json..xrai.jsonl event-stream format. Open for discussion.id: evt_1 with different payloads, what’s the merge rule? Current proposal: later t wins; ties broken by author lexicographic. Acceptable?t is ISO-8601 with timezone. For distributed scenes, clock skew could cause out-of-order events. Proposal: clients normalize to UTC at emit; conflict resolution is “server clock wins” when a sync service is present. Open.causedBy + branch pair..xrai.jsonl) for live / append-only scenarios.causedBy / causes populated). If less, events are too ceremonial — reconsider.If all three signals miss, this RFC will be superseded by a simpler v1.2 revision.