Files
benilla-octo/crates/benilla-formats/examples/emdump.rs
T
SamandClaude Fable 5 90efd4efad the voidwalker's eyes come back, and skinning becomes ours
- 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>
2026-07-27 19:38:40 +07:00

50 lines
2.3 KiB
Rust

//! `emdump <internal\path.m2>` — every particle emitter one model authors, one line each.
//!
//! The per-emitter counterpart to `benilla-extract partcensus`, which answers "which models in the
//! corpus carry emitters matching this flag mask?" but never what an individual emitter *is*. B16
//! needed the other direction — the voidwalker's eye glow is emitters 1 and 2 of four, bone 60/61,
//! flags `0x0001` (plain anchored), `Add`/`GLOWBALL.BLP` — and reading that off a hex dump by hand
//! is exactly the kind of asset fact that should be one command.
//!
//! The header line carries the model's **reach** (how far its own transparent-pass batches sort
//! from its origin — `m2_owner_reach`) and the `Transparent3d` draw-order rung it produces
//! (`owner_last_rung`): the rung is what keeps a model's effects drawing after its own transparent
//! batches, and it is sized from that number, so the two belong on screen together.
fn main() -> anyhow::Result<()> {
let virt = std::env::args().nth(1).unwrap();
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 dir = virt.rsplit_once('\\').map_or("", |(d, _)| d);
let subs = benilla_formats::parse_m2_render_submeshes(&bytes, dir, &[]).unwrap_or_default();
// The renderer's own bound and rung, not a re-derivation of them: a survey tool has to print
// what the renderer will actually do.
let reach = benilla_formats::m2_owner_reach(&subs);
let rung = benilla_formats::owner_last_rung(reach);
println!("{virt}: reach {reach:.3} yd -> draw-order rung {rung:.0}");
for (i, e) in benilla_formats::parse_m2_particle_emitters(&bytes)?
.iter()
.enumerate()
{
println!(
"emitter {i}: bone {:>3} flags {:#06x} model_space={} pos ({:.4},{:.4},{:.4}) blend {:?} \
shape {:?} speed {:.4}±{:.4} grav {:.4} life {:.4} tex {:?}",
e.bone,
e.flags,
e.model_space(),
e.position[0],
e.position[1],
e.position[2],
e.blend,
e.shape,
e.emission_speed,
e.speed_variation,
e.gravity,
e.lifespan,
e.texture,
);
}
Ok(())
}