Files
benilla-octo/crates/benilla-formats/tests/selection_ring_radius.rs
T
SamandClaude Fable 5 693c26d9c4 held weapons pick, book images size to their art, the panel opens again
- a unit's pick geometry is every model chained to it — body, held items, mount — one candidate per root resolving to the same unit, which is how the reference registers its pick (it walks the attachment tree and registers each model); Naxxramas' floating weapons could only be selected by their name plate before (no body, no candidate), and every armed unit gains its gear as clickable surface (our own body 12 → 21 tested parts); the broad phase stays the body's sphere, so a very long polearm on a small body is the named residual
- the selection ring's writer has two branches and we had transcribed one: a model whose stand box is degenerate (zero extent in x and y) rings at the literal 1.2 × scale — 2.7 yd under a trigger creature — instead of `sqrt(0.5·sqrt(dx²+dy²))` = 0 clamped to an invented 0.05 floor; both invented fallbacks are gone
- an HTML book image with no width/height is its art's texel square (one texel = one FrameXML unit, bit-for-bit the `<AbsDimension>` converter), not the rest of the page — *A Treatise on Military Ranks* drew its crest twice too big and stretched to the page's aspect; a texture-size oracle answers off the same decoder the renderer draws with, and an unfloated height-less `<IMG>` reserves its art's height in the flow; 797 shipped textures swept, exactly two zero-size axes move and neither is visible
- the dev debug panel killed the client on open with "Attachments have differing sample counts": Bevy's 2-D depth texture is keyed on the render target alone, so two Camera2ds on one window at different MSAA counts cannot both be valid, and the egui overlay camera had silently kept the default 4× after the player-UI camera went to Off; it now says `Msaa::Off`, and a preflight check reports any two 2-D cameras that disagree on a shared target, every run

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 17:16:04 +07:00

86 lines
4.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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 = benilla_formats::wow_data_or_skip!();
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"
);
}
}
/// The sizer's **other** branch: a box whose X and Y extents are both exactly zero takes the
/// literal 1.2 (`0x60af4f..0x60af67`, `0x3f99999a`) and never reaches the formula.
///
/// wow-re recorded this branch as one that "never fires for real creatures" — true of the four
/// life-size units it measured, false of the whole trigger-creature family. `InvisibleStalker`
/// authors **all 135** of its sequence boxes at zero, so 1.2 is its ring, and the Naxxramas weapon
/// mobs (an `InvisibleStalker` body holding a visible axe, display 15294 at scale 2.25 ⇒ a 2.7 yd
/// ring) are exactly where a player meets it. We computed `sqrt(0.5·sqrt(0))` = 0 and drew a ring
/// the width of a coin — the director's A/B against the reference is what caught it. Decision 1658.
#[test]
fn a_degenerate_stand_box_rings_at_the_reference_constant() {
let data = benilla_formats::wow_data_or_skip!();
let mut chain = open_chain(&data).expect("open chain");
let path = "Creature\\InvisibleStalker\\InvisibleStalker.mdx";
let b = load_m2_bounds(&mut chain, path).unwrap_or_else(|e| panic!("bounds {path}: {e}"));
// The premise: this model really is the degenerate case, on the header box and the Stand box
// alike (0 vertices ⇒ nothing to bound). If Blizzard had authored a box here the constant
// below would be the wrong answer, so pin the input, not just the output.
assert_eq!(
(b.bbox_min, b.bbox_max, b.sphere_radius),
([0.0; 3], [0.0; 3], 0.0),
"{path}: expected an entirely unauthored bound"
);
assert_eq!(
b.ring_footprint,
benilla_formats::DEGENERATE_RING_FOOTPRINT,
"{path}: a zero-extent box takes the writer's 1.2, not the formula's 0"
);
}