proofsheet_core/
determinism.rs1#[derive(Debug, Clone, PartialEq)]
20pub struct Determinism {
21 pub seed: u64,
23 pub epoch_ms: i64,
25 pub timezone: String,
27 pub locale: String,
29 pub frame_ms: f64,
31}
32
33impl Default for Determinism {
34 fn default() -> Self {
35 Determinism {
36 seed: 42,
37 epoch_ms: 1_750_000_000_000,
40 timezone: "UTC".into(),
41 locale: "en-US".into(),
42 frame_ms: 1000.0 / 60.0,
43 }
44 }
45}
46
47impl Determinism {
48 pub fn with_seed(mut self, seed: u64) -> Self {
49 self.seed = seed;
50 self
51 }
52
53 pub fn with_locale(mut self, locale: impl Into<String>) -> Self {
54 self.locale = locale.into();
55 self
56 }
57
58 pub fn preamble(&self) -> String {
60 format!(
63 r#"(() => {{
64 let s = {seed} >>> 0;
65 Math.random = function () {{
66 s |= 0; s = (s + 0x6D2B79F5) | 0;
67 let t = Math.imul(s ^ (s >>> 15), 1 | s);
68 t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
69 return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
70 }};
71 const T0 = {epoch};
72 const RealDate = Date;
73 function FrozenDate(...a) {{
74 if (!(this instanceof FrozenDate)) return new RealDate(T0).toString();
75 return a.length ? new RealDate(...a) : new RealDate(T0);
76 }}
77 FrozenDate.prototype = RealDate.prototype;
78 FrozenDate.now = () => T0;
79 FrozenDate.parse = RealDate.parse;
80 FrozenDate.UTC = RealDate.UTC;
81 Object.defineProperty(FrozenDate, 'name', {{ value: 'Date' }});
82 window.Date = FrozenDate;
83
84 let perf = 0;
85 performance.now = () => perf;
86
87 const FRAME = {frame};
88 window.requestAnimationFrame = (cb) => {{
89 perf += FRAME;
90 const t = perf;
91 return setTimeout(() => cb(t), 0);
92 }};
93 window.cancelAnimationFrame = (h) => clearTimeout(h);
94
95 // crypto.getRandomValues is a second entropy source that would otherwise
96 // leak nondeterminism into anything generating ids.
97 if (window.crypto && crypto.getRandomValues) {{
98 crypto.getRandomValues = (arr) => {{
99 for (let i = 0; i < arr.length; i++) {{
100 arr[i] = Math.floor(Math.random() * 256);
101 }}
102 return arr;
103 }};
104 }}
105}})();"#,
106 seed = self.seed,
107 epoch = self.epoch_ms,
108 frame = self.frame_ms,
109 )
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn preamble_embeds_its_settings() {
119 let d = Determinism::default().with_seed(7);
120 let js = d.preamble();
121 assert!(js.contains("let s = 7 >>> 0"));
122 assert!(js.contains("const T0 = 1750000000000"));
123 }
124
125 #[test]
126 fn preamble_overrides_every_known_entropy_source() {
127 let js = Determinism::default().preamble();
128 for sym in [
129 "Math.random",
130 "Date.now",
131 "performance.now",
132 "requestAnimationFrame",
133 "getRandomValues",
134 ] {
135 assert!(js.contains(sym), "preamble does not override {sym}");
136 }
137 }
138
139 #[test]
140 fn default_epoch_is_pinned() {
141 assert_eq!(Determinism::default().epoch_ms, 1_750_000_000_000);
145 }
146}