Files
benilla-octo/crates/benilla-formats/tests/m2_shared_section.rs
T
SamandClaude Fable 5 db86a9c7ad the zone counters line up, and the ballista shine holds still
- the world-state readout — the top-centre counters a battleground or Silithus keeps up — takes the reference's real geometry: every icon those rows name is a sprite authored into the upper-left corner of a power-of-two canvas (`AllianceTower` is 16×16 of ink at (3,2) in a 32×32 file), nothing anywhere crops it, and the reference compensates geometrically — a 42×42 slot hung 6 units off the row's left edge with the label seated 10 above its centreline, taken as one system; our snug 20×20 box stripped exactly that compensation, so the art drew at half size floating away from its own `0/200` label — and an empty icon column now reserves its rect instead of sliding the text left, which straightens the readout's left edge
- the dynamic icon is a second slot, not a swap: `Icon` and `DynamicIcon` feed two different regions and shipped content never mixes them, so the flag is a 32×32 button off the row's right edge with an ADD-blend flash overlay pulsing 0.5 s in / 0.5 s out, lit only at `uiState == 2` — the flag-taken value; before, any non-zero state replaced the static art, which fires on the ordinary state 1 and hid a faction shield that should never have moved, and the pulse's alpha ramp is now pinned by a test, not just its art
- the dev cost pill yields the band: the readout is the game, the pill is scaffolding, and a stock 1.12 UI has no free corner to move house to — so it steps below whatever the readout draws, reading `WorldStateAlwaysUpFrame:GetBottom()` on its own 4 Hz cadence and only while it is drawing, rather than mirroring the frame's row pitch in Rust; the probe's cost is stated at the site (an edge read settles the layout — up to one graph solve, ~47 µs, four times a second)
- the Hearthglen ballista's bolt heads and shields stop flickering: a shine layer is a second batch over its base's own triangles, and ours split the pair across lanes — the base rode the retained lane's f64 CPU bake while the env-mapped, additive, no-depth-write shine stayed on the entity path's f32 GPU rotate, and a few ULPs between coplanar surfaces turns the depth test into a per-pixel coin flip re-rolled by every camera move; a batch now names the M2 skin section it draws, neither member of a shared section diverts into a consolidator (depths equal by construction), and a corpus census bounds the cost — 1,080 of 9,691 models draw a section more than once, 264 such sections across 221 `World\` doodads — with toggling pixels at the pin dropping 0.26% to 0.14%, matching the consolidators-off arm bit-for-bit

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 15:07:21 +07:00

90 lines
3.9 KiB
Rust

//! Difftest **`RenderSubmesh::section`** against real art — the fact the consolidator gate in
//! `terrain_stream::spawn::assemble` rests on: two batches naming one M2 skin section draw the
//! SAME triangles, so they are coplanar and must never be split across vertex-transform lanes.
//!
//! `Ballista.m2` is the positive, and it is the model the defect was reported on: its bolt head
//! (section 7) and its shields (section 10) each carry a base batch **plus** an
//! `ARMORREFLECT3` shine batch — additive, render flag `0x10` (no depth write), env-mapped. Every
//! one of those three facts is a consolidator exclusion, so before the gate the base rode the
//! retained/merged lane while the shine stayed on the entity path, and the pair's depths agreed
//! only to a few ULPs: a per-pixel coin flip that re-rolled with the camera.
//!
//! `HumanTentMedium.m2` is the discriminator — a doodad whose every section is drawn once, which a
//! parse that answered "shared" everywhere would fail.
//!
//! Skips (passes) when the client isn't present at `<repo>/WoW/Data`.
use benilla_formats::{load_m2_mesh, open_chain, RenderSubmesh};
/// The union of the facts the consolidators exclude on: `static_gx::divert` refuses env-mapped and
/// depth-flagged batches, the merge lane refuses additive ones. A batch answering `true` can never
/// leave the entity path, whatever its siblings do.
fn refusable(s: &RenderSubmesh) -> bool {
s.env_map || s.no_depth_write || s.no_depth_test || s.additive
}
#[test]
fn the_ballista_authors_a_coplanar_shine_over_its_bolt_head_and_shields() {
let data = benilla_formats::wow_data_or_skip!();
let mut chain = open_chain(&data).expect("open vanilla patch chain");
let subs = load_m2_mesh(
&mut chain,
"World\\Azeroth\\Elwynn\\PassiveDoodads\\Ballista\\Ballista.m2",
)
.expect("load Ballista");
// Every M2 batch names its section; the field exists to be compared, so `None` here would make
// the gate a silent no-op on the whole M2 population.
assert!(
subs.iter().all(|s| s.section.is_some()),
"every M2 batch carries its skin-section index"
);
// The two shared sections, and the split inside each: one plain base, one refusable shine.
let mut shared = 0;
for section in [7u16, 10] {
let batches: Vec<&RenderSubmesh> =
subs.iter().filter(|s| s.section == Some(section)).collect();
assert_eq!(
batches.len(),
2,
"Ballista section {section} is drawn by a base batch and a shine batch"
);
assert_eq!(
batches.iter().filter(|s| refusable(s)).count(),
1,
"exactly one of section {section}'s two batches is a consolidator exclusion — that \
asymmetry is the defect the assemble gate closes"
);
// The shine layer's own three facts, so a parse regression on any one of them red-bars
// here rather than silently re-opening the split.
let shine = batches.iter().find(|s| refusable(s)).unwrap();
assert!(shine.env_map, "the shine batch generates its texcoords");
assert!(shine.no_depth_write, "…carries render flag 0x10");
assert!(shine.additive, "…and blends additively (blend mode 4)");
shared += 1;
}
assert_eq!(shared, 2);
}
#[test]
fn a_plain_doodad_draws_every_section_once() {
let data = benilla_formats::wow_data_or_skip!();
let mut chain = open_chain(&data).expect("open vanilla patch chain");
let subs = load_m2_mesh(
&mut chain,
"World\\Generic\\Buildings\\HumanTentMedium\\HumanTentMedium.m2",
)
.expect("load HumanTentMedium");
let mut seen: Vec<Option<u16>> = subs.iter().map(|s| s.section).collect();
let before = seen.len();
seen.sort_unstable();
seen.dedup();
assert_eq!(
seen.len(),
before,
"the tent draws each of its sections exactly once — nothing here is coplanar with itself"
);
}