Files
benilla-octo/crates/benilla-formats/tests/ribbons.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

75 lines
2.6 KiB
Rust

//! Difftest the vanilla M2 ribbon-emitter parser against real trail-carrying models (wow-5875-re
//! `ribbon-emitter-spec.md` field map). Skips (passes) when the client isn't present.
use std::path::PathBuf;
use benilla_formats::{open_chain, parse_m2_ribbon_emitters};
fn vanilla_data_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../WoW/Data")
}
#[test]
fn ribbon_records_match_real_bytes() {
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");
// The red wisp — three streamers trailing an animated creature.
let wisp =
parse_m2_ribbon_emitters(&chain.read_file("Creature\\WISP\\WispRed.m2").unwrap()).unwrap();
assert_eq!(wisp.len(), 3, "WispRed authors three ribbons");
for (i, r) in wisp.iter().enumerate() {
assert!(
r.edges_per_second > 0.0 && r.edges_per_second < 1000.0,
"ribbon {i} sane edge rate, got {}",
r.edges_per_second
);
assert!(
r.edge_lifetime >= 0.25,
"ribbon {i} lifetime respects the reference's 0.25 s load clamp"
);
assert!(
r.height_above.peak() + r.height_below.peak() > 0.0,
"ribbon {i} has a nonzero peak cross-section"
);
assert!(
r.texture.is_some(),
"ribbon {i} resolves a trail texture from the M2 textures table"
);
for &(_, a) in &r.alpha.keys {
assert!(
(0.0..=1.0).contains(&a),
"ribbon {i} fixed16 alpha decodes into 0..1, got {a}"
);
}
for &(_, c3) in &r.color.keys {
for c in c3 {
assert!((0.0..=1.5).contains(&c), "ribbon {i} color channel {c}");
}
}
}
// The Thunderblade — a weapon whose enchant trail rides the item root.
let blade = parse_m2_ribbon_emitters(
&chain
.read_file("ITEM\\ObjectComponents\\WEAPON\\Sword_1H_Thunderblade_A_01.m2")
.unwrap(),
)
.unwrap();
assert_eq!(blade.len(), 3, "Thunderblade authors three ribbons");
assert!(blade.iter().all(|r| r.texture.is_some()));
// A ribbon-less model parses to empty (the overwhelming majority).
let torch = parse_m2_ribbon_emitters(
&chain
.read_file("World\\Generic\\PassiveDoodads\\Lights\\Torch.m2")
.unwrap(),
)
.unwrap();
assert!(torch.is_empty(), "the torch has no ribbons");
}