Merge pull request #245 from storytold/fix/232-golden-libm-variant

Effects: the sim golden test accepts macOS 27's libm hash for Mr. Mercury (#232)
This commit is contained in:
BFlat 2026-10-08 13:36:30 +07:00 committed by GitHub
commit 06aa138c94
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 40 additions and 11 deletions

View File

@ -10,12 +10,16 @@
//! `f32` bits) and compared with the pinned hashes below: any change to the CPU output of these
//! effects, however small, fails the test.
//!
//! The hashes are exact `f32` bit patterns, so they depend on the platform's libm; they are
//! pinned for aarch64 macOS (where the gates run). Elsewhere the test only checks that every
//! case renders deterministically.
//! The hashes are exact `f32` bit patterns, so they depend on the platform's libm (`sin`, `cos`,
//! `powf`…); they are pinned for aarch64 macOS (where the gates run). Elsewhere the test only
//! checks that every case renders deterministically. A case whose frame differs between macOS
//! releases only through libm also accepts that release's hash ([`LIBM_VARIANTS`]).
//!
//! `SIM_GOLDEN_PRINT=1` prints the table (to re-pin after an intended change);
//! `SIM_GOLDEN_OUT=<dir>` also writes every frame's raw pixels (`f32` little-endian RGBA) there.
//! To tell a libm difference from a change in the code, render the cases with `SIM_GOLDEN_OUT`
//! here and at the commit that pinned the hash, and compare the frames: identical frames mean
//! the code didn't change, and the new hash goes into [`LIBM_VARIANTS`].
use effectcraft_effects::{Buf, EffectCtx, EffectEnv, EffectHost, LayerPixels, Params, apply, default_value, find};
use effectcraft_keyframe::Value;
@ -316,17 +320,41 @@ fn sim_effects_golden_hashes() {
// Deterministic: a second pass (warm simulation caches) renders the same frames.
assert_eq!(got, hashes(), "simulation effects render deterministically");
if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
let bad: Vec<String> = got
.iter()
.zip(GOLDEN)
.filter(|((n, h), (gn, gh))| n != gn || h != gh)
.map(|((n, h), (_, gh))| format!("{n}: 0x{h:016x} (pinned 0x{gh:016x})"))
.collect();
assert_eq!(got.len(), GOLDEN.len(), "case count");
assert!(bad.is_empty(), "CPU output changed:\n{}", bad.join("\n"));
let bad = mismatches(&got, GOLDEN, LIBM_VARIANTS);
assert!(bad.is_empty(), "CPU output changed (if it may be libm, see the module docs):\n{}", bad.join("\n"));
}
}
/// The cases whose hash is neither the pinned one nor one of the case's [`LIBM_VARIANTS`].
fn mismatches(got: &[(String, u64)], golden: &[(&str, u64)], variants: &[(&str, u64, &str)]) -> Vec<String> {
got.iter()
.zip(golden)
.filter(|((n, h), (gn, gh))| n != gn || (h != gh && !variants.iter().any(|(vn, vh, _)| vn == n && vh == h)))
.map(|((n, h), (_, gh))| format!("{n}: 0x{h:016x} (pinned 0x{gh:016x})"))
.collect()
}
/// A libm variant passes for its own case only (#232).
#[test]
fn libm_variants_are_accepted_for_their_case_only() {
let golden = [("a", 1), ("b", 2)];
let variants = [("a", 9, "another libm")];
let got = |a: u64, b: u64| vec![("a".to_string(), a), ("b".to_string(), b)];
assert!(mismatches(&got(1, 2), &golden, &variants).is_empty());
assert!(mismatches(&got(9, 2), &golden, &variants).is_empty(), "the variant");
assert_eq!(mismatches(&got(9, 9), &golden, &variants), ["b: 0x0000000000000009 (pinned 0x0000000000000002)"], "not another case's");
assert_eq!(mismatches(&got(3, 2), &golden, &variants).len(), 1);
assert!(LIBM_VARIANTS.iter().all(|(n, ..)| GOLDEN.iter().any(|(g, _)| g == n)), "every variant names a pinned case");
}
/// Hashes also accepted for a case: the same frame rendered by the same code with another macOS
/// release's libm (case, hash, where it was seen).
const LIBM_VARIANTS: &[(&str, u64, &str)] = &[
// #232: the pinning commit (ce6ece4) renders byte-identical frames there.
("ec.sim.ccmrmercury/1/32bpc/t2.2", 0x3823bd73a3cdab0d, "macOS 27.2 (26B5091g), rustc 1.98.0"),
];
/// Pinned hashes (aarch64 macOS).
const GOLDEN: &[(&str, u64)] = &[
("ec.sim.ccrainfall/0/8bpc/t0.5", 0xc9f1eb02dd83841c),

View File

@ -19,7 +19,8 @@ cargo xtask ci # fmt, clippy -D warnings, tests, layers, assets, was
which read the clock are declared time-dependent. `crates/effects/tests/sim_golden.rs` pins
exact pixel hashes of the simulation effects the GPU shares plans with (CC Rainfall … Card
Wipe) at several settings, times, bit depths and resolutions, so any change to their CPU output
fails (re-pin with `SIM_GOLDEN_PRINT=1` after an intended change; pinned for aarch64 macOS).
fails (re-pin with `SIM_GOLDEN_PRINT=1` after an intended change; pinned for aarch64 macOS,
with the hashes another macOS release's libm gives for unchanged code in `LIBM_VARIANTS`).
- **Export** (`crates/export/tests`): every format is encoded and decoded back, checking frame
count, size and pixels. When `ffmpeg`/`ffprobe` are installed they are used as an outside
check; they are never linked or shipped.