- 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>
160 lines
6.1 KiB
Rust
160 lines
6.1 KiB
Rust
//! Difftest the **billboard-card lit-face** shape (decision 0788) against real content: which
|
||
//! billboard batches are authored back-to-front against the law's `+X`-at-the-viewer, and — the
|
||
//! load-bearing half — which must NOT be touched.
|
||
//!
|
||
//! `RenderSubmesh::billboard_card_faces_away` decides whether a consumer turns a card's normals
|
||
//! round so it is lit off the face it presents. Get it wrong in the permissive direction and 3-D
|
||
//! billboard geometry loses its shading; get it wrong in the strict direction and the cards go on
|
||
//! swinging warm/cool with the camera. Both sides are pinned here on the shipped assets. Skips
|
||
//! when the client isn't present.
|
||
|
||
use benilla_formats::{load_m2_mesh, open_chain, BillboardKind};
|
||
|
||
/// The hanging shop sign — the report this shape came from. Its two "chains" are not chain geometry
|
||
/// at all but a pair of 4-vert lock-Z billboard cards on a tiled chain texture, each authored with
|
||
/// its whole plane's normal on the −X side, i.e. facing away from the viewer the law points them at.
|
||
/// The sign body is ordinary geometry and must stay untouched.
|
||
#[test]
|
||
fn the_shop_signs_chain_cards_face_away_and_its_body_is_untouched() {
|
||
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\\Human\\Passive Doodads\\Signs\\CheeseShop01.m2",
|
||
)
|
||
.expect("load CheeseShop01");
|
||
|
||
let cards: Vec<_> = subs.iter().filter(|s| s.billboard.is_some()).collect();
|
||
assert_eq!(cards.len(), 2, "the sign hangs on two billboard cards");
|
||
for c in &cards {
|
||
let bb = c.billboard.as_ref().unwrap();
|
||
assert_eq!(
|
||
bb.kind,
|
||
BillboardKind::LockZ,
|
||
"a hanging chain card spins about model up (bone flag 0x40)"
|
||
);
|
||
assert_eq!(c.positions.len(), 4, "a card is one quad");
|
||
assert!(
|
||
c.two_sided,
|
||
"the card is two-sided (material 0x04), so the reference draws the back we see"
|
||
);
|
||
assert!(
|
||
c.billboard_card_faces_away(),
|
||
"the card's plane normal is authored on the −X side: lit off the face it presents"
|
||
);
|
||
}
|
||
assert!(
|
||
subs.iter()
|
||
.any(|s| s.billboard.is_none() && !s.billboard_card_faces_away()),
|
||
"the sign body is not a billboard and is never re-normalled"
|
||
);
|
||
}
|
||
|
||
/// The negative that matters: the questgiver `?` marker is a lock-Z billboard too, but its geometry
|
||
/// is 3-D (hundreds of verts, normals pointing every way round it). A rule that flipped every −X
|
||
/// normal on a billboard batch would gut its shading, so the shape demands ONE plane.
|
||
#[test]
|
||
fn the_questgiver_marker_is_3d_billboard_geometry_not_a_card() {
|
||
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, "Interface\\Buttons\\TalkToMeQuestionMark.m2")
|
||
.expect("load TalkToMeQuestionMark");
|
||
|
||
let billboards: Vec<_> = subs.iter().filter(|s| s.billboard.is_some()).collect();
|
||
assert!(
|
||
!billboards.is_empty(),
|
||
"the marker rides a billboard bone (it faces the camera)"
|
||
);
|
||
for b in &billboards {
|
||
assert!(
|
||
b.positions.len() > 4,
|
||
"3-D geometry, not a quad — got {} verts",
|
||
b.positions.len()
|
||
);
|
||
assert!(
|
||
!b.billboard_card_faces_away(),
|
||
"3-D billboard geometry is not a flat card: its normals must be left alone"
|
||
);
|
||
}
|
||
}
|
||
|
||
/// The shape's remaining arms, on synthetic normals (no assets needed): an EDGE-ON card has no
|
||
/// facing to correct, the already-correct majority is untouched, degenerate normals never flip,
|
||
/// and a non-billboard batch is out of scope however it is normalled.
|
||
#[test]
|
||
fn the_shape_ignores_edge_on_correct_and_degenerate_batches() {
|
||
use benilla_formats::{Billboard, ModelBlend, RenderSubmesh};
|
||
|
||
let card = |normals: Vec<[f32; 3]>, billboard: bool| RenderSubmesh {
|
||
positions: vec![[0.0; 3]; normals.len()],
|
||
normals,
|
||
uvs: vec![[0.0; 2]; 0],
|
||
indices: vec![],
|
||
texture: None,
|
||
skin_slot: None,
|
||
geoset_id: 0,
|
||
char_slot: None,
|
||
blend: ModelBlend::Opaque,
|
||
wrap_x: true,
|
||
wrap_y: true,
|
||
two_sided: true,
|
||
joints: vec![],
|
||
weights: vec![],
|
||
vertex_colors: vec![],
|
||
interior: false,
|
||
emissive: false,
|
||
sidn: None,
|
||
window: false,
|
||
additive: false,
|
||
no_depth_write: false,
|
||
no_depth_test: false,
|
||
fog_policy: benilla_formats::FogPolicy::Scene,
|
||
billboard: billboard.then(|| Billboard {
|
||
bone: 0,
|
||
pivot: [0.0; 3],
|
||
kind: BillboardKind::LockZ,
|
||
scale_anim: None,
|
||
seq_translations: vec![],
|
||
}),
|
||
welded_billboard: false,
|
||
alpha_anim: None,
|
||
uv_anim: None,
|
||
uv_seq: None,
|
||
rgb_anim: None,
|
||
rgb_seq: None,
|
||
wmo_batch: None,
|
||
env_map: false,
|
||
section: None,
|
||
};
|
||
|
||
assert!(
|
||
card(vec![[-1.0, 0.0, 0.0]; 4], true).billboard_card_faces_away(),
|
||
"the away-facing card (the control) flips"
|
||
);
|
||
// Soft/averaged authoring: unnormalised and a hair off plane — still one card.
|
||
assert!(
|
||
card(vec![[-0.98, 0.01, -0.02], [-1.02, -0.01, 0.01]], true).billboard_card_faces_away(),
|
||
"a soft-normal card is still one plane"
|
||
);
|
||
assert!(
|
||
!card(vec![[1.0, 0.0, 0.0]; 4], true).billboard_card_faces_away(),
|
||
"the majority already presents its lit face — untouched"
|
||
);
|
||
assert!(
|
||
!card(vec![[0.0, 0.0, -1.0]; 4], true).billboard_card_faces_away(),
|
||
"edge-on to the camera axis: no facing to correct"
|
||
);
|
||
assert!(
|
||
!card(vec![[0.0; 3]; 2], true).billboard_card_faces_away(),
|
||
"degenerate zero normals never flip"
|
||
);
|
||
assert!(
|
||
!card(vec![], true).billboard_card_faces_away(),
|
||
"no normals, no rule"
|
||
);
|
||
assert!(
|
||
!card(vec![[-1.0, 0.0, 0.0]; 4], false).billboard_card_faces_away(),
|
||
"an ordinary batch is out of scope however it is normalled"
|
||
);
|
||
}
|