A from-scratch World of Warcraft 1.12.1 client in Rust + Bevy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
80 lines
3.0 KiB
Rust
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
|
|
);
|
|
}
|