A from-scratch World of Warcraft 1.12.1 client in Rust + Bevy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
59 lines
2.9 KiB
Rust
59 lines
2.9 KiB
Rust
//! Pins the selection-ring radius against the **pixel-measured reference** on real build-5875 assets.
|
||
//!
|
||
//! The reference client sizes a **living unit's** ground selection ring (draw `0x608e00`, sizer
|
||
//! `0x60aee0`) as:
|
||
//!
|
||
//! ```text
|
||
//! ring_radius = OBJECT_FIELD_SCALE_X × sqrt( 0.5 · sqrt(dx² + dy²) )
|
||
//! ```
|
||
//!
|
||
//! where `dx,dy` are the horizontal (X,Y) extents of the unit's **Stand** animation bounding box (the M2
|
||
//! `M2Sequence` CAaBox), Stand being animation **id 0** via `animationLookup[0]`. This was byte-traced
|
||
//! *and* Unicorn-emulated in wow-re (`system/object-layer/scratch/selection-ring-scale.md`) and
|
||
//! reproduces the reference apitrace's measured ring radii to ~1 mm. It is NOT the render bounding sphere
|
||
//! (`0xCC`) — that (`0x5d6fe0`, `[unit+0x2b0]`) is the *corpse* decal; the render sphere over-sized tall
|
||
//! humans and under-sized the squat chicken because it folds in height, whereas the nested-sqrt footprint
|
||
//! compresses range.
|
||
//!
|
||
//! [`M2Bounds::ring_footprint`] is the model-local (pre-scale) part; the world radius is it ×
|
||
//! `OBJECT_FIELD_SCALE_X`. The four creatures below were the ones captured in the reference trace, drawn
|
||
//! life-size (scale ≈ 1.0), so `ring_footprint` ≈ the measured world radius. Skips when the gitignored
|
||
//! client data isn't present.
|
||
|
||
use benilla_formats::{load_m2_bounds, open_chain};
|
||
|
||
#[test]
|
||
fn ring_footprint_matches_reference_pixels() {
|
||
let data = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../WoW/Data");
|
||
if !data.is_dir() {
|
||
eprintln!("skipping: vanilla client not present at {}", data.display());
|
||
return;
|
||
}
|
||
let mut chain = open_chain(&data).expect("open chain");
|
||
|
||
// (model, ring radius measured from the reference apitrace at scale 1.0). The Stand-box footprint
|
||
// must reproduce these; a header-render-box or render-sphere source does not.
|
||
let cases = [
|
||
("Creature\\Chicken\\Chicken.mdx", 0.572_f32),
|
||
("Character\\Human\\Female\\HumanFemale.mdx", 0.731),
|
||
("Character\\Human\\Male\\HumanMale.mdx", 0.841),
|
||
("Creature\\Horse\\Horse.mdx", 1.295),
|
||
];
|
||
|
||
for (path, measured) in cases {
|
||
let b = load_m2_bounds(&mut chain, path).unwrap_or_else(|e| panic!("bounds {path}: {e}"));
|
||
assert!(
|
||
(b.ring_footprint - measured).abs() < 0.01,
|
||
"{path}: ring_footprint {:.4} should reproduce the reference-measured {measured} (Stand-box \
|
||
sqrt(0.5·sqrt(dx²+dy²)))",
|
||
b.ring_footprint
|
||
);
|
||
// Guard against a regression to the (wrong) corpse-path render-sphere source: for all four it is
|
||
// far off (0.5×sphere = 0.33 / 1.19 / 1.39 / 1.96), so it can't sneak past the bound above.
|
||
assert!(
|
||
(b.ring_footprint - 0.5 * b.sphere_radius).abs() > 0.05,
|
||
"{path}: ring_footprint must not coincide with the corpse-path 0.5×renderSphere"
|
||
);
|
||
}
|
||
}
|