Save/reload timeline consistency — DAG model and wire protocol (architecture report)¶
Independent answer to the same open-questions.md prompt as report 05.
Converges on the same core recommendation (event-sourced, fork-on-reload,
co-save-embedded UUID) but contributes a concrete DAG formalization, a
FormID-stability warning neither the compass report nor any prior filed
report raised, and a fully specified wire protocol.
Findings¶
- [DESIGN-INPUT] Skyrim's save topology is fundamentally a DAG, not a line. Because a player can load any historical save at any time, state should be modeled as save-nodes (identified by save-embedded
SaveUUID) connected by edges representing the events that transitioned parent → child state. Deriving "current world state" is a path-traversal query from the root node along the active branch's lineage to the head — this is a direct, more formal restatement of report 05's "event-sourcing branching" finding, useful because it gives Chronicle's engine a concrete traversal algorithm rather than just a metaphor. - [RISK] FormID instability is a persistence hazard this report is the only one to flag. A FormID's upper 8 (or 12, for
.esllight plugins) bits encode the mod's position in the active load order. Adding/removing/reordering mods invalidates any raw FormID stored externally. Chronicle must store composite keys (plugin name + local FormID offset), never raw 32-bit FormIDs — SKSE'sResolveFormIdexists specifically to re-resolve these against the current load order via the save header's embedded plugin list. - [BUILD-ON] A concrete binary co-save record layout is proposed: a
TMNL(Timeline) record withSaveUUID(16 bytes),ParentSaveUUID(16 bytes),SaveSequence(uint64),EngineGameTime/gamets(double),RealTimestamp(uint64 ms). This is a directly implementable schema for the co-save shim, one level more concrete than report 05's prose description of the same watermark concept. - [BUILD-ON] A 4-message WebSocket protocol is proposed:
CLIENT_INIT(handshake, plugin/load-order versions) →SYNC_TIMELINE(shim → service, sent fromkPostLoadGameonce theTMNLco-save chunk is parsed, carryingsave_uuid/parent_save_uuid/save_sequence/gamets) →TIMELINE_READY(service → shim, returns an epoch fencing token) →MUTATION_EVENT(shim → service, must carry the activeepoch_id). - [DESIGN-INPUT] Epoch fencing solves the stale-async-write race concretely. If the player triggers a long-running LLM call and then quickloads before it returns, the response would otherwise land in the wrong timeline. Rule: every load/new-game increments an
epoch_id; every mutation must carry the epoch it was issued under; the service discards any mutation whoseepoch_idis older than its current active epoch. This is a more precise mechanism than report 05's "gate writes on watermark receipt" — the two are compatible (epoch fencing is one way to implement that gate). - [DESIGN-INPUT] Input buffering during the load window is specified as an explicit rule: on
kMessage_PreLoadGame, set an atomicg_isLoading = trueflag; suppress all event-generation hooks untilkMessage_PostLoadGamecompletes, theTMNLchunk is transmitted, and the service returnsTIMELINE_READY. - [RISK] Documented crash-level evidence of the Papyrus-not-ready race: SkyrimNet issue #465, "Save-load crash in
WarmupPapyrusPropertyCache→dynamic_character_bio" — querying Papyrus form properties immediately onkMessage_PostLoadGame, before the engine finishes populating property caches, causes null-pointer dereferences. Mitigation proposed: delay Papyrus-derived event triggers by a minimum threshold (e.g. 200ms) afterkPostLoadGame, or wait for an explicit Papyrus-side initialization heartbeat. - [RISK] SkyrimNet PR #476 reportedly fixed a bug where event histories dropped "originator-less" events during quicksave/quickload timeline rebuilds — background/environmental events with no clear originating actor were silently omitted. Relevant if Chronicle's event schema ever allows an event without an actor field.
- [BUILD-ON] Three-tier GC policy, more concrete than report 05's: (1) disk-alignment mark-and-sweep — the SKSE plugin scans
Saves/or listens forkMessage_DeleteGame, transmits an active-manifest of extantSaveUUIDs, service does reachability traversal from all active heads and flags unreachable DAG nodes; (2) tombstone/soft-delete first, to protect against transient file-lock or Mod-Organizer-2 virtual-filesystem visibility gaps during startup scans; (3) hard purge only after a grace period (e.g. 7 days) or a storage threshold, cascading into vector-DB embedding deletion and re-indexing. - [RISK] Volatile/uncommitted state needs explicit handling: events between save points are an "uncommitted transient edge" — held in a volatile buffer keyed to the active
SaveUUID. OnSAVE_CREATED, commit the buffer. On reload/death without saving (SYNC_TIMELINEfor an earlier save), discard the buffer rather than committing it as if it happened.
Comparative table (as given in the source report)¶
| Dimension | Mantella/Pantella | CHIM/HerikaServer | SkyrimNet |
|---|---|---|---|
| Backend | External Python; SQLite/JSON | External web server (PHP/Prisma/SQL + vector DB) | Integrated C++ SKSE plugin, async worker threads |
| Temporal IDs | Sequential transcript indices, unindexed | gamets, localts, ts |
Save-scoped UUIDs, entity UUIDs, event-history indices |
| Reload handling | Ignore | Append/filter (monotonic log, RAG-relevance filtering) | Reconcile/clean (timeline purge, protected global-knowledge packs survive) |
| Timeline branching | Unhandled | Partial (vector retrieval surfaces anomalies) | Isolated via explicit cleanup protocol |
| Known failure mode | Cross-timeline memory pollution | Personality drift / timestamp desync | Warmup property-cache race crash (#465) |
Note: this report characterizes SkyrimNet as having an explicit "timeline cleanup protocol" (assigns entity/virtual-speaker UUIDs, purges on load, protects curated .sknpack world-knowledge from pruning) — a more optimistic read of SkyrimNet's reload handling than report 05, which called SkyrimNet's reload behavior "effectively ignore/implicit." Neither report's characterization is drawn from a primary SkyrimNet design doc; treat as an open disagreement between the two save/reload sources, not just an uncertainty (see open-questions.md).
Flagged uncertainties (carried into open-questions.md)¶
- SkyrimNet's "explicit timeline cleanup protocol" claim is sourced to a GitHub Discussion (#387) and repo activity, not a design document — weaker sourcing than the co-save/SKSE mechanics sections of this same report.
- The
TMNLbinary layout and the 4-message WebSocket protocol are this report's own proposal, not something any existing mod implements today — treat as a design starting point, not prior art.