the missing bra, the robe's hem, and a doll that puts its arrow down

- the missing bra: the underwear section's CharSections row spends BOTH of its texture columns and the body compositor read only the first — the pelvis — so a bare-chested female composited panties and then bare skin above them; the second column is torso art authored on all eight female race rows and no male row, which is how the male-only regression test stayed green over the missing half, and the new test is byte-exact — the composite's torso tile must equal the shipped BLP's mip 0 verbatim
- underwear is the tile's FALLBACK, not an under-layer: the reference tests a prefix of a tile's equipment cells and skips the underwear blit entirely when any is occupied, while cells past the prefix stack on top of an underwear that was allowed to draw — so a shirt or chest suppresses the bra, trousers the panties, but a belt never hides the panties and a plain tabard never hides the bra; benilla blitted underwear first and equipment over it, which coincides on most gear and is still the wrong law
- a robe outranks footwear on the lower-leg tile: the equipment stacking table is a DEFAULT that two of the eight layer choosers overrule, and the robe bit lifts a chest to cell 4 over a boot's 3, so sandals stop repainting a robe's hem; and `_U` outranks the gendered file — the reference probes the universal name first and patches the gender letter in only on a miss (43 of 7944 shipped basenames carry both, and on those the gendered art is dead), which closes the bare-skin ring a shorter `_F` pant file left below the knee; `benilla-extract charatlas` prints the blits in order with the file each region resolved to, reading the same `equip_blits` law the compositor walks
- a model widget edits its copy the way the reference does: fifteen attach ids reset off every duplicate — blood, breath, the nameplates, the spell hands, and the nocked arrow — and the hand grips arm from attachment occupancy, where ours sat hardcoded open under a comment claiming the pane sheaths its weapons (it never did); the character doll stops drawing a hunter's arrow and closes the hand around the bow, and the booth bake log now names its attach ids (`at=[2,35] grip=[false,true]`), which is also the reset's retest readout

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-27 15:10:55 +07:00
co-authored by Claude Fable 5
parent de004383ce
commit e29cb90ebd
16 changed files with 1387 additions and 168 deletions
+3 -1
View File
@@ -859,8 +859,10 @@ mod quest_timer_tests;
#[cfg(test)]
mod durability_tests;
// `pub(crate)` for its `harness`/`push`/`row` helpers: `perf::hud`'s own test drives the readout
// through them rather than keeping a second copy of the XML-loading boilerplate.
#[cfg(test)]
mod world_state_tests;
pub(crate) mod world_state_tests;
#[cfg(test)]
mod screenshot_tests;
@@ -25,7 +25,7 @@ fn load_xml(s: &UiScript, file: &str) {
);
}
fn harness() -> UiScript {
pub(crate) fn harness() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
for f in [
@@ -42,7 +42,7 @@ fn harness() -> UiScript {
}
/// An Eastern Plaguelands-shaped row.
fn row(icon: &str, text: &str, tooltip: &str) -> WorldStateUiView {
pub(crate) fn row(icon: &str, text: &str, tooltip: &str) -> WorldStateUiView {
WorldStateUiView {
ui_state: 1,
text: text.into(),
@@ -76,7 +76,7 @@ fn textures(s: &mut UiScript) -> Vec<String> {
.collect()
}
fn push(s: &mut UiScript, rows: Vec<WorldStateUiView>) {
pub(crate) fn push(s: &mut UiScript, rows: Vec<WorldStateUiView>) {
s.set_world_state_ui(rows);
s.tick(0.0);
s.fire_event("UPDATE_WORLD_STATES", vec![]);
@@ -313,71 +313,6 @@ fn the_bindings_answer_the_reference_shape() {
);
}
/// The readout and the dev cost pill both want the top centre, and before this they drew straight
/// through each other (the pill is anchored 8 px down; the readout's first row starts at 15). The
/// pill asks the frame itself how far down the band is spoken for
/// ([`crate::perf::top_centre_claimed`]) and seats itself below that — so this pins the probe
/// against the shipped XML: nothing claimed while the readout is empty, and the readout's real
/// resolved bottom once it is up.
#[test]
fn the_readout_tells_the_dev_pill_how_much_of_the_top_it_uses() {
// NOT 768. The layout answers in WoW UI units — a screen that is always 768 units tall
// whatever the window is (decision 0582) — and the pill draws in window px, so a probe that
// subtracts one from the other is right only when the two happen to coincide. Feeding a
// window height that is NOT the virtual one is the whole point of this test: it is what the
// director's client does, and the first version of this probe put the pill back on top of the
// readout's third row there while passing every test at 768.
const SCREEN_H: f32 = 900.0;
let mut s = harness();
s.fire_event("PLAYER_ENTERING_WORLD", vec![ScriptValue::Str("".into())]);
s.resolve();
assert_eq!(
crate::perf::top_centre_claimed(&s, SCREEN_H),
0.0,
"hidden readout claims nothing — the pill keeps its usual seat"
);
push(
&mut s,
vec![
row(
"Interface\\WorldStateFrame\\AllianceTower",
"Towers Controlled: 3",
"Alliance Towers Controlled",
),
row(
"Interface\\WorldStateFrame\\HordeTower",
"Towers Controlled: 1",
"Horde Towers Controlled",
),
],
);
s.resolve();
let claimed = crate::perf::top_centre_claimed(&s, SCREEN_H);
assert!(
claimed > 8.0,
"two rows reach past the pill's own seat, so the pill must move: {claimed}"
);
// The frame's own geometry, read back the way the probe reads it: the container's top offset
// plus one row per pushed row. Asserted against the XML rather than restated as constants.
let expected: f32 = s
.eval::<f64>("return (20 + WORLD_STATE_ROW_HEIGHT * 2 + 15) / GetScreenHeight()")
.expect("the frame's own numbers") as f32
* SCREEN_H;
assert!(
(claimed - expected).abs() < 0.5,
"the claim is the readout's resolved bottom: {claimed} vs {expected}"
);
push(&mut s, Vec::new());
s.resolve();
assert_eq!(
crate::perf::top_centre_claimed(&s, SCREEN_H),
0.0,
"and it hands the band back when the scope empties"
);
}
/// The alpha bounding box of a shipped icon, in texel coordinates (`x0, y0, x1, y1`, y down).
fn ink_box(chain: &mut benilla_formats::Chain, path: &str) -> (f32, f32, f32, f32, f32, f32) {
let bytes = chain.read_file(path).expect("read icon");