Files
benilla-octo/crates/benilla-assets/src/anim_rng.rs
T
Sam SungandClaude Opus 5.5 d69d09dfce Comments in benilla-assets: one line of fact each, no history, no record numbers (#95)
Part of #41: the comments of `benilla-assets`, Rust and WGSL, rewritten to the rule in `docs/METHOD.md`: what the code does and the reference fact behind it, in a line. 3,872 comment lines become 1,568.

- The citations that were only a private record number go first, mechanically, each edit shown to be a pure removal.
- The rewrite corrects the comments that stated wrong facts: the alpha-key cutoff now cites the reference's per-blend-mode table (`0x85ad20`), the texture-filter note names the subsystems that ignore the global setting, a test doc said an exhausted variation roll returns the last variation (it returns the first), and about twenty more found on the way (renamed or deleted items, a shader flag the doc misread, docs sitting on the wrong function). "Deviation:" now marks only a choice with a reason; an unbuilt behaviour says so plainly.
- Verified: every file strips to identical code, and `cargo fmt --check` is clean. A second reader compared every file old against new, restored the facts the rewrite had dropped, and checked each new citation against the reference.

String literals are untouched; they are #92.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 16:27:58 +07:00

107 lines
4.0 KiB
Rust

//! The client's one `rand()` stream, the MSVC CRT LCG at `0x7400e5`, in this leaf crate because
//! the engine's placed doodads and the game's creature, GameObject and portrait-booth arms all
//! draw from it. The reference keeps one cell per thread (`[ptd+0x14]`) and runs every arm on the
//! main thread (the frame loop `0x420c00`); that interleaving de-syncs a stand of identical props,
//! so it must stay one stream.
//!
//! The reference seeds it with `srand(GetTickCount())` in CRT static init (`0x5d1c70`, the call
//! at `0x5d1c8b`), so each run rolls differently. Its exact sequence is out of reach: every other
//! draw moves the stream, FrameXML's `random()` and each particle emitter among them.
use bevy::prelude::Resource;
/// The shared `rand()` state, seeded once per session by [`Self::seed_for_session`].
#[derive(Resource)]
pub struct AnimRng(u32);
impl Default for AnimRng {
fn default() -> Self {
Self(1) // `_initptd 0x40aca8`: the CRT's per-thread default, before `srand` runs
}
}
impl AnimRng {
/// One `rand()` draw, in `[0, 32767]` (`0x7400ed`-`0x740101`).
pub fn draw(&mut self) -> u16 {
self.0 = self.0.wrapping_mul(214_013).wrapping_add(2_531_011);
((self.0 >> 16) & 0x7fff) as u16
}
/// The play window's replay count `R`, as in the reference's `windowHi = now + span·R`
/// (`0x712692`-`0x7126cd`). It draws even when the range cannot change `R`, as the reference
/// does; skipping the draw de-phases every later roll.
pub fn replay_count(&mut self, replay: (u32, u32)) -> u32 {
let (lo, hi) = replay;
let r = lo + ((u64::from(self.draw()) * u64::from(hi.saturating_sub(lo))) >> 15) as u32;
r.max(1)
}
/// The reference's `srand(GetTickCount())`: a wall-clock seed, once per process. Deviation: a
/// capture run (`deterministic`) keeps the default seed, so its frames reproduce.
pub fn seed_for_session(&mut self, deterministic: bool) {
if deterministic {
return;
}
// `GetTickCount` counts milliseconds since boot; wall-clock milliseconds stand in for it.
self.0 = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(1, |d| d.as_millis() as u32);
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Seed 1 gives the MSVC CRT's documented sequence.
#[test]
fn the_draw_is_the_crt_lcg() {
let mut rng = AnimRng::default();
let first: Vec<u16> = (0..6).map(|_| rng.draw()).collect();
assert_eq!(first, vec![41, 18_467, 6_334, 26_500, 19_169, 15_724]);
assert!(first.iter().all(|&r| r <= 0x7fff));
}
#[test]
fn a_zero_replay_pair_is_one_pass_and_still_advances_the_stream() {
let mut rng = AnimRng::default();
assert_eq!(rng.replay_count((0, 0)), 1);
let mut bare = AnimRng::default();
bare.draw();
assert_eq!(
rng.draw(),
bare.draw(),
"the (0,0) window drew exactly once"
);
}
#[test]
fn a_replay_range_scales_the_window() {
let mut rng = AnimRng::default();
// roll 41 over a 0..8 range: 41·8 >> 15 = 0, so the floor still applies.
assert_eq!(rng.replay_count((0, 8)), 1);
// A `min` above zero is the floor, whatever the roll.
let mut rng = AnimRng::default();
assert!(rng.replay_count((3, 3)) == 3);
// An inverted pair cannot underflow the subtraction.
let mut rng = AnimRng::default();
assert_eq!(rng.replay_count((2, 1)), 2);
}
#[test]
fn a_capture_keeps_the_fixed_seed_and_a_session_does_not() {
let mut capture = AnimRng::default();
capture.seed_for_session(true);
let mut fixed = AnimRng::default();
assert_eq!(capture.draw(), fixed.draw());
let mut live = AnimRng::default();
live.seed_for_session(false);
assert_ne!(
live.0,
AnimRng::default().0,
"a live session seeds off the wall clock"
);
}
}