- The voidwalker's eye glow vanished whenever the camera rose above eye level. Three investigations said depth; measuring the depth buffer inside the live quads said otherwise — the fragments reached the framebuffer every frame, and the body's own transparent batches painted over them. The real client draws a model's particles after that model's batches, always. Ours now does: every effect carries a draw-order rung sized to clear exactly its own model. - Widened to every effect family after asking "what else does this fix": ribbon trails (a wisp's streamers wove six batches deep into its own body and flickered as the head whipped) and 3-D model particles (Cone of Cold's shards). A census over all 9,691 models found 967 whose own body could paint over their effects; all take the rung now. - The skinning lane is ours end to end: every rig's joint matrices live in our shared GPU buffer, written only when a rig actually moved, and our shader reads them. The engine's lane rewrote the entire joint buffer every frame and swept every entity in the world for changed transforms — that machinery now runs against empty sets. Another 6.7 CPU ms off the worst reported dungeon spot, which now holds a flat 60 fps with p99 under 19 ms. - Getting around while developing: Ctrl+Cmd+G lands your character where the free-fly camera is, and Alt-clicking the world map teleports you there (both ride the server's own teleport, GM-gated). The frozen-body coordinates the panel used to copy while flying now follow the camera. - A stuck main thread now diagnoses itself: a watchdog samples our own stacks and prints where the time went, and a hung teardown names its last system instead of hanging the window. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
50 lines
2.2 KiB
Rust
50 lines
2.2 KiB
Rust
//! Print every render batch's **animated material alpha** across a named sequence — the factor the
|
||
//! renderer multiplies into the instance's render alpha (`A = instanceAlpha × colourAlpha × weight`).
|
||
//!
|
||
//! `cargo run -p benilla-formats --example batch_alpha -- 'Creature\Voidwalker\Voidwalker.m2' 0`
|
||
//!
|
||
//! The number that matters is whether a batch ever reads **exactly 1.0**: benilla swaps a faded
|
||
//! entity onto its `AlphaMode::Blend` twin whenever `alpha < 1.0`, and that twin forces depth-write
|
||
//! ON. A batch whose authored weight never reaches 1.0 therefore never returns to the steady
|
||
//! material — so a `no_depth_write` batch (M2 flag 0x10) starts writing depth it must never write.
|
||
|
||
fn main() -> anyhow::Result<()> {
|
||
let mut args = std::env::args().skip(1);
|
||
let virt = args
|
||
.next()
|
||
.ok_or_else(|| anyhow::anyhow!("usage: batch_alpha <m2 path> [sequence slot]"))?;
|
||
let slot: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
|
||
let data = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../WoW/Data");
|
||
let mut chain = benilla_formats::open_chain(&data)?;
|
||
let bytes = chain.read_file(&virt)?;
|
||
let subs = benilla_formats::parse_m2_render_submeshes(&bytes, "", &[])?;
|
||
println!("sequence slot {slot} — {} batches", subs.len());
|
||
for (i, s) in subs.iter().enumerate() {
|
||
let Some(a) = s.alpha_anim.as_ref() else {
|
||
println!(
|
||
" batch {i:2}: no alpha_anim (static 1.0) depth_write={}",
|
||
!s.no_depth_write
|
||
);
|
||
continue;
|
||
};
|
||
let samples: Vec<f32> = (0..24u16)
|
||
.map(|k| a.sample(Some(slot), 3.0 * f32::from(k) / 24.0))
|
||
.collect();
|
||
let lo = samples.iter().copied().fold(f32::MAX, f32::min);
|
||
let hi = samples.iter().copied().fold(f32::MIN, f32::max);
|
||
let ever_one = samples.iter().any(|&v| v >= 1.0);
|
||
println!(
|
||
" batch {i:2}: alpha {lo:.4}..{hi:.4} ever==1.0: {ever_one:<5} \
|
||
depth_write={:<5} additive={:<5} {}",
|
||
!s.no_depth_write,
|
||
s.additive,
|
||
if ever_one {
|
||
""
|
||
} else {
|
||
"<-- STUCK on the blend twin"
|
||
}
|
||
);
|
||
}
|
||
Ok(())
|
||
}
|