From 8a82cebae6f9cdfdcdc01708a7a15d67e259fc81 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 20 Aug 2026 19:09:00 +0700 Subject: [PATCH] the walking tax banded, and the minimap stops rewriting the world MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - a panning UI batch rides its Transform instead of rewriting its mesh, minimap tiles emit unclipped (the mask shader already zeroes the corners), and the blip layer's origin snaps to half a logical pixel — steady UI mesh writes while moving drop from ~183/s to 10–14/s, and three minimap quads stop arming change detection over ~40k world meshes - the classify twin GC comes off the despawn edge: a part despawn schedules the full walk behind a 2 s deadline instead of running it that frame — 12–22 full walks a second while walking, gone - the walking tax measured and banded at Goldshire: parked 10.7 → moving 14.1 ms/frame of process CPU, ~+2.5 after the two fixes above - WOW_PART_CHURN and WOW_MESH_HOLDERS count part spawn/despawn frames and name which entities hold a churning mesh; the capture rig's `at:` pin learns a facing Co-Authored-By: Claude Fable 5 --- crates/benilla-app/src/capture/probe_rig.rs | 27 ++++- crates/benilla-app/src/minimap/mod.rs | 18 ++- crates/benilla-app/src/perf/census.rs | 95 +++++++++++++++ crates/benilla-app/src/perf/mod.rs | 11 ++ crates/benilla-app/src/ui_pass.rs | 121 +++++++++++++++++++- crates/benilla-world/src/model_render.rs | 40 +++++-- 6 files changed, 289 insertions(+), 23 deletions(-) diff --git a/crates/benilla-app/src/capture/probe_rig.rs b/crates/benilla-app/src/capture/probe_rig.rs index be7078a8..a31a7ce4 100644 --- a/crates/benilla-app/src/capture/probe_rig.rs +++ b/crates/benilla-app/src/capture/probe_rig.rs @@ -54,7 +54,7 @@ //! | `gear:` | `.character premade gear ` | SEC_BASIC_ADMIN 4 | name or entry; levels up + equips | //! | `spec:` | `.character premade spec ` | SEC_BASIC_ADMIN 4 | resets talents, learns the tree | //! | `at:` | `.tele ` | SEC_TICKETMASTER 2 | 997 named rows in `game_tele` | -//! | `at:m,x,y,z` | `.go xyz x y z m` | SEC_TICKETMASTER 2 | | +//! | `at:m,x,y,z[,o]` | `.go xyz x y z m` (`.go xyzo` with `o`) | SEC_TICKETMASTER 2 | pin the facing when a motion probe walks | //! | `gm:on\|off` | `.gm on\|off` | SEC_GAMEMASTER 3 | see 0649 on why `off` matters | //! //! Probe accounts are gmlevel **6**, so every one of these lands. Pass `gear:?` (or `spec:?`) to @@ -506,15 +506,21 @@ fn build_steps(spec: &RigSpec, dead: bool) -> Vec { } if let Some(at) = &spec.at { steps.push(match parse_point(at) { - Some((map, x, y, z)) => format!(".go xyz {x} {y} {z} {map}"), + Some((map, x, y, z, Some(o))) => format!(".go xyzo {x} {y} {z} {o} {map}"), + Some((map, x, y, z, None)) => format!(".go xyz {x} {y} {z} {map}"), None => format!(".tele {at}"), }); } steps } -/// `map,x,y,z` → the `.go xyz` argument order (`x y z map`). Anything else is a `game_tele` name. -fn parse_point(at: &str) -> Option<(i32, f32, f32, f32)> { +/// `map,x,y,z[,o]` → `.go xyz x y z map`, or `.go xyzo x y z o map` when a facing is given +/// (VERIFIED vmangos `Chat.cpp:408`: `xyzo`, SEC_TICKETMASTER like `xyz`). Anything else is a +/// `game_tele` name. The facing is load-bearing exactly when a motion probe walks from the +/// pin: `W` follows the BODY's facing, and each probe character keeps whatever facing it last +/// had — an unpinned facing sent probe0 and probe4 down different routes from the same point, +/// which invalidated a cross-slot A/B before 1462's sitting caught it. +fn parse_point(at: &str) -> Option<(i32, f32, f32, f32, Option)> { let mut parts = at.split(',').map(str::trim); let map = parts.next()?.parse().ok()?; let (x, y, z) = ( @@ -522,7 +528,11 @@ fn parse_point(at: &str) -> Option<(i32, f32, f32, f32)> { parts.next()?.parse().ok()?, parts.next()?.parse().ok()?, ); - parts.next().is_none().then_some((map, x, y, z)) + let o = match parts.next() { + Some(o) => Some(o.parse().ok()?), + None => None, + }; + parts.next().is_none().then_some((map, x, y, z, o)) } fn rig_char_name(spec: &RigSpec) -> Option { @@ -743,9 +753,14 @@ mod tests { build_steps(&point, false), vec![".go xyz -1277.5 124 131.2 1"] ); + let faced = RigSpec::parse("at:0,-9399.03,10.41,59.83,0.0").unwrap(); + assert_eq!( + build_steps(&faced, false), + vec![".go xyzo -9399.03 10.41 59.83 0 0"] + ); assert_eq!(parse_point("ThunderBluff"), None); assert_eq!(parse_point("1,2,3"), None); // three numbers is not a map + point - assert_eq!(parse_point("1,2,3,4,5"), None); + assert_eq!(parse_point("1,2,3,4,5,6"), None); // and six is past even a faced point } #[test] diff --git a/crates/benilla-app/src/minimap/mod.rs b/crates/benilla-app/src/minimap/mod.rs index 24ee91a9..30240f90 100644 --- a/crates/benilla-app/src/minimap/mod.rs +++ b/crates/benilla-app/src/minimap/mod.rs @@ -517,7 +517,11 @@ fn emit_minimap( z_key: slot.z, texture: Some(handle.clone()), color: [tint[0], tint[1], tint[2], slot.alpha], - clip: Some(slot.rect), + // No CPU clip (1463): the mask shader already zeroes everything outside + // `mask_rect` (`ui_quad.wgsl`'s `inside` test), and clipping a PANNING tile + // re-cut its quad every frame — constant positions, churning UVs — which is + // exactly the shape the batcher's pan gate cannot ride on a `Transform`. + // Unclipped, the tile is a pure translation and never rewrites its mesh. mask: mask.clone(), ..default() }); @@ -552,6 +556,14 @@ fn emit_minimap( } let win = window.iter().next(); let cursor = win.and_then(|w| w.cursor_position()); + // The player's pan term, quantized to a half-logical-pixel grid (one device px at 2×): + // every blip offset — and the rim arrows' bearing — derives from `wx`/`wy`, so the whole + // blip layer steps together a few times a second instead of re-emitting sub-pixel-shifted + // quads every frame while walking (1463; a 0.07 px/frame slide on a 16 px icon is not a + // visible motion, but each slide rewrote the batch mesh and armed the world's + // `AssetChanged` scans). The blips' world positions stay exact — only the shared pan + // origin snaps. + let q = 0.5 / blip_px_per_yd; blips::BlipCtx { center, side, @@ -559,8 +571,8 @@ fn emit_minimap( radius_yd: (side * 0.5) / blip_px_per_yd, z: slot.z, alpha: slot.alpha, - wx, - wy, + wx: (wx / q).round() * q, + wy: (wy / q).round() * q, wz: wow[2], cursor, // The same point in UI space (y-up, ÷s through the 0582/0584 seam — the tooltip's diff --git a/crates/benilla-app/src/perf/census.rs b/crates/benilla-app/src/perf/census.rs index d1ee902d..737b7cba 100644 --- a/crates/benilla-app/src/perf/census.rs +++ b/crates/benilla-app/src/perf/census.rs @@ -295,6 +295,101 @@ pub(super) fn count_mesh_events( } } +/// `WOW_PART_CHURN=1`: per-second M2-part churn. `rm_frames` is the count of frames with ≥1 +/// `MeshMaterial3d` removal — exactly the predicate that promotes +/// `classify_water_side` to its full walk (0930's twin-GC mark), so a regime reading ~60 here +/// full-walks every frame. `mat_mod` counts `WowModelMaterial` asset Modified events — the +/// global asset-changed tick that wakes every `AssetChanged` scan. +pub(super) fn count_part_churn( + added: Query<(), Added>>, + mut removed: RemovedComponents>, + mut mat_events: MessageReader< + bevy::asset::AssetEvent, + >, + time: Res