From 6356bc8f435443507906398b2ec7aba0a22af57a Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 31 Aug 2026 20:20:41 +0700 Subject: [PATCH] Enter World stops freezing character select, and bags close by the law MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enter World stops freezing the character screen: from the click, the select image stayed on the glass ~100 ms because the loading cover could not draw until the state-flip frame — which cost 60 ms, 57 of it render+present: the world camera's first render of a 3000-entity world on cold pipelines, plus fifteen booth cameras waking on the very frame that owes the glass a cover; the glue now yields the glass on the click's own frame and the world camera (booths with it) holds off until the cover has presented — flip frame 60 → 43 ms and off the critical path — measured by the new `WOW_FRAME_PHASES` instrument, which stamps every phase of a frame and prints the render+present remainder no in-schedule stamp can see - switching to a character you haven't clicked before no longer flashes somebody else's body in their stage — the background swapped the frame the selection changed while the character waited for its whole assembly, so whichever won is what the screen showed (53 ms of the human standing in the orc's stage, and the reverse under a stage still loading); stage and character now commit as a PAIR, loads still starting on the click and an empty stage still swapping at once — on the same cold walk, eight selections make eight rebakes, every one the right body in the frame its stage lands, where before it was twelve with four of them wrong - the bank's six bag buttons repaint again — the stock UI repaints them only on `PLAYERBANKSLOTS_CHANGED`, which benilla fired for the vault's 24 slots alone, so a bag dropped in never appeared and one moved between slots stayed drawn in the one it left; the lock clear becomes its own system ordered ahead of BOTH feeds (resolve, then push, then fire), ending the stuck-grey icon — and `GetInventoryItemCount` is carved as not-the-stack-count: an EMPTY slot answers 1 (a sentinel, not a count), a container past the slot cutoff answers a literal 0 before any lookup (the whole answer to the stray digit on the bank's bag buttons), and an equipped bag answers the SUM of its contents gated on a DBC bit our class-11 guess missed twice — including the Soul Bag, so a warlock's shard count never showed - the bags close by the law: `BAG_CLOSED` is the only thing in all of FrameXML that hides an open bag window, and benilla never fired it, so a window outlived its bag in both bands (equipped and bank); the carve pins the condition — the bag changed AND the slot was occupied — so a swap fires BAG_CLOSED then BAG_UPDATE with the slot occupied throughout, an unequip fires it with the announce suppressed for exactly that transition, and two identical empty bags exchanged between slots still close both windows, because the diff is on the bag's own guid rather than the container state; `PLAYERBANKSLOTS_CHANGED` meanwhile turns out to carry NO arguments — a census of all 485 dispatcher call sites — the slot travels in how many times it fires, and every bank button repaints from its own slot id Co-Authored-By: Claude Fable 5 --- crates/benilla-app/src/char_select/mod.rs | 86 +++++ crates/benilla-app/src/char_select/screen.rs | 36 +++ crates/benilla-app/src/loading_screen.rs | 130 ++++++++ crates/benilla-app/src/net/apply.rs | 4 +- crates/benilla-app/src/net/apply/loot.rs | 8 +- crates/benilla-app/src/pending_item_ops.rs | 38 ++- crates/benilla-app/src/perf/mod.rs | 3 + crates/benilla-app/src/perf/phases.rs | 177 ++++++++++ crates/benilla-app/src/pipe_warm/mod.rs | 27 +- crates/benilla-app/src/player/setup.rs | 15 +- crates/benilla-app/src/portrait/glue_booth.rs | 202 ++++++++++-- crates/benilla-app/src/portrait/mod.rs | 17 +- crates/benilla-app/src/ui_char.rs | 301 ++++++++++++++++-- crates/benilla-app/src/ui_items/feed.rs | 291 +++++++++++++++-- crates/benilla-app/src/ui_items/mod.rs | 9 +- crates/benilla-app/src/ui_script/bag_tests.rs | 1 + .../src/ui_script/bagnon_render_tests.rs | 2 +- .../benilla-app/src/ui_script/bank_tests.rs | 123 ++++++- .../src/ui_script/character_tests.rs | 1 + crates/benilla-app/src/ui_script/lifecycle.rs | 39 ++- .../benilla-app/src/ui_script/quiver_tests.rs | 4 +- .../src/ui_script/tooltip_compare_tests.rs | 2 + .../src/ui_script/world_entry_tests.rs | 19 +- crates/benilla-formats/src/itemsubclass.rs | 9 +- .../messages/update_object/fields/player.rs | 12 + crates/benilla-ui/src/script/char_stats.rs | 205 +++++++++++- crates/benilla-ui/src/script/cursor/doll.rs | 4 + 27 files changed, 1609 insertions(+), 156 deletions(-) create mode 100644 crates/benilla-app/src/perf/phases.rs diff --git a/crates/benilla-app/src/char_select/mod.rs b/crates/benilla-app/src/char_select/mod.rs index b877ee10..c31d6082 100644 --- a/crates/benilla-app/src/char_select/mod.rs +++ b/crates/benilla-app/src/char_select/mod.rs @@ -127,6 +127,7 @@ impl Plugin for CharSelectPlugin { // Before the list refresh, and before `select_input` reads a click that // landed on the panel rather than the screen (decision 1196). debug_select_addons, + debug_select_walk, addons::drive_addons_panel, refresh::refresh_list, refresh::refresh_banner_and_buttons, @@ -142,6 +143,14 @@ impl Plugin for CharSelectPlugin { ) .chain() .after(benilla_world::schedule::WorldStage::Net), + ) + // AFTER the cover's own stage, so the raise it reads is this frame's — see the + // system's doc for why the glue must yield the glass the moment the cover goes up. + .add_systems( + Update, + screen::hide_under_world_cover + .after(benilla_world::schedule::WorldStage::Present) + .run_if(in_state(ClientState::CharSelect)), ); } } @@ -746,6 +755,83 @@ fn debug_logout_smoke( } } +/// The select-screen **walk** instrument (`WOW_CHARSELECT_WALK=[:]`) — +/// the thing a headless run had no way to do at all: *click a row*. It steps the selection one row +/// per `period_s` through the whole roster (the ref's arrow-key cycle, in code), starting from the +/// row the screen opened on, and logs each step with its elapsed stamp — so a booth/scene timeline +/// (`WOW_BOOTH_LOG=1`, `WOW_STREAM_TRACE`) is attributable to the selection that caused it. With +/// `enter_after_s`, it then presses **Enter World** that long after the last step, which is the +/// only headless way to land on the world-entry edge from a screen that was actually *used* +/// (`WOW_CHAR`'s fast path answers the first roster and never renders a second selection). +/// +/// Built for the two reports this file's booth feed owns — the cold-selection model swap and the +/// entry-edge stall — and kept, because every future "what does the screen do when you click" +/// question needs exactly this and nothing more. Inert without the env. +fn debug_select_walk( + mut roster: ResMut, + pick: Res, + time: Res