- 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>
90 lines
3.9 KiB
Rust
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"
|
|
);
|
|
}
|