Replay run start¶
The native game has one shared reset/startup path and a quest-specific second prelude. The current replay format records the exact state needed at our chosen run boundary instead of trying to reconstruct earlier menu history.
For original captures that boundary is:
- the CRT RNG state immediately before the run's first terrain draw
- the creature-pool residue present at that point
- the mode, quest level, complete status blob, retry/hardcore/detail/violence settings, tick rate, and world size
The session's most recent crt_srand argument is not sufficient. Menus and
earlier startup work may already have consumed draws before gameplay begins.
Frida therefore records rng_state_before_bootstrap,
rng_state_after_bootstrap, and rng_bootstrap_calls. Finalization requires
the complete boundary to form an exact LCG chain and stores the before-state as
ReplayHeader.seed. Original captures always set
ReplayHeader.preserve_bugs=true; run settings are copied exactly instead of
falling back to rewrite defaults.
Native startup shape¶
Entering gameplay calls gameplay_reset_state() before branching by mode. That
shared path resets gameplay structures, consumes RNG, and calls
terrain_generate_random().
Survival and rush continue from that generic terrain. Quest mode then calls
quest_start_selected(), which performs additional resets and RNG work,
generates quest terrain, equips the start weapon, and builds the quest spawn
script.
The architecture is therefore:
flowchart TD
A["gameplay_reset_state()"] --> B["Shared reset and RNG work"]
B --> C["terrain_generate_random()"]
C --> D{"mode"}
D -->|"survival / rush"| E["Run continues"]
D -->|"quest"| F["quest_start_selected()"]
F --> G["Quest reset, RNG, terrain, and spawn setup"]
G --> E
Relevant native evidence is in
crimsonland.exe_decompiled.c:
gameplay_reset_state()around line 11543terrain_generate_random()around line 13987quest_start_selected()around line 32878- gameplay mode entry around line 40832
Why the replay boundary is later¶
Replaying the whole process from a stale session seed would require recording and reproducing unrelated menu and startup draws. It would also hide the real question when a run diverges: whether the same run-setup state produces the same gameplay behavior.
The current boundary is the state latched just before the first run terrain
draw. For native captures, creature slots can still contain reset-relevant
residue at that point, so the raw run_start.pool_residue is copied to
ReplayHeader.initial_creature_pool. A port-recorded replay uses None and
starts from a fresh pool.
flowchart LR
A["Native startup and menu history"] --> B["Run-setup RNG latch"]
C["Captured creature-pool residue"] --> D["ReplayHeader"]
B --> D
E["Mode, status, and quest settings"] --> D
D --> F["Shared replay session builder"]
F --> G["Terrain and mode startup"]
G --> H["Tick 0"]
This preserves native state directly while keeping replay startup small and deterministic.
Current replay contract¶
CRD replay format 16 is the only supported version. A ReplayHeader includes
the run seed/state, mode, player count, status, quest settings, and optional
initial creature-pool residue.
The file envelope is exactly one zstd frame containing the typed msgpack replay;
raw msgpack, concatenated frames, trailing bytes, and invalid frame checksums are
rejected. Checkpoint sidecars use the same single-frame rule with checkpoint
format 5. Replay envelopes are capped at 65 MiB compressed and 64 MiB decoded;
checkpoint envelopes are capped at 257 MiB compressed and 256 MiB decoded in
both Python and Zig.
Every ReplayTick carries:
dt: the exact finite, non-negative f32 deltainputs: one f32-quantized packed input row per playerprelude: orderedgame_frame_rng_advance,perk_menu_open, andperk_pickoperations applied before simulationpostlude: orderedperk_menu_openoperations applied after simulation while tick RNG tracing remains activecommands: Typ-o commands applied as part of the tick
Frida capture format 22 writes the same five values in each raw tick's
channels.replay_step. Finalization uses that channel to build the CRD sidecar,
and CDT schema 15 preserves it for direct comparison with replay-recorded
traces.
There is no independent replay-input stream or inferred movement input.
replay_step is the single authority for what drove the tick.
Startup and tick evidence¶
The channels intentionally separate cause from effect:
replay_steprecords the time step, input intent, ordered prelude and postlude, and commandscheckpointprovides a compact deterministic state hash/input to fast localizationsim_staterecords player movement state includingheading,move_speed,move_phase,aim, andaim_headingentity_samplesrecords stable-UID pool staterng_streamrecords draw values and state transitionstiming_samplesties the native update boundary toreplay_step.dt
When movement diverges, compare replay_step first. Matching inputs with a
different sim_state point at integration or state-reset behavior; different
inputs point at capture or replay-driving data.
Latest-only policy¶
- CRD loaders require replay format 16.
- Frida finalization requires raw capture format 22.
- CDT readers require container 2 and schema 15.
- Unknown fields and incomplete lifecycle rows are rejected.
- Older throwaway artifacts are regenerated, not migrated.
This keeps startup semantics in one current implementation and prevents compatibility code from masking a parity difference.