#[derive(Debug, Clone, PartialEq)]
pub struct Determinism {
pub seed: u64,
pub epoch_ms: i64,
pub timezone: String,
pub locale: String,
pub frame_ms: f64,
}
impl Default for Determinism {
fn default() -> Self {
Determinism {
seed: 42,
epoch_ms: 1_750_000_000_000,
timezone: "UTC".into(),
locale: "en-US".into(),
frame_ms: 1000.0 / 60.0,
}
}
}
impl Determinism {
pub fn with_seed(mut self, seed: u64) -> Self {
self.seed = seed;
self
}
pub fn with_locale(mut self, locale: impl Into<String>) -> Self {
self.locale = locale.into();
self
}
pub fn preamble(&self) -> String {
format!(
r#"(() => {{
let s = {seed} >>> 0;
Math.random = function () {{
s |= 0; s = (s + 0x6D2B79F5) | 0;
let t = Math.imul(s ^ (s >>> 15), 1 | s);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
}};
const T0 = {epoch};
const RealDate = Date;
function FrozenDate(...a) {{
if (!(this instanceof FrozenDate)) return new RealDate(T0).toString();
return a.length ? new RealDate(...a) : new RealDate(T0);
}}
FrozenDate.prototype = RealDate.prototype;
FrozenDate.now = () => T0;
FrozenDate.parse = RealDate.parse;
FrozenDate.UTC = RealDate.UTC;
Object.defineProperty(FrozenDate, 'name', {{ value: 'Date' }});
window.Date = FrozenDate;
let perf = 0;
performance.now = () => perf;
const FRAME = {frame};
window.requestAnimationFrame = (cb) => {{
perf += FRAME;
const t = perf;
return setTimeout(() => cb(t), 0);
}};
window.cancelAnimationFrame = (h) => clearTimeout(h);
// crypto.getRandomValues is a second entropy source that would otherwise
// leak nondeterminism into anything generating ids.
if (window.crypto && crypto.getRandomValues) {{
crypto.getRandomValues = (arr) => {{
for (let i = 0; i < arr.length; i++) {{
arr[i] = Math.floor(Math.random() * 256);
}}
return arr;
}};
}}
}})();"#,
seed = self.seed,
epoch = self.epoch_ms,
frame = self.frame_ms,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn preamble_embeds_its_settings() {
let d = Determinism::default().with_seed(7);
let js = d.preamble();
assert!(js.contains("let s = 7 >>> 0"));
assert!(js.contains("const T0 = 1750000000000"));
}
#[test]
fn preamble_overrides_every_known_entropy_source() {
let js = Determinism::default().preamble();
for sym in [
"Math.random",
"Date.now",
"performance.now",
"requestAnimationFrame",
"getRandomValues",
] {
assert!(js.contains(sym), "preamble does not override {sym}");
}
}
#[test]
fn default_epoch_is_pinned() {
assert_eq!(Determinism::default().epoch_ms, 1_750_000_000_000);
}
}