From e61f0e5560390f45a68232971155b59c144f8523 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Thu, 8 Oct 2026 13:36:09 +0700 Subject: [PATCH] Effects: the sim golden test accepts macOS 27's libm hash for Mr. Mercury (#232) On macOS 27.2 (rustc 1.98) one of the 404 pinned simulation hashes, CC Mr. Mercury at non-default settings, 32 bpc, t = 2.2 s, came out different. The reporter rendered every case at the commit that pinned the hashes and at current main on that machine, and the frames were byte-identical. So the code didn't change: the platform's libm did (the hashes are exact f32 bits through sin, cos and powf). The test now also accepts listed per-case libm variants, with that hash as the first, and every other case stays exact. Its docs say how to tell a libm difference from a code change. Test: libm_variants_are_accepted_for_their_case_only (sim_golden; runs on every platform). Co-Authored-By: Claude Opus 5.5 --- crates/effects/tests/sim_golden.rs | 48 +++++++++++++++++++++++------- docs/testing.md | 3 +- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/crates/effects/tests/sim_golden.rs b/crates/effects/tests/sim_golden.rs index a6e4e5a2..2a3a7c72 100644 --- a/crates/effects/tests/sim_golden.rs +++ b/crates/effects/tests/sim_golden.rs @@ -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=` 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 = 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 { + 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), diff --git a/docs/testing.md b/docs/testing.md index cb90185f..4aa8514a 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -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.