From e3f9b7f4000129ee8d42e293eabbd661d20d95b1 Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 16 Aug 2026 17:54:09 +0700 Subject: [PATCH] the bevy 0.19 excursion: graded 5.5 ms slower lane-for-lane, rolled back MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - the full bevy 0.19 / avian 0.7 / egui 0.35 port landed and rendered at visual parity, then profiling matched the lanes honestly: +5.5 ms against 0.18 in our scenes, priced to three named buckets — so it was reverted the same day, with the pins mapped for a future release - back on 0.18 the client is faster than 0.18 ever ran here, keeping both performance days' wins - what the hunt left behind: the material-animation scan visits only rows that can be registry keys, and a capture that settles on an all-black image now says EMPTY instead of grading it Co-Authored-By: Claude Fable 5 --- crates/benilla-app/src/capture/mod.rs | 17 +++ .../src/capture/probes/live_fps.rs | 18 +++ crates/benilla-app/src/char_select/mod.rs | 14 +- crates/benilla-app/src/login/mod.rs | 124 ++++-------------- crates/benilla-app/src/login/smoke.rs | 109 +++++++++++++++ .../benilla-world/src/doodad_anim/mat_anim.rs | 64 +++++++-- crates/benilla-world/src/doodad_anim/mod.rs | 4 +- crates/benilla-world/src/model_render.rs | 10 ++ .../src/terrain_stream/spawn/assemble.rs | 17 ++- crates/benilla-world/src/world_plugins.rs | 30 +++++ 10 files changed, 291 insertions(+), 116 deletions(-) create mode 100644 crates/benilla-app/src/login/smoke.rs diff --git a/crates/benilla-app/src/capture/mod.rs b/crates/benilla-app/src/capture/mod.rs index 1356f543..d9609359 100644 --- a/crates/benilla-app/src/capture/mod.rs +++ b/crates/benilla-app/src/capture/mod.rs @@ -861,6 +861,23 @@ fn drive_capture( watch.stable, ); } + // The emptiness tripwire (1373): stability proves the image stopped changing, not + // that it contains anything — a lane that renders nothing is perfectly stable, and + // the 1371 black era sailed through this gate without a word. The bytes are already + // in hand; color channels only, since alpha is opaque even on a black frame. + if watch.stable >= stable_frames() || capped { + if let Some(px) = watch.prev.as_deref() { + if px + .chunks_exact(4) + .all(|p| p[0] == 0 && p[1] == 0 && p[2] == 0) + { + error!( + "capture: settled image is EMPTY — every color channel is zero; the \ + scene rendered nothing (1373)" + ); + } + } + } if watch.stable < stable_frames() && !capped { Phase::Building(n + 1) } else if let Some((rw, rh)) = resize_request().filter(|_| !ctx.resized) { diff --git a/crates/benilla-app/src/capture/probes/live_fps.rs b/crates/benilla-app/src/capture/probes/live_fps.rs index f410cb6e..570ae30c 100644 --- a/crates/benilla-app/src/capture/probes/live_fps.rs +++ b/crates/benilla-app/src/capture/probes/live_fps.rs @@ -29,6 +29,11 @@ use benilla_world::world_census::WorldCensus; /// (scenario=`live`), and exits. pub(crate) struct LiveFpsPlugin; +/// How long past `WOW_LIVE_FPS_AT` the probe waits for a world before declaring the run dead. +/// Entry normally lands well inside `at` itself; 60 s of grace covers a cold-cache load without +/// ever letting a stranded run ride out a harness timeout (see the `Waiting` arm). +const BOOT_DEADLINE_SECS: f32 = 60.0; + impl Plugin for LiveFpsPlugin { fn build(&self, app: &mut App) { let frames = std::env::var("WOW_LIVE_FPS") @@ -146,6 +151,19 @@ fn drive_live_fps( // (The un-occludable window that keeps the SETTLE phase from streaming the world at // ~1 fps is [`ProbeFocusPlugin`]'s now — every probe needs it, not just this one.) if time.elapsed_secs() < probe.at || self_player.is_empty() { + // The boot deadline: entering the world takes seconds, and `at` already grants a + // settle window on top — a run still worldless this far past it is stranded on a + // glue screen (dead server, refused login the marker arms didn't catch) and every + // second more is the 1371 sitting's dead wall-clock again. `FATAL` is the marker + // leg.sh keys on. + if time.elapsed_secs() > probe.at + BOOT_DEADLINE_SECS { + error!( + "live-fps: FATAL — still not in world {:.0}s past the probe delay; a measurement run with no world is dead. exiting", + BOOT_DEADLINE_SECS + ); + exit.write(AppExit::error()); + probe.phase = LiveFpsPhase::Done; + } return; } if let Ok(mut w) = windows.single_mut() { diff --git a/crates/benilla-app/src/char_select/mod.rs b/crates/benilla-app/src/char_select/mod.rs index 6504810e..1222ccd4 100644 --- a/crates/benilla-app/src/char_select/mod.rs +++ b/crates/benilla-app/src/char_select/mod.rs @@ -277,6 +277,7 @@ fn apply_roster_policy( // (decision 1174's always-present run fact) — absent in every ordinary run, which is the // player answer and the one this screen was written for. rig: Option>, + mut exit: MessageWriter, ) { if !roster.env_read { roster.env_read = true; @@ -357,7 +358,18 @@ fn apply_roster_policy( info!("char select: WOW_CHAR={name} — fast path"); send_pick(&mut roster, &pick, guid); } - None => warn!("char select: WOW_CHAR={name} not on this account — showing roster"), + None => { + warn!("char select: WOW_CHAR={name} not on this account — showing roster"); + // A harness run (env creds, no smoke) can never pick a different row itself — + // parked here it burns its whole wall-clock. Same marker the login arm uses, + // same greppable verdict for leg.sh. + if crate::run_mode::unattended_login() + && std::env::var_os("WOW_LOGIN_SMOKE").is_none() + { + error!("login: FATAL — WOW_CHAR={name} is not on this account; exiting"); + exit.write(AppExit::error()); + } + } } } } diff --git a/crates/benilla-app/src/login/mod.rs b/crates/benilla-app/src/login/mod.rs index 24cac98a..afec0a7e 100644 --- a/crates/benilla-app/src/login/mod.rs +++ b/crates/benilla-app/src/login/mod.rs @@ -17,9 +17,11 @@ //! re-authenticates on its own takes the account back off whoever just displaced it. //! //! Module split: this file (state, policy, input, dialogs, the saved-account persistence), -//! [`screen`] (the authored layout, transcribed from `AccountLogin.xml`). +//! [`screen`] (the authored layout, transcribed from `AccountLogin.xml`), [`smoke`] (the +//! `WOW_LOGIN_SMOKE` headless prover). mod screen; +mod smoke; use std::sync::atomic::Ordering; @@ -42,6 +44,7 @@ use crate::portrait::{GluePreview, GlueScene}; use crate::sound::GlueSound; pub(crate) use screen::LoginAction; +pub(crate) use smoke::smoke_character; /// The flat resubmit pacing after a transport failure with pending credentials (decision 0065's /// reconnect cadence, moved app-side by 0539 — the IO thread never sleeps). @@ -79,7 +82,7 @@ impl Plugin for LoginPlugin { ) .chain() .run_if(in_state(ClientState::Login)), - (debug_login_smoke, screen::debug_login_shot), + (smoke::debug_login_smoke, screen::debug_login_shot), ) .chain() .after(benilla_world::schedule::WorldStage::Net), @@ -205,8 +208,15 @@ fn drive_policy( mut stages: MessageReader, mut failures: MessageReader, mut disconnects: MessageReader, + mut exit: MessageWriter, ) { let now = time.elapsed_secs(); + // A harness run (env creds, and not the smoke — the smoke owns its own verdict) has nobody at + // the keyboard: a login failure no resubmit can change leaves it parked on a dialog for its + // whole wall-clock, and every retry a runner grants it is spent the same way. Those failures + // exit non-zero instead, on one greppable marker — "login: FATAL" — that leg.sh keys on. + let harness = + crate::run_mode::unattended_login() && std::env::var_os("WOW_LOGIN_SMOKE").is_none(); let empty = GlueStrings::default(); let strings = strings.as_deref().unwrap_or(&empty); @@ -239,6 +249,10 @@ fn drive_policy( Err(why) => { error!("login: REFUSING the env fast path — {why} Set WOW_ALLOW_ACCOUNT=1 if the cross-account login is deliberate."); dialog.open_error(&why); + // The refusal is deterministic — the slot is baked into the binary — so the + // run can never get past this screen (the 1371 legs burned 3 × timeout on it). + error!("login: FATAL — account guard refused the only credentials this run has; exiting"); + exit.write(AppExit::error()); } } } @@ -258,6 +272,10 @@ fn drive_policy( warn!("login: {}", msg.reason); intent.clear(); dialog.open_error(&msg.reason); + if harness { + error!("login: FATAL — terminal login failure with nobody at the keyboard ({}); exiting", msg.reason); + exit.write(AppExit::error()); + } continue; } match msg.code { @@ -267,6 +285,10 @@ fn drive_policy( warn!("login: refused (code {code:#04x}) — {}", msg.reason); intent.clear(); dialog.open_error(fail_text(strings, Some(code))); + if harness { + error!("login: FATAL — refused (code {code:#04x}) and no resubmit can change it; exiting"); + exit.write(AppExit::error()); + } } None if intent.announced => { warn!("login: {}", msg.reason); @@ -760,87 +782,6 @@ fn save_account(name: &str) { } } -// ── Instruments ────────────────────────────────────────────────────────────────────────────────── - -/// Split `WOW_LOGIN_SMOKE=user:pass[:Character]` into its three fields. **Three-way, once**: a -/// `split_once(':')` here would hand the character name to the password, which is exactly what it -/// did on this instrument's first live run (`result 0x04`, and the wrong-password path is what -/// this smoke exists to prove, so it looked plausible). -fn smoke_spec(spec: &str) -> (&str, &str, Option<&str>) { - let mut parts = spec.splitn(3, ':'); - let user = parts.next().unwrap_or_default(); - let pass = parts.next().unwrap_or_default(); - let character = parts.next().map(str::trim).filter(|n| !n.is_empty()); - (user, pass, character) -} - -/// The optional third field — the body to enter as, or `None` for the plain -/// reach-character-select smoke. [`crate::char_select`]'s roster policy is what actually answers -/// the roster with it. -pub(crate) fn smoke_character(spec: &str) -> Option { - smoke_spec(spec).2.map(str::to_string) -} - -/// The login smoke (`WOW_LOGIN_SMOKE=user:pass[:Character]`, decision 0539 §7): once the screen is -/// up, submit those credentials through the real screen path; exit success on reaching CharSelect, -/// log + exit failure on a refusal — the wrong-password path is provable headlessly. -/// -/// **Naming a character keeps the run going into the world instead of exiting** (decision 1262). -/// This is the only headless way to reach the world down the *player's* path: `WOW_CHAR` also -/// enters the world, but setting it makes the run **unattended** -/// ([`crate::run_mode::unattended_login`]), and an unattended run takes the other branch at every -/// session decision — so the attended half was untestable without a person at the keyboard. The -/// pick itself stays `char_select`'s (`apply_roster_policy` reads the third field the same way it -/// reads `WOW_CHAR`); this only declines to exit. Pair with `WOW_PROBE_EXIT_AT` to bound the run. -#[allow(clippy::too_many_arguments)] -fn debug_login_smoke( - state: Res>, - mut intent: ResMut, - submit: Res, - abandon: Res, - mut failures: MessageReader, - time: Res