Delta Time Parity Reference¶
This page is the source of truth for delta-time semantics used by Python/Zig rewrites and parity tooling.
Runtime timing model overview¶
frame_dt: per-tick seconds-domain delta (float/f32) used for simulation math.frame_dt_ms: per-tick integer cadence (i32) used by integer timer/cooldown logic.- In native flow, both are observed across the gameplay frame path; integer cadence is derived from second-domain dt via
__ftol-equivalent truncation. - Rewrite contract: store canonical timing in seconds, derive integer cadence only through helper conversion.
Mutation timeline within one gameplay tick¶
- Outer-loop
REFLEX_BOOSTEDscaling applies first. gameplay_update_and_renderapplies gameplay-pass scaling and re-derivesframe_dt_ms.player_updateremaps to player-local dt and restores on exit.- Function-end restore returns global timing state to expected post-tick values.
- Zeroing/gating paths may force zero dt under specific pause/slow/guard conditions.
Ordering is parity-critical: scaling, remap, integer re-derive, and restore must occur in the same sequence.
Decompile reference anchors¶
analysis/binary_ninja/raw/crimsonland.exe.bndb_hlil.txt:81336shows the__ftolcontrol-word save/restore sequence.analysis/binary_ninja/raw/crimsonland.exe.bndb_hlil.txt:81342shows the round-control override (| 0x0c) used for trunc/chop behavior before conversion.analysis/ghidra/raw/crimsonland.exe_decompiled.c:6742shows an explicit gameplay zero-gate write where bothframe_dt_msandframe_dtare forced to zero.
Conversion contract (__ftol)¶
- Crimsonland parity requires truncation/chop semantics (round toward zero), not bankers rounding and not nearest integer.
- Tie examples:
+0.5 -> 0+2.5 -> 2-1.5 -> -1- Canonical helper for cadence conversion:
ftol_ms_i32(dt_seconds)where milliseconds arefloat32(dt_seconds * 1000.0)before truncation.
Equivalent forms:
# Python parity helper shape
def ftol_ms_i32(dt_seconds: float) -> int:
return int(float(f32(dt_seconds * 1000.0)))
// Zig parity helper shape
fn ftolMsI32(dt_seconds: f32) i32 {
return @intFromFloat(dt_seconds * 1000.0);
}
// C++ parity intent
int ftol_ms_i32(float dt_seconds) {
return (int)(dt_seconds * 1000.0f); // truncates toward zero
}
Units and field semantics for rewrite¶
dt: canonical per-tick seconds-domain value.dt_sim: simulation-domain dt after active gates/scales.dt_player_local: player-local dt used inside player update/remap windows.dt_ms_i32: integer cadence derived fromdtviaftol_ms_i32().dt_sim_ms_i32: integer cadence derived fromdt_simviaftol_ms_i32().- Replay rows:
Replay.dtis required and length-matched toinputs.*_ms_i32values are derived on load/use, not stored as authoritative rows.
Worked tick timeline example¶
Example tick with concrete numbers and call-order:
- Outer frame loop starts from nominal
1/60 = 0.016666668. - If
REFLEX_BOOSTEDouter scaling is active, apply* 0.899999976first: dt_entry = f32(0.016666668 * 0.899999976) = 0.015- At
gameplay_update_and_renderentry, derive entry cadence: dt = 0.015dt_ms_i32 = ftol_ms_i32(0.015) = 15- With
time_scale_active_entry=trueandreflex_boost_timer=0.5: time_scale_factor = f32((1.0 - 0.5) * 0.699999988 + 0.300000012) = 0.65dt_sim = f32(0.015 * 0.65) = 0.00975dt_sim_ms_i32 = ftol_ms_i32(0.00975) = 9- Player-local remap uses:
dt_player_local = f32((0.600000024 / 0.65) * 0.00975) = 0.009000001- If zero-gate triggers this tick:
- keep entry cadence (
dt=0.015,dt_ms_i32=15) - force sim cadence to zero (
dt_sim=0.0,dt_sim_ms_i32=0)
Consumer map¶
Seconds-domain consumers:
- Movement and kinematic integration.
- Physics-style position/velocity progression.
- Continuous interpolation-style logic.
Integer-ms cadence consumers:
- Survival/rush/quest timers and wave cadence.
- Cooldowns, durations, and countdown-style gameplay gates.
- Legacy timer codepaths expecting integer elapsed deltas.
Replay/capture/debug semantics¶
- Replay records per-tick
dtas authoritative timing input for deterministic reruns. - Capture may include sub-tick
timing_samplesrows to preserve phase-level timing evidence. - Finalize must preserve timing evidence into trace channels (not drop/flatten away).
- Diff/bisect must compare
timing_samplesand report first timing-phase mismatch when present.
Porting pitfalls and invariants¶
Common drift causes:
- Mixing truncation and rounding rules across systems.
- Duplicated local dt-to-ms derivations with inconsistent semantics.
- Missing or reordered restore steps around player-local remap.
- Hidden fallback branches that ignore replay dt rows.
Required invariants:
run_replayandrun_replay_infoconsume equivalent replay timing rows.- Replay codecs reject missing/legacy timing rows for current format version.
- Trace/finalize readers reject unsupported schema versions (no silent compatibility mode).
- Parity-critical dt-to-ms integer derivation uses only
ftol_ms_i32. - Recorder-produced replays always emit finite, non-negative
Replay.dtrows and row count equals input ticks.