一键导入
determinism
Determinism in Rust Games guidance for Fortress Rollback. Use when Ensuring determinism, replacing HashMap, float handling, cross-platform determinism.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Determinism in Rust Games guidance for Fortress Rollback. Use when Ensuring determinism, replacing HashMap, float handling, cross-platform determinism.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Crate Publishing guidance for Fortress Rollback. Use when Publishing to crates.io, version bumps, release checklist.
Changelog Practices guidance for Fortress Rollback. Use when Writing CHANGELOG entries, deciding what to document.
Design Decision Log Pattern guidance for Fortress Rollback. Use when Architectural decisions, design alternatives, superseding prior choices.
Repository-wide engineering policy and project context for Fortress Rollback. Use when implementing, diagnosing, reviewing, testing, documenting, or releasing changes in this repository.
GitHub Actions Best Practices guidance for Fortress Rollback. Use when Writing GitHub Actions workflows, CI debugging, actionlint, caching.
Workspace Organization guidance for Fortress Rollback. Use when Organizing workspace, splitting crates, module structure decisions.
| name | determinism |
| description | Determinism in Rust Games guidance for Fortress Rollback. Use when Ensuring determinism, replacing HashMap, float handling, cross-platform determinism. |
Given identical inputs, the game must produce bit-identical outputs on every run, on every machine.
| Source | Problem | Solution |
|---|---|---|
HashMap / HashSet | Random iteration order | BTreeMap / BTreeSet |
f32::sin(), cos(), etc. | Platform-specific impls | libm or fixed-point |
rand::random() | System entropy | Seeded Pcg64 |
Instant::now() / SystemTime | Wall clock varies | Frame counters only |
| Thread execution order | Scheduler-dependent | Single-threaded simulation |
| Memory addresses | ASLR, allocator | Never use pointers in logic |
usize | 32 vs 64-bit | Use explicit u32/u64 |
| Source | Problem | Solution |
|---|---|---|
| Float precision | x87 vs SSE vs ARM | Control FPU or use fixed-point |
| Compiler optimizations | May reorder ops | Consistent compiler flags |
| FMA instruction | Different precision | Disable or ensure consistency |
| Denormals | Perf vs precision | Flush to zero |
| Non-Deterministic | Deterministic Alternative | Notes |
|---|---|---|
HashMap<K,V> | BTreeMap<K,V> | Keys must impl Ord |
HashSet<T> | BTreeSet<T> | Sorted iteration |
HashMap<K,V> | IndexMap<K,V> | Insertion-order; deterministic only if insertion order is |
HashMap iteration | Sort keys first | When HashMap needed for perf |
| Collection | Lookup | Insert | Deterministic |
|---|---|---|---|
HashMap | O(1) | O(1) | No |
BTreeMap | O(log n) | O(log n) | Yes (sorted) |
IndexMap | O(1) | O(1) | If insertions are |
HashMap + sort | O(1) | O(1) | Yes (at iteration) |
# Check what features are enabled (watch for compile-time-rng, const-random)
cargo tree -f "{p} {f}" | grep ahash
cargo tree -f "{p} {f}" | grep rand
# Safe configurations
ahash = { version = "0.8", default-features = false }
rand = { version = "0.8", default-features = false }
rand_pcg = "0.3"
IEEE-754 guarantees correct rounding only for +, -, *, /, sqrt. Everything else (sin, cos, exp, log, pow, etc.) is platform-dependent.
| Approach | Crate | Determinism | Performance | Use Case |
|---|---|---|---|---|
libm | libm 0.2 | Cross-platform | Moderate | Easiest drop-in |
| Fixed-point | fixed 1.29 | Guaranteed | Good | Physics, positions |
| CORDIC trig | cordic 0.1 | Guaranteed | Good | Fixed-point trig |
| Soft-float | softfloat-wrapper 0.3 | Maximum | Slow | Absolute portability |
| Kahan sum | accurate 0.4 | Stable | Good | Large summations |
# For Bevy projects
glam = { version = "0.29", features = ["libm"] }
use fixed::types::I32F32;
#[derive(Clone, Copy, PartialEq, Eq)]
struct Position { x: I32F32, y: I32F32 }
impl Position {
fn distance_squared(&self, other: &Self) -> I32F32 {
let dx = self.x - other.x;
let dy = self.y - other.y;
dx * dx + dy * dy
}
}
// Non-deterministic: reduction order varies
let sum: f64 = values.par_iter().sum();
// Deterministic: collect then sum sequentially
let values: Vec<f64> = values.par_iter().map(|x| expensive(x)).collect();
let sum: f64 = values.iter().sum();
// Force evaluation order with explicit parentheses
let result = ((a + b) + c) + d;
WASM provides stronger guarantees: IEEE 754 specified semantics, canonical NaN, little-endian, fully specified behavior.
// NaN canonicalization at boundaries
fn canonicalize_float(x: f32) -> f32 {
if x.is_nan() { f32::NAN } else { x }
}
Caveats: threading breaks determinism in WASM; host imports (time, random) are non-deterministic.
use rand_pcg::Pcg64;
use rand::SeedableRng;
struct GameState {
rng: Pcg64, // Must be saved/restored with state
}
impl GameState {
fn new(seed: u64) -> Self {
Self { rng: Pcg64::seed_from_u64(seed) }
}
}
| Library | Notes |
|---|---|
rand_pcg | Fast, portable, deterministic |
rand_chacha | Cryptographic, very portable |
Bevy queries do NOT guarantee iteration order. Always sort by a stable ID:
#[derive(Component, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Rollback(pub u32);
fn movement_system(mut query: Query<(&Rollback, &mut Position)>) {
let mut items: Vec<_> = query.iter_mut().collect();
items.sort_by_key(|(rb, _)| *rb);
for (_, mut pos) in items { /* deterministic */ }
}
// FORBIDDEN in game logic: Instant::now(), SystemTime, time.delta_seconds()
const TICK_RATE: f32 = 1.0 / 60.0;
#[derive(Resource, Clone)]
pub struct FrameCount(pub u32);
fn compute_checksum(state: &GameState) -> u64 {
let mut hasher = seahash::SeaHasher::new(); // Portable, deterministic
state.frame.hash(&mut hasher);
// Hash floats as bits
for player in &state.players {
player.position.x.to_bits().hash(&mut hasher);
player.position.y.to_bits().hash(&mut hasher);
}
hasher.finish()
}
Do NOT use DefaultHasher -- it is randomized.
start_synctest_session()| Bug | Fix |
|---|---|
| Float accumulation drift | Reconstruct from integers: pos = start + vel * (frame * TICK_RATE) |
| Uninitialized padding in hashing | Zero padding explicitly in new() |
| ECS query order | Always sort by Rollback ID |
const vs runtime libm | LLVM const-folding may differ from runtime |
BTreeMap/BTreeSet instead of HashMap/HashSetrand_pcg or rand_chacha), saved with statelibm enabled OR fixed-point mathahash using default-features = falseCargo.lock committed, toolchain pinned