Simulation.checkpoint() captures the complete state used by the current simulation profile at a completed boundary. Simulation.fromCheckpoint(world, checkpoint) prepares an independent runtime and returns it only after validation and native restoration succeed. Keep the old runtime until that promise resolves, then replace it and dispose the old one. A rejected checkpoint never edits the old runtime.
This advances milestones 1 and 3. It does not complete the remaining engine roadmap, introduce durable room checkpoint replication, or migrate the relay's durable room format. The local player save now uses format 2, with an explicit lossless upgrade from beta format 1.
#Format and ownership
The JSON envelope contains format: 1, engine version, darkwind.simulation.6, world/content hash, component-catalog hash, payload and SHA-256 of the unsigned canonical envelope. Runtime values are not rounded for replay. Content identity uses the existing authored-world serialization policy. Semantic changes require a new profile or explicit migration; unknown profiles, versions and catalog identities are rejected.
| Payload | Captured state |
|---|---|
| Compiled content | Primitive compiler version, authored world hash, source-to-ECS identity mapping hash and asset catalog hash |
| Simulation | Place, actual physical position, player velocity/ground state, inventory, flags, door state, tick, pause state, interaction cache and claimed shared items |
| Input | Accepted frame, sequence, expiry age and retained facing, including input accepted between simulation ticks |
| ECS | Full versioned entities/components, including authored geometry, and accepted commit tick, validated against spatial, identity and compiled-binding invariants |
| Scheduler | Last completed tick or an explicitly unstarted schedule |
| Events | Retained payloads, exact sequence/tick/expiry and the presentation consumer's acknowledgement cursor |
| Physics | Native Rapier world snapshot, player/door collider bindings, velocity, grounded state, held-jump edge state and character-controller settings |
Platform-free scenes use physics profile [email protected]/character.1; owned-platform scenes use [email protected]/character-platforms.1. Settings include up direction, offset, normal nudge, sliding, impulse application, mass, autostep, snap distance and climb/slide angles. Disabled options are represented explicitly as null. The native snapshot retains bodies, colliders, contacts and backend state; a headless test also restores a sleeping dynamic body, wakes it with an impulse and compares subsequent snapshots.
Simulation ticks and accepted input sequences now use canonical decimal uint64 strings in JSON, worker messages and saved state. Arithmetic and comparison use bigint, including above 2^53. ECS, scheduler and event ticks must agree exactly at a checkpoint. The separate wall-clock accumulator still uses seconds as floating point, but its step counter is bigint. See time and migration contracts for legacy upgrades, input ordering and exhaustion behavior.
Capture copies state synchronously before asynchronous hashing, so later ticks cannot change a checkpoint already being captured. The simulation owns a frozen copy of its authored content. Pending structural commands, active ECS queries, faulted schedules and disposed runtimes reject capture. External wall-clock accumulators, renderer/audio resources, network sessions, timing measurements, journal history and process-local handles are outside simulation state. Restore needs a fresh reader baseline and handles rebound by stable entity ID; old-runtime handles/cursors are not transferred.
#Explicit migration
Simulation.migrateCheckpoint accepts profiles 1, 2 and 3 on their original world-format-1 content after checking their sealed envelopes. The output is profile 7 with compiler primitive-world.2. Profile 1 upgrades safe numeric time fields; profile 2 retains exact string ticks. Profiles 1 and 2 add compiled authored rows resolved from the saved doors, inventory and flags, then bind the compiled signature and independently restore the complete runtime. In profiles 1 and 2, a legacy row using the reserved authored: namespace rejects instead of being overwritten. Existing player, spawned entities, native physics, scheduler, input and retained events are preserved. The raw profile-2 fixture, captured from 28573b1, retains a spawned entity and a tick above 2^53; profiles 1 and 2 remain pinned as migration sources. Profile 3 already has authored rows: migration verifies its old compiled signature, changes the compiler identity, adds the missing platform descriptors, and retains previous entity values/native/player state. The raw profile-3 fixture, captured from d785573, includes an edited authored transform and a spawned entity. New world-format-2/3/4 authoring content uses profile 7 directly; migration does not silently change a checkpoint to another content release. Local player saves and relay persistence retain their existing formats.
Profile 4 also migrates explicitly for its original world formats 1–3. Its content signature and component catalog must match, and its entity values, native state and other gameplay payload are preserved while the missing platform descriptors are added. The raw profile-4 fixture, captured from 260f7e4, contains authored imported geometry and native state. Profile 5 activates bounded AnimationState advancement and validates clip/model bindings during commit and restore. Invalid preexisting bindings reject migration; they are never guessed or silently renamed. See animation state.
Profile 5 also migrates explicitly. Profile 6 adds the bounded KinematicPlatform descriptor, canonical/native binding validation, support identity, and pending targets. The actual profile-5 fixture verifies unchanged native/entity state and continued replay. Every historical replay checkpoint is also checked against its profile-5 hash after removing only the new descriptor and envelope version. Platform-free native worlds retain physics profile character.1; owned-platform worlds use character-platforms.1. See platform lifecycle and persistence limits.
Profile 6 now migrates explicitly to profile 7 by adding the PlatformRoute descriptor while retaining all earlier descriptors and values. Profiles 1–5 add both platform descriptors as needed. The genuine profile-6 fixture includes a supported rider and pending native platform target; its next native boundary remains identical after migration. Profile 7 adds authored route time and phase/pose validation for world format 4. Old profiles cannot claim that new content format. All 426 historical platform-free boundaries and 26 restores remain checked against both profile-5 and profile-6 hashes after catalog/envelope conversion. The new route fixture independently pins 420 moving-platform boundaries and seven restores. See authored routes for phase, obstruction and save limits.
#Validation and native restoration
The envelope is limited to 16 MiB of canonical UTF-8 JSON; the native snapshot is limited to 8 MiB before canonical base64 encoding. Core fields are closed and bounded. Validation checks integrity, content/catalog identity, input shape, tick agreement, event cursors/lifetimes, complete ECS schemas and relationships, native player/door bindings, and agreement between native player state, simulation state and player components. Native worlds prepared for a rejected candidate are disposed.
SHA-256 detects corruption; it is not an authorization signature. This is a trusted same-profile checkpoint API, not an endpoint for importing arbitrary untrusted Rapier binaries. Native decoder fuzzing, authenticated remote envelopes and isolated untrusted import remain later recovery/security work.
Rapier numeric handles are opaque float64 bit patterns: index 1 can appear as 5e-324. The adapter stores them as 16 lowercase hex digits and checks membership in the native sets. These are backend-local bindings, never authored or network entity IDs.
The pinned 0.21.0 source's collider deserialization cache looks up an absent native parent through the body table and can report body zero for a static collider. The adapter checks colliders.raw.coParent as the authoritative binding instead of trusting that restored JS cache. It also explicitly frees both the temporary serialization pipeline and the consumed native deserialization wrapper. These raw calls are confined to the pinned physics adapter and need review when upgrading Rapier. Tests cover restored static door bindings and the player's actual native parent.
#Replay evidence
The independent Engine Zoo checkpoint.5 fixture uses the real Simulation, ECS, scheduler, event stream and pinned physics backend. Its four cases cover:
- Jump held through landing, wall collision, stale input expiry, paused boundaries, pending dialogue, consumed events and expiry.
- Opening and closing a physical door across restores.
- Outbound and return travel between two places, including nonzero starting ticks and destination facing.
- Ticks and input sequences crossing 2^53, with pending/expired events and continued replay after restoration.
Each case runs uninterrupted and again with a full JSON checkpoint restore every 17 boundaries. Every complete sealed checkpoint must match, including native physics bytes. The fixture records 426 boundaries and 26 restores; its golden evidence has digest e6b325c23fb6d93dc9e1853893d949af7eedb3a35ec45fedf64a7d012cdf9817.
The Engine Observatory's Simulation checkpoint replay check runs the same fixture in an isolated worker. It checks the pinned digest, and Playwright compares the entire returned report with a fresh Node run. Cancellation, worker exceptions, a stalled worker deadline and missing-worker configurations release ownership and report their outcome. The worker has a 30-second deadline and only loads when diagnostics run; no room admission or persistent save is involved.
This establishes exact agreement for the named fixture on the tested Node/Chromium pair. It is not general cross-platform physics determinism, a complete slope/platform/CCD corpus, or proof of future behavior modules. Separate headless tests exercise the actual Darkwind collision world, pending input/dialogue, corruption rejection and native body sleep/wake. Complete physics coverage and physical reference devices remain open.
#Verification and measurement
Run npm test, npm run check, npm run validate, npm run build and npm run test:browser. npm run bench:checkpoint -- OUTPUT.json measures capture and independent restore of an airborne player in the actual Darkwind world, including hashing, content checks and JSON copying. It records source identity, profile, payload sizes and timing percentiles. Retained allocation counters or short timing runs do not establish native heap stability or network/durable recovery objectives.
Historical profile-1 clean-source acceptance at e18fdb7f13351fa9fe50c9a3e4f1d90a4b6b95fe is preserved in the 18 browser scenarios, capability report and checkpoint benchmark. On the recorded Intel Core Ultra X7 358H / Node 22.23.4 machine, 30 samples after five warmups measured 13.7525 ms capture p95 and 21.4626 ms restore p95, for a 20,277-byte JSON envelope with 9,127 native physics bytes. Content hashing is included in each capture; this cost is not charged to an ordinary simulation tick. These short measurements do not establish whole-process memory stability or larger-world capacity.
Profile-2 clean-source acceptance is recorded in the 19-scenario browser report, capability report and checkpoint benchmark. See the migration contract for legacy precision and browser save retention.
Profile-3 clean-source acceptance covers 129 headless checks and 22 browser scenarios. The checkpoint benchmark, source cbb9ec34f56b0d5d868dc99c2fdf21d6ff6144ef, includes all 1,481 canonical entities and the compiled signature. On the recorded machine, five warmups and 30 samples measured 36.778 ms capture p95 and 315.186 ms restore p95; JSON grew to 950,285 bytes, while native physics remained 9,127 bytes. This exposes the cost of full authored-row snapshots and reconstructing a complete runtime. Capture exceeds a 30 Hz tick budget and must not be scheduled as ordinary per-tick work. Off-frame checkpoint scheduling, reuse of verified compiled content and content/state separation need further work before frequent remote snapshots. The player's lightweight local save remains format 2 and does not serialize this full checkpoint.