Files
benilla-octo/crates/benilla-formats/tests/m2_billboard.rs
T
SamandClaude Fable 5 2ad0043523 benilla: initial public release
A from-scratch World of Warcraft 1.12.1 client in Rust + Bevy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:27:01 +07:00

80 lines
3.0 KiB
Rust

//! Difftest M2 billboard-bone detection against the real Lamppost: its glow card (GLOW32.BLP) rides a
//! spherical billboard bone (flag 0x08); its post does not. Skips when the client isn't present.
use std::path::PathBuf;
use benilla_formats::{load_m2_mesh, open_chain, BillboardKind};
fn vanilla_data_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../WoW/Data")
}
#[test]
fn lamppost_glow_is_spherical_billboard() {
let data = vanilla_data_dir();
if !data.is_dir() {
eprintln!("skipping: vanilla client not present at {}", data.display());
return;
}
let mut chain = open_chain(&data).expect("open vanilla patch chain");
let subs = load_m2_mesh(
&mut chain,
"World\\Azeroth\\Elwynn\\PassiveDoodads\\Lamppost\\Lamppost.m2",
)
.expect("load Lamppost");
// The glow card is a billboard; at least one batch (the post) is not.
let spherical = subs
.iter()
.filter_map(|s| s.billboard.as_ref())
.find(|b| b.kind == BillboardKind::Spherical);
let bb = spherical.expect("lamppost has a spherical-billboard batch (the glow card)");
assert!(
bb.pivot.iter().all(|c| c.is_finite()),
"billboard pivot is finite, got {:?}",
bb.pivot
);
assert!(
subs.iter().any(|s| s.billboard.is_none()),
"the lamppost post batch is not billboarded"
);
eprintln!("lamppost billboard pivot = {:?}", bb.pivot);
// The glow card's spherical billboard bone carries a global-sequence SCALE pulse (the "breathe"):
// 5 keys oscillating ~0.86..1.04 over the 1333 ms global sequence. This is what makes the lamppost
// glow pulse in the reference. Verified against the real Lamppost.m2 (bone[4] scale track, gseq 0).
let anim = bb
.scale_anim
.as_ref()
.expect("the lamppost glow card's billboard bone has a global-sequence scale pulse");
assert_eq!(anim.duration_ms, 1333, "global sequence loop length (ms)");
assert_eq!(anim.keys.len(), 5, "scale keyframe count");
assert!(anim.interp, "the scale track interpolates (interp != 0)");
// It is a genuine pulse: the sampled scale varies over the loop, and stays in a sane breathe range.
let samples: Vec<f32> = (0..anim.duration_ms)
.step_by(33)
.map(|t| anim.sample(t)[0])
.collect();
let (lo, hi) = samples
.iter()
.fold((f32::MAX, f32::MIN), |(lo, hi), &v| (lo.min(v), hi.max(v)));
assert!(
(0.8..0.95).contains(&lo) && (1.0..1.1).contains(&hi),
"scale pulse breathes within ~0.86..1.04, got {lo}..{hi}"
);
assert!(
hi - lo > 0.1,
"the pulse has visible amplitude, got {lo}..{hi}"
);
// Uniform scale (x == y == z) at an arbitrary phase — a billboard card scales evenly.
let s = anim.sample(700);
assert!(
(s[0] - s[1]).abs() < 1e-6 && (s[1] - s[2]).abs() < 1e-6,
"scale is uniform, got {s:?}"
);
eprintln!(
"lamppost glow pulse: {lo:.3}..{hi:.3} over {} ms",
anim.duration_ms
);
}