a crash leaves a report, and the wire's counts are capped

- a panic writes benilla-config/Diagnostics/crash-<time>.txt with the build id, the location, a backtrace and the last 200 log lines, and names the file as its last line, so a bug report can attach it (2266)
- every wire-count allocation hint is capped and the compressed update object reads through the cursor, so a malformed packet can no longer ask for gigabytes (2266)
- the 23 in-world run conditions are one set, the 45 probe variables one registry, the install-gated tests say when they skip, and every gate is timed (2266)
- one workspace-level lint allowance replaces 413 per-site attributes, and the decision corpus gets a lint in the gate (2266)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-09-17 16:38:59 +07:00
co-authored by Claude Fable 5.1
parent 8f8fb1e08c
commit 8562fb0ef1
368 changed files with 1919 additions and 646 deletions
+10
View File
@@ -12,6 +12,16 @@ authors = ["sam <samuv.eth@gmail.com>"]
# since day one, enacted at the 0530 debt sweep). Per-crate feature additions stay in the crate.
# `image` deliberately stays per-crate: its default-features/feature sets differ per consumer,
# and a workspace default-features choice can't be subtracted from downstream.
# **Lint policy** (decision 2266 §C4). `too_many_arguments` is a readability lint for plain
# functions; on a Bevy system the parameter list IS the scheduler's dependency declaration, and
# a system that reads eight resources has eight parameters by construction. The lint had fired
# 364 times in this workspace and been allowed 364 times, 281 of them on systems — a lint whose
# every verdict is overridden is not a gate, it is a tax on adding a system. Allowed once here;
# the per-site attributes are gone. `type_complexity` stays on: it fires on shapes a type alias
# or a `SystemParam` bundle genuinely fixes.
[workspace.lints.clippy]
too_many_arguments = "allow"
[workspace.dependencies]
anyhow = "1.0.102"
# bevy's `default` is `["2d", "3d", "ui"]`, and every one of those three pulls `audio`
+3
View File
@@ -5,5 +5,8 @@ edition.workspace = true
license.workspace = true
description = "ADT terrain-tile reader for WoW 1.12.1 (build 5875) — in-repo, decision 0021"
[lints]
workspace = true
[dependencies]
benilla-bytes = { path = "../benilla-bytes" }
+3
View File
@@ -5,6 +5,9 @@ edition.workspace = true
license.workspace = true
description = "The Bevy app as a library: everything but the build-id stamp, which lives in the `benilla` launcher shim so a commit dirties ~30 lines instead of this crate (decision 0993)"
[lints]
workspace = true
[features]
# **The dev/player seam** (decisions 0026 + 1173). Default-ON, so every ordinary `cargo run`,
# capture, probe and gate builds exactly what it built before. `--no-default-features` compiles
-1
View File
@@ -145,7 +145,6 @@ fn elect_event(cache: &ZoneCache, next: &ZoneSignal) -> Option<&'static str> {
/// indoor override), the PvP tuple, write the host globals, then fire the elected zone event
/// and — independently, like the client's second site — `MINIMAP_ZONE_CHANGED` when the
/// subzone-else-zone line changed.
#[allow(clippy::too_many_arguments)]
fn feed_zone_events(
script: Option<NonSendMut<UiScript>>,
world: benilla_world::world_point::WorldPoint,
+1 -1
View File
@@ -112,7 +112,7 @@ impl Plugin for AreaTriggerPlugin {
Update,
check_area_triggers
.in_set(WorldStage::Stream)
.run_if(in_state(crate::char_select::ClientState::InWorld)),
.in_set(crate::char_select::InWorldGated),
);
}
}
-2
View File
@@ -742,7 +742,6 @@ type AuraParts<'w, 's> = Query<
/// Note the swap keys on the *instance* alpha only: a batch whose own animated colour alpha dips is
/// a per-batch quantity the reference combines separately, and forcing those onto a blend pass would
/// change every existing unit's look.
#[allow(clippy::too_many_arguments)] // one Bevy system's full input set
pub(crate) fn apply_aura_alpha(
time: Res<Time>,
mut commands: Commands,
@@ -842,7 +841,6 @@ fn author_cards(
/// property that its attached models render off (the same reason
/// [`benilla_world::model_fade::apply_despawn_fade`] and the self feather both walk descendants, not
/// children).
#[allow(clippy::too_many_arguments)] // the author's full channel set, threaded down the walk
fn author_descendants(
entity: Entity,
alpha: f32,
+2 -3
View File
@@ -49,7 +49,7 @@ use bevy::prelude::*;
use benilla_ui::script::keybind::{AddonBindingBody, KeybindCommand, KeybindRequest};
use benilla_ui::script::UiScript;
use crate::char_select::ClientState;
use crate::char_select::InWorldGated;
use crate::ui_script::{PlayerUiHover, PointerOverUiPanel, UiKeyboardCapture};
pub(crate) mod chord;
@@ -222,7 +222,7 @@ impl Plugin for BindingsPlugin {
.in_set(crate::ui_script::UiInput)
.in_set(BindingSet)
.before(benilla_world::schedule::WorldStage::Input)
.run_if(in_state(ClientState::InWorld)),
.in_set(InWorldGated),
drain_binding_requests,
),
);
@@ -435,7 +435,6 @@ fn sync_dispatch(script: Option<NonSendMut<UiScript>>, mut dispatch: ResMut<Bind
/// The dispatch pass — see the module doc. Runs right after the UI key feed (same frame's
/// capture gate), before `WorldStage::Input` (a bound key must act this frame, once).
#[allow(clippy::too_many_arguments)]
fn latch_and_dispatch(
script: Option<NonSendMut<UiScript>>,
mut keyboard: MessageReader<KeyboardInput>,
+1 -1
View File
@@ -2254,7 +2254,7 @@ mod tests {
/// The install's own `Bindings.xml`, parsed — `None` (and a skipped test) without a client.
fn install_bindings() -> Option<Vec<benilla_ui::bindings_xml::AddonBinding>> {
let data = benilla_formats::wow_data()?;
let data = benilla_formats::wow_data_or_skip!(None);
let mut chain = benilla_formats::open_chain(&data).expect("open the 1.12 patch chain");
let xml = String::from_utf8_lossy(
&chain
+1 -1
View File
@@ -227,7 +227,7 @@ fn sync_shadows(
/// Re-project each shadow whose inputs moved; clear it when the box degenerates, the fade
/// reaches zero, or no receiving surface is in the box (the reference's no-ground gate).
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
#[allow(clippy::type_complexity)]
fn update_shadows(
time: Res<Time>,
catalog: Option<Res<AnimData>>,
-1
View File
@@ -284,7 +284,6 @@ fn load_shakes(mut commands: Commands, assets: Option<Res<WorldAssets>>) {
struct Shakes(CameraShakeCatalog);
/// Enqueue a shake for each qualifying footfall and death thud on the frame's event stream.
#[allow(clippy::too_many_arguments)]
fn fire_shakes(
mut events: MessageReader<AnimSoundEvent>,
time: Res<Time>,
@@ -33,7 +33,6 @@ const SUBJECT_YAW: f32 = 2.36;
/// feeds then push it into the VM during the settle window exactly as live wire data would. Item
/// icons resolve through the offline `ItemDisplayCatalog` (display ids chosen from entries that
/// catalog is known to carry); names land directly in the caches (no server to ask).
#[allow(clippy::too_many_arguments)]
pub(super) fn seed_ui_fixture(
mut ctx: ResMut<CaptureCtx>,
mut commands: Commands,
-1
View File
@@ -29,7 +29,6 @@ const FXVIEW_GO_GUID: u64 = (0xF110u64 << 48) | 0xFC0FEE;
/// through the same [`attach_effect_visuals`] body the game uses, and (for missiles) fly the
/// root along its facing so trails extend. Inert outside fxview captures (the request resource
/// only exists then).
#[allow(clippy::too_many_arguments)]
pub(crate) fn drive_fx_view(
req: Option<Res<FxViewRequest>>,
state: Option<ResMut<FxViewState>>,
+1 -1
View File
@@ -126,7 +126,7 @@ fn burst_path(out: &str, index: u32, count: u32) -> String {
/// filter instead of the scene. That cost a whole B38 session: eight bursts and three retracted
/// mechanisms measured on a corpse, because the images still looked like a plausible frame. A loud
/// no-op is worth far more than a burst that has to be recognised as garbage after the fact.
#[allow(clippy::too_many_arguments, clippy::type_complexity)] // one Bevy system's full input set
#[allow(clippy::type_complexity)] // one Bevy system's full input set
fn fire_live_shot(
mut shot: ResMut<LiveShot>,
time: ProbeClock,
+1 -2
View File
@@ -101,6 +101,7 @@ mod probe_charter;
mod probe_chest;
mod probe_clam;
mod probe_crossing;
pub(crate) mod probe_env;
mod probe_gm_ticket;
mod probe_goquest;
mod probe_guard_poi;
@@ -912,7 +913,6 @@ impl Plugin for CapturePlugin {
/// Each frame, force the deterministic capture conditions: pinned time-of-day, no perf HUD, and the
/// fixed camera pose. Runs in `WorldStage::Present` (after `control` is gated off and after terrain
/// streaming reads the camera), so the harness is the sole, stable author of the view.
#[allow(clippy::too_many_arguments)]
fn pin_scene(
ctx: Res<CaptureCtx>,
mut debug: ResMut<DebugState>,
@@ -1001,7 +1001,6 @@ pub(crate) struct ProbeCensus<'w, 's> {
}
/// Drive the capture lifecycle: wait for streaming, settle, screenshot, exit.
#[allow(clippy::too_many_arguments)]
fn drive_capture(
mut ctx: ResMut<CaptureCtx>,
mut watch: ResMut<FrameWatch>,
@@ -447,7 +447,7 @@ fn error_name(error: u32) -> &'static str {
}
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
#[allow(clippy::too_many_lines)]
fn auction_probe(
time: ProbeClock,
mut probe: ResMut<AuctionProbe>,
@@ -202,7 +202,6 @@ fn find_bank_slot(store: &ObjectStore, guid: u64) -> Option<u8> {
(0..24u8).find(|&i| store.0.player_bank_slot(i) == Some(guid))
}
#[allow(clippy::too_many_arguments)]
fn bank_probe(
time: ProbeClock,
mut probe: ResMut<BankProbe>,
@@ -95,7 +95,6 @@ enum Phase {
}
// One Bevy system's full input set (the guard-poi probe's shape).
#[allow(clippy::too_many_arguments)]
fn bg_queue_probe(
time: ProbeClock,
mut probe: ResMut<BgQueueProbe>,
@@ -267,7 +267,6 @@ fn vm_bind_location(script: &UiScript) -> String {
.unwrap_or_default()
}
#[allow(clippy::too_many_arguments)]
fn binder_probe(
time: ProbeClock,
mut probe: ResMut<BinderProbe>,
@@ -234,7 +234,6 @@ fn report(label: &str, s: &[Sample]) {
);
}
#[allow(clippy::too_many_arguments)]
fn book_probe(
time: ProbeClock,
mut probe: ResMut<BookProbe>,
+1 -1
View File
@@ -141,7 +141,7 @@ impl Plugin for ProbeCamPlugin {
drive_probe_cam
.in_set(WorldStage::Input)
.before(crate::player::PlayerControlSet)
.run_if(in_state(crate::char_select::ClientState::InWorld)),
.in_set(crate::char_select::InWorldGated),
);
}
}
@@ -453,7 +453,6 @@ fn probe_guild_name(prefix: &str) -> String {
format!("{prefix} {secs:08}")
}
#[allow(clippy::too_many_arguments)]
fn charter_probe(
time: ProbeClock,
mut probe: ResMut<CharterProbe>,
@@ -211,7 +211,6 @@ fn report(label: &str, expect: u16, seen: &[Option<u16>]) -> u32 {
}
}
#[allow(clippy::too_many_arguments)]
fn chest_probe(
time: ProbeClock,
mut probe: ResMut<ChestProbe>,
@@ -204,7 +204,6 @@ fn find_in_backpack(
.map(|_| (guid, u32::from(i) + 1))
}
#[allow(clippy::too_many_arguments)]
fn clam_probe(
time: ProbeClock,
mut probe: ResMut<ClamProbe>,
+552
View File
@@ -0,0 +1,552 @@
//! The probe fleet's **environment registry** — every `WOW_PROBE*` variable the app reads, in one
//! table the code is checked against in both directions (decision 2265 §A5).
//!
//! The set used to live in three places that could not see each other: the read sites (one per
//! variable, spread over the fleet), a hand-kept arming list in `dev.rs` for the un-occludable
//! window ([`super::ProbeFocusPlugin`], decision 0906), and nothing at all that said what the
//! fleet accepts. The arming list is the one that bit: 0906's rule is that *every* scripted
//! probe defends itself against the macOS occlusion throttle, and the list had drifted to ten of
//! the twenty-five variables that schedule on the wall clock — a mail or auction probe launched
//! exactly as the method prescribes ran covered, at ~1 fps, measuring garbage.
//!
//! So the list is now a **column** here ([`ProbeVar::wall_clock`]), the arming reads the column,
//! and two structural tests below keep the table honest: every `"WOW_PROBE…"` string literal in
//! the crate is a row, every row is a literal somewhere other than this file, and every value
//! `WOW_PROBE` itself dispatches on is in [`PROBE_NAMES`]. `WOW_PROBE=list` prints it.
//!
//! What this is **not**: a dispatcher. The variables keep their own read sites and their own
//! shapes — several carry free-form values (Lua chunks, chat lines with spaces and `=`, `A>B`
//! drag specs) and several combine in one run — so folding them into one variable would need an
//! escaping mini-language and would change the launch line every session uses. The defect was
//! the drift, and a registry is the whole cure for drift.
/// One probe-fleet environment variable.
pub(crate) struct ProbeVar {
/// The variable, exactly as the read site spells it.
pub name: &'static str,
/// One line: the value shape and what setting it does, from the comment at the read site.
pub purpose: &'static str,
/// Whether the variable **schedules on elapsed real time** — its script fires at `<secs>`
/// marks, integrates a rate per frame, or steps a phase machine on `Time<Real>` — so the run
/// is only right while frames keep arriving at full rate.
///
/// Why it matters (decision 0906): macOS drops a fully covered window to ~1 fps drawables,
/// and on such a window a wall-clock probe does not measure slowly, it **runs the wrong
/// script** — one leg fired `W@16` and `Space@19` in the same frame and jumped from a
/// standstill; another integrated its camera at 125 yd/s for 500 and never crossed the
/// radius it was testing (0794). Every `true` row therefore arms
/// [`super::ProbeFocusPlugin`] from `dev.rs`, which keeps the probe window un-occludable
/// (and, for a run that draws no pixels, parks it small in a corner — decision 1148).
///
/// A `false` row is a flag or a modifier: a `_AT`/`_STEP`/`_KEEP` rides its parent's
/// arming, a pricing lever changes *what* a run draws rather than *when*, and a state
/// machine that advances on wire replies (the char-create probe) has no clock to be robbed of.
pub wall_clock: bool,
}
/// Every `WOW_PROBE*` variable the app reads. Grouped by the probe that owns each; the order is
/// the order `WOW_PROBE=list` prints.
pub(crate) const PROBE_VARS: &[ProbeVar] = &[
// ── The variable itself, and the run shell every scripted probe rides ────────────────────
ProbeVar {
name: "WOW_PROBE",
purpose: "<name> — a value-dispatched live probe (see the names below); `list` prints this table",
// Five of the six values step a phase machine on `time.elapsed_secs` (melee's 3 s swing
// cadence, crossing/taxi/guardpoi's phases, castcancel's press-at-t); `partner` answers
// invites the frame they land, but rides the same variable.
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_EXIT_AT",
purpose: "<secs> — exit the app after N wall seconds; bounds any scripted live probe's lifetime",
// Fires on `ProbeClock` at `<secs>`, and it is the one probe variable a trace-only run
// sets on its own (`WOW_MOVE_TRACE`/`WOW_STREAM_TRACE` legs, method.md) — 0794's
// throttled camera leg was exactly such a run.
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_PARK",
purpose: "corner|edge|off — where the un-occludable probe window sits, and whether it is pinned on top",
// A dial ON the occlusion defence itself, not a schedule.
wall_clock: false,
},
ProbeVar {
name: "WOW_PROBE_RESIZE",
purpose: "\"<secs>:<W>x<H>\" — resize the primary window mid-run (the headless fullscreen-toggle stand-in)",
// Fires on `ProbeClock` at `<secs>`.
wall_clock: true,
},
// ── The actuation channels: chat, keys, Lua, pointer ─────────────────────────────────────
ProbeVar {
name: "WOW_PROBE_CHAT",
purpose: "\"<line>[;<line>…]\" — send each line as chat once in-world; the park-the-probe-anywhere instrument (`.go xyz …`)",
// Line `n` is due at `at + every * n` on `ProbeClock`.
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_CHAT_AT",
purpose: "<secs> — when the first chat line goes out (default 8)",
wall_clock: false,
},
ProbeVar {
name: "WOW_PROBE_CHAT_EVERY",
purpose: "<secs> — space the chat lines apart instead of one burst (do X, wait, then do Y)",
wall_clock: false,
},
ProbeVar {
name: "WOW_PROBE_KEY",
purpose: "\"<key>@<secs>[:<hold>][;…]\" — synthesize key presses once in-world (a jump, a mount flourish, a held W)",
// Each tap fires at its `@<secs>` and releases `<hold>` later, on `ProbeClock`.
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_LUA",
purpose: "\"<chunk>\" — run a Lua chunk in the live UI VM once per world entry (the press-the-button-headlessly instrument)",
// First fire at `_AT` seconds, re-armed `_AGAIN` seconds into every later world entry.
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_LUA_AT",
purpose: "<secs> — when the chunk runs after the first world entry (default 10)",
wall_clock: false,
},
ProbeVar {
name: "WOW_PROBE_LUA_AGAIN",
purpose: "<secs> — how long after every LATER world entry (a relog) the chunk runs again (default 4)",
wall_clock: false,
},
ProbeVar {
name: "WOW_PROBE_HOVER",
purpose: "\"<frame>[;<frame>…]\" — sweep the real pointer across the named frames' centres, pressing nothing",
// Starts at `_AT`, one move per `_STEP` seconds, alternating on `_DUTY` — all wall seconds.
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_HOVER_AT",
purpose: "<secs> — when the sweep starts (default 14)",
wall_clock: false,
},
ProbeVar {
name: "WOW_PROBE_HOVER_STEP",
purpose: "<secs> — seconds per pointer move (default 0.25; ~0.016 sweeps at frame rate, the director's gesture)",
wall_clock: false,
},
ProbeVar {
name: "WOW_PROBE_HOVER_JITTER",
purpose: "<px> — walk the pointer inside a px box each step so it moves while the hovered frame stays put",
wall_clock: false,
},
ProbeVar {
name: "WOW_PROBE_HOVER_DUTY",
purpose: "<on>:<off> — sweep for `on` seconds, park over nothing for `off`, repeat (a leg that alternates can be read)",
wall_clock: false,
},
ProbeVar {
name: "WOW_PROBE_DRAG",
purpose: "\"<From>><To>[;…]\" — drag one named frame onto another through the real pointer path, one gesture step per frame",
// Starts at `_AT`, advances one step per `_STEP` seconds, on `ProbeClock`.
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_DRAG_AT",
purpose: "<secs> — when the first drag starts (default 14)",
wall_clock: false,
},
ProbeVar {
name: "WOW_PROBE_DRAG_STEP",
purpose: "<secs> — seconds per gesture step (default 0.1); a press and release in one frame is a click, not a drag",
wall_clock: false,
},
ProbeVar {
name: "WOW_PROBE_DRAG_LUA",
purpose: "\"<chunk>\" — a Lua chunk evaluated after each drag whose string result is logged as the report",
wall_clock: false,
},
// ── The scripted controller inputs (decisions 0621/0653) ────────────────────────────────
ProbeVar {
name: "WOW_PROBE_LOOK",
purpose: "\"<deg_per_sec>@<start_s>:<duration_s>[;…]\" — the scripted mouse-turn: turn the avatar's aim at a rate for a while",
// `90°/s for 6 s` has to produce 540° whatever the frame rate did; integrates rate × dt
// between `at` and `until` on `ProbeClock`.
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_PITCH",
purpose: "\"<deg>@<start_s>[:<deg_per_sec>][;…]\" — the scripted dive: aim a swimming avatar's nose up or down (+up)",
// A dive scripted to reach 30° by second 25 has to reach it at second 25, and it is the
// one script that can be a run's only actuator (a drifting swimmer needs no keys).
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_CAM",
purpose: "\"<yaw_deg>,<pitch_deg>[,<dist_yd>]@<start_s>[:<pan_deg_per_s>][;…]\" — park the third-person camera at absolute poses",
// Each pose takes over at its `@<start>` and pans at its rate from then, on `ProbeClock`.
wall_clock: true,
},
// ── The FPS probe's dials (`WOW_LIVE_FPS`, the capture harness) ─────────────────────────
ProbeVar {
name: "WOW_PROBE_UNCAP",
purpose: "immediate|vsync — the FPS probe's present mode instead of the measured-best AutoNoVsync",
wall_clock: false,
},
ProbeVar {
name: "WOW_PROBE_VSYNC",
purpose: "1 — keep vsync ON in the FPS probe: it then measures the present ceiling the display grants this window",
wall_clock: false,
},
// ── The pricing levers and traces: change what a run draws or logs, never when ───────────
ProbeVar {
name: "WOW_PROBE_UI_ONE_TEX",
purpose: "1 — pricing lever: split UI runs on state flags alone, ignoring texture identity (the draw-count ceiling an atlas would reach)",
wall_clock: false,
},
ProbeVar {
name: "WOW_PROBE_SHARED_SKIN",
purpose: "1 — pricing lever: share character-skin materials across bodies (see `char_skin::build_char_skin_materials`)",
wall_clock: false,
},
ProbeVar {
name: "WOW_PROBE_NAME_TRACE",
purpose: "1 — per-frame NAME_TRACE lines for the self player's plate seat (a smoothness question is measured, never eyeballed)",
wall_clock: false,
},
// ── The `=1` live probes: each parks the body somewhere real and steps a phase machine ───
// Every one below reads `time.elapsed_secs_f64()` into a `since`/`now` phase machine —
// waits, settles and timeouts in wall seconds — so each arms the occlusion defence.
ProbeVar {
name: "WOW_PROBE_BGQUEUE",
purpose: "1 — level past the bracket floor, greet Stormwind's Warsong Gulch battlemaster and queue through his list",
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_MAIL",
purpose: "1 — GM-mail the probe's own character, open the Goldshire mailbox and drive inbox/take/send/delete through the VM",
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_AUCTION",
purpose: "1 — GM-hop to a Stormwind auctioneer and drive browse/throttle/sell/owner-list/cancel through the VM",
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_BANK",
purpose: "1 — GM-hop to a pure banker and drive the six-opcode bank wire (activate/deposit/withdraw/buy-slot/refusal)",
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_BINDER",
purpose: "1 — GM-hop to an innkeeper, select the bind row and answer the confirm through the VM's own ConfirmBinder()",
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_SERVICE",
purpose: "1 — right-click four real NPCs, one per UNIT_NPC_FLAGS shape, and report the service-ladder arm and the window that opened",
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_VENDOR_SWAP",
purpose: "1 — open one Goldshire vendor over the other's window and read the `npc` token, title and portrait at MERCHANT_SHOW",
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_MODEL_CAMERA",
purpose: "1 — build a plain <Model> pane on a camera-bearing file and read the renderer's camera and root back",
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_GMTICKET",
purpose: "1 — drive the five-opcode GM ticket wire through the VM (status, clean slate, file, edit, abandon)",
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_CHARTER",
purpose: "1 — buy a guild charter at the Stormwind registrar, open it with a real bag right-click and rename it",
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_BOOK",
purpose: "1 — teleport to the Old Town plaque and measure what the item-text reader costs per frame, closed vs open (B240)",
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_CHEST",
purpose: "1 or <x>,<y>,<z>[,<map>] — open a real chest on the click's own route and report the self anim id before/during/after",
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_GOQUEST",
purpose: "1 or <x>,<y>,<z>[,<map>] — park at a GameObject questgiver and report the dialog status below and above MinLevel",
wall_clock: true,
},
ProbeVar {
name: "WOW_PROBE_CLAM",
purpose: "1 or <entry> — stock an openable item, right-click it through UseContainerItem and report whether a loot window opens",
wall_clock: true,
},
// ── Modifiers of the value-dispatched probes ────────────────────────────────────────────
ProbeVar {
name: "WOW_PROBE_DOCK",
purpose: "x,y,z[,map] — the dock `WOW_PROBE=crossing` boards from (map defaults to 0 and must be sent)",
wall_clock: false,
},
// ── The character-select probe: advances on wire replies, not on a clock ───────────────
ProbeVar {
name: "WOW_PROBE_CHARCREATE",
purpose: "\"<name>[,race,class,gender[,skin,face,hair,haircolor,facial]]\" — create (and delete) a character at select over the wire",
// A roster/result state machine: `AwaitingRoster` → create → result byte → delete →
// result byte, each step on the reply that arrives. No clock read anywhere in it.
wall_clock: false,
},
ProbeVar {
name: "WOW_PROBE_CHARCREATE_KEEP",
purpose: "1 — keep the character the probe created instead of deleting it",
wall_clock: false,
},
];
/// The named values of `WOW_PROBE` itself, `(value, purpose)` — the one place they are listed.
/// `dev.rs` dispatches on the literals (one plugin per value) and `lib.rs` answers `list`
/// before any window opens; the test below keeps both in step with this table.
pub(crate) const PROBE_NAMES: &[(&str, &str)] = &[
("list", "print this table and exit, before any window opens"),
(
"melee",
"auto-fight the nearest enemy so the dbg-trace sink can record the combat-text timeline",
),
(
"partner",
"the second client that says yes: auto-accept every group invite and duel challenge (0434/0637)",
),
(
"crossing",
"board a cross-continent boat and report the map seam surviving (0455)",
),
(
"taxi",
"open the flight-master menu on the wire and ride Stormwind → Sentinel Hill to a verdict (0484)",
),
(
"guardpoi",
"ask a Stormwind guard for the weapons trainer and check the SMSG_GOSSIP_POI marker field by field",
),
(
"castcancel",
"hearth and press W mid-cast — the local self-cancel's end-to-end timing instrument",
),
];
/// The variables whose presence arms the un-occludable probe window — the `wall_clock` column.
pub(crate) fn wall_clock_vars() -> impl Iterator<Item = &'static str> {
PROBE_VARS.iter().filter(|v| v.wall_clock).map(|v| v.name)
}
/// `WOW_PROBE=list` — print the registry, one variable per line, then the named values.
pub(crate) fn print() {
println!("The probe fleet's environment (capture::probe_env, decision 2265 §A5).");
println!(
"wall-clock: the variable schedules on elapsed real time and arms the un-occludable probe"
);
println!("window (ProbeFocusPlugin, decision 0906) — covered, such a run drops to ~1 fps and");
println!("executes the wrong script.");
println!();
println!("{:<26} {:<10} PURPOSE", "VARIABLE", "WALL-CLOCK");
for v in PROBE_VARS {
let wc = if v.wall_clock { "yes" } else { "-" };
println!("{:<26} {:<10} {}", v.name, wc, v.purpose);
}
println!();
println!("WOW_PROBE=<name>:");
for (name, purpose) in PROBE_NAMES {
println!(" {name:<12} {purpose}");
}
}
#[cfg(test)]
mod tests {
use super::*;
/// This file's own path under `src/` — excluded from both scans, since its table and its
/// tests are the one place the literals are *supposed* to appear without being read.
const SELF: &str = "capture/probe_env.rs";
/// **The table cannot drift from the code, in either direction** (decision 2265 §A5).
///
/// Every `"WOW_PROBE…"` string literal in the crate must be a row here — a variable the
/// fleet reads that the registry does not know is exactly the hand-maintained gap this
/// module replaced — and every row must be a literal somewhere other than this file, or the
/// registry is describing a variable nothing reads. String literals only: doc comments
/// write the backticked form and are skipped by line.
#[test]
fn every_probe_variable_is_registered_and_every_registered_variable_is_read() {
let registered: Vec<&str> = PROBE_VARS.iter().map(|v| v.name).collect();
for (i, name) in registered.iter().enumerate() {
assert!(
!registered[..i].contains(name),
"`{name}` is registered twice"
);
assert!(
name.starts_with("WOW_PROBE"),
"`{name}` is not a probe variable — the registry is `WOW_PROBE*` only"
);
}
let mut read: Vec<(String, String)> = Vec::new(); // (literal, file)
for (rel, text) in crate_sources() {
if rel == SELF {
continue;
}
for lit in probe_literals(&text) {
read.push((lit, rel.clone()));
}
}
let unregistered: Vec<String> = read
.iter()
.filter(|(lit, _)| !registered.contains(&lit.as_str()))
.map(|(lit, file)| format!("{file}: \"{lit}\""))
.collect();
assert!(
unregistered.is_empty(),
"these `WOW_PROBE…` literals are read but not in `PROBE_VARS` — add a row (name, \
purpose from the read site's comment, and whether it schedules on the wall clock):\n {}",
unregistered.join("\n ")
);
let unread: Vec<&str> = registered
.iter()
.copied()
.filter(|name| !read.iter().any(|(lit, _)| lit == name))
.collect();
assert!(
unread.is_empty(),
"these `PROBE_VARS` rows are not a string literal anywhere else in the crate — \
nothing reads them; drop the row or restore the read site:\n {}",
unread.join("\n ")
);
}
/// **`WOW_PROBE`'s named values are listed once.** Every `Ok("<name>")` the code compares
/// `WOW_PROBE`'s value against (the `dev.rs` plugin dispatch, the `lib.rs` `list` exit) must
/// be a `PROBE_NAMES` row, and every row must be dispatched on — so `WOW_PROBE=list` prints
/// the values that exist, not the ones someone remembered.
#[test]
fn every_dispatched_probe_name_is_listed_and_every_listed_name_is_dispatched() {
let listed: Vec<&str> = PROBE_NAMES.iter().map(|(n, _)| *n).collect();
for (i, name) in listed.iter().enumerate() {
assert!(!listed[..i].contains(name), "`{name}` is listed twice");
}
let mut dispatched: Vec<(String, String)> = Vec::new(); // (value, file)
for (rel, text) in crate_sources() {
if rel == SELF {
continue;
}
for value in dispatched_probe_values(&text) {
dispatched.push((value, rel.clone()));
}
}
assert!(
!dispatched.is_empty(),
"no `WOW_PROBE` value dispatch found anywhere — the scanner has lost its needle"
);
let unlisted: Vec<String> = dispatched
.iter()
.filter(|(v, _)| !listed.contains(&v.as_str()))
.map(|(v, file)| format!("{file}: WOW_PROBE={v}"))
.collect();
assert!(
unlisted.is_empty(),
"these `WOW_PROBE` values are dispatched on but not in `PROBE_NAMES`:\n {}",
unlisted.join("\n ")
);
let undispatched: Vec<&str> = listed
.iter()
.copied()
.filter(|name| !dispatched.iter().any(|(v, _)| v == name))
.collect();
assert!(
undispatched.is_empty(),
"these `PROBE_NAMES` rows are not dispatched on anywhere (`var(\"WOW_PROBE\")… == \
Ok(\"<name>\")`):\n {}",
undispatched.join("\n ")
);
}
/// Every `.rs` under this crate's `src/`, as `(path under src/, text)`.
fn crate_sources() -> Vec<(String, String)> {
let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let mut out = Vec::new();
let mut stack = vec![src.clone()];
while let Some(dir) = stack.pop() {
for entry in std::fs::read_dir(&dir).expect("crate source dir is readable") {
let path = entry.expect("dir entry").path();
if path.is_dir() {
stack.push(path);
} else if path.extension().is_some_and(|e| e == "rs") {
let rel = path
.strip_prefix(&src)
.expect("under src")
.to_string_lossy()
.replace('\\', "/");
let text = std::fs::read_to_string(&path).expect("source is readable");
out.push((rel, text));
}
}
}
out.sort();
out
}
/// The source with every comment line (`//`, `///`, `//!`) dropped — the scans below want
/// code, and a doc comment quoting a variable is not a read of it.
fn code_lines(text: &str) -> String {
text.lines()
.filter(|l| !l.trim_start().starts_with("//"))
.collect::<Vec<_>>()
.join("\n")
}
/// Every `"WOW_PROBE[A-Z0-9_]*"` string literal in the code — the whole literal, quote to
/// quote, so a variable named inside a longer message is not a match.
fn probe_literals(text: &str) -> Vec<String> {
let code = code_lines(text);
let mut out = Vec::new();
let mut rest = code.as_str();
while let Some(i) = rest.find("\"WOW_PROBE") {
let after = &rest[i + 1..];
let end = after
.find(|c: char| !(c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_'))
.unwrap_or(after.len());
if after[end..].starts_with('"') {
out.push(after[..end].to_string());
}
rest = &rest[i + 1..];
}
out
}
/// Every value the code compares `WOW_PROBE` against: `var("WOW_PROBE")` followed, within the
/// same expression, by `Ok("<value>")`.
fn dispatched_probe_values(text: &str) -> Vec<String> {
let code = code_lines(text);
let needle = "var(\"WOW_PROBE\")";
let mut out = Vec::new();
let mut rest = code.as_str();
while let Some(i) = rest.find(needle) {
let after = &rest[i + needle.len()..];
// The comparison sits right after the read: `.as_deref() == Ok("…")`. Bound the
// look-ahead so a bare `is_ok()` gate is not paired with some later `Ok("…")`.
let window = &after[..after.len().min(48)];
if let Some(j) = window.find("Ok(\"") {
let value = &after[j + 4..];
if let Some(k) = value.find('"') {
out.push(value[..k].to_string());
}
}
rest = after;
}
out
}
}
@@ -211,7 +211,6 @@ fn report(label: &str, expect: u32, seen: Option<u32>) -> u32 {
}
}
#[allow(clippy::too_many_arguments)]
fn goquest_probe(
time: ProbeClock,
mut probe: ResMut<GoQuestProbe>,
@@ -73,7 +73,6 @@ enum Phase {
}
// One Bevy system's full input set (the taxi-probe shape).
#[allow(clippy::too_many_arguments)]
fn guard_poi_probe(
time: ProbeClock,
mut probe: ResMut<GuardPoiProbe>,
+1 -1
View File
@@ -110,7 +110,7 @@ impl Plugin for ProbeLookPlugin {
drive_probe_look
.in_set(WorldStage::Input)
.before(crate::player::PlayerControlSet)
.run_if(in_state(crate::char_select::ClientState::InWorld)),
.in_set(crate::char_select::InWorldGated),
);
}
}
@@ -222,7 +222,6 @@ fn last_event(script: &UiScript) -> String {
.unwrap_or_default()
}
#[allow(clippy::too_many_arguments)]
fn mail_probe(
time: ProbeClock,
mut probe: ResMut<MailProbe>,
@@ -203,7 +203,6 @@ fn shoot(commands: &mut Commands, name: &str) {
.observe(save_to_disk(path));
}
#[allow(clippy::too_many_arguments)]
fn model_camera_probe(
mut commands: Commands,
time: ProbeClock,
@@ -126,7 +126,7 @@ impl Plugin for ProbePitchPlugin {
drive_probe_pitch
.in_set(WorldStage::Input)
.before(crate::player::PlayerControlSet)
.run_if(in_state(crate::char_select::ClientState::InWorld)),
.in_set(crate::char_select::InWorldGated),
);
}
}
@@ -295,7 +295,6 @@ struct Rig {
}
/// The whole rig: find-or-create the body at select, enter as it, then apply the state batch.
#[allow(clippy::too_many_arguments)]
fn drive_rig(
mut rig: ResMut<Rig>,
mut roster: ResMut<Roster>,
@@ -340,7 +340,6 @@ fn open_window(
None
}
#[allow(clippy::too_many_arguments)]
fn service_probe(
time: ProbeClock,
mut probe: ResMut<ServiceProbe>,
+1 -1
View File
@@ -145,7 +145,7 @@ fn load_expectations(mut probe: ResMut<TaxiProbe>, world_assets: Option<Res<Worl
}
// One Bevy system's full input set (the crossing-probe shape) + its self query tuple.
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
#[allow(clippy::type_complexity)]
fn taxi_probe(
time: ProbeClock,
mut probe: ResMut<TaxiProbe>,
@@ -163,7 +163,6 @@ fn clear_log(script: &UiScript) {
}
}
#[allow(clippy::too_many_arguments)]
fn vendor_swap_probe(
time: ProbeClock,
mut probe: ResMut<VendorSwapProbe>,
@@ -82,7 +82,6 @@ type DressQuery = (
);
/// One line per streamed player within [`DEFAULT_RADIUS`], contradictions first.
#[allow(clippy::too_many_arguments)]
fn fire_dress_census(
mut probe: ResMut<DressCensus>,
time: ProbeClock,
@@ -114,7 +114,6 @@ type CensusQuery = (
/// One census line per [`ground_derived`] body within [`GroundCensus::radius`] of ours, worst drop
/// first, under a summary line naming the count that matters.
#[allow(clippy::too_many_arguments)] // one Bevy system's full input set
fn fire_ground_census(
mut probe: ResMut<GroundCensus>,
time: ProbeClock,
@@ -242,7 +242,6 @@ fn palette_probes(rows: &[Mat4], binds: &[Vec3], radii: &[f32]) -> Vec<Vec3> {
}
/// One `JIT` line per frame for the subject: the three terms' Δ and Δ², in mm and in pixels.
#[allow(clippy::too_many_arguments)] // one param per term the reading has to separate
fn sample_jitter(
mut meter: ResMut<JitterMeter>,
time: ProbeClock,
@@ -204,7 +204,6 @@ struct SamplePin<'w, 's> {
/// Wait for in-world + the delay, uncap, warm, sample, print, exit — the live twin of the
/// harness probe's `Phase::ProbeWarmup`/`Probing` arms.
#[allow(clippy::too_many_arguments)]
fn drive_live_fps(
mut probe: ResMut<LiveFps>,
time: Res<Time<bevy::time::Real>>,
@@ -72,7 +72,6 @@ impl Plugin for RevealAuditPlugin {
}
}
#[allow(clippy::too_many_arguments)]
fn drive_reveal_audit(
mut audit: ResMut<RevealAudit>,
progress: Res<WorldLoadProgress>,
@@ -120,7 +120,6 @@ type VisualQuery = (
/// One line per streamed entity within [`UnitVisuals::radius`] of the body — cubes first, then
/// everything else — under a summary line naming the count that matters.
#[allow(clippy::too_many_arguments)] // one Bevy system's full input set
fn fire_unit_visuals(
mut probe: ResMut<UnitVisuals>,
time: ProbeClock,
@@ -379,7 +379,6 @@ fn class_file(class: u8) -> &'static str {
/// Its own resource rather than a [`CreateSelection`] field on purpose: ticking it there would trip
/// that resource's change detection every frame and defeat `refresh_dynamic`'s `is_changed` gate,
/// re-running the whole dial/panel/icon refresh 60× a second.
#[allow(clippy::too_many_arguments)]
fn create_input(
buttons: Query<(Entity, &CreateAction)>,
clicks: Res<crate::glue::GlueClicks>,
@@ -23,7 +23,7 @@ use crate::glue::widgets::{FallbackFace, GlueDisabled, Hilight, HoverLabel, Lock
/// Refill everything that follows the selection — icon rects, dial labels, info texts, faction
/// tints, class-slot mapping — on selection change or a fresh spawn.
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
#[allow(clippy::type_complexity)]
pub(super) fn refresh_dynamic(
sel: Res<CreateSelection>,
catalog: Option<Res<CharCreate>>,
@@ -224,7 +224,7 @@ pub(super) fn refresh_dynamic(
/// latch while a create is in flight. The screen-agnostic passes — up/down art swaps, the glue
/// buttons' art + caption color, outline mirroring — are [`crate::glue`]'s, registered beside
/// this in the plugin chain.
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
pub(super) fn refresh_hover(
sel: Res<CreateSelection>,
catalog: Option<Res<CharCreate>>,
@@ -37,7 +37,6 @@ const SCREEN_Z: i32 = 1100;
// ── Spawn ────────────────────────────────────────────────────────────────────────────────────────
#[allow(clippy::too_many_arguments)]
pub(super) fn enter_create(
mut commands: Commands,
assets: Res<AssetServer>,
@@ -572,7 +572,6 @@ pub(super) struct ScrollBand;
///
/// Ordered before the select screen's own click handling so a click that lands on the panel is
/// never also read as a click on the screen behind it.
#[allow(clippy::too_many_arguments)]
pub(super) fn drive_addons_panel(
mut commands: Commands,
mut panel: ResMut<AddonsPanel>,
@@ -1599,7 +1598,6 @@ fn spawn_panel(
/// the row's TOPLEFT at (−14, 0), spawned as the row strip's child so the anchor is structural.
/// `## URL` rides as an extra line (1197: information, not a launch button). A MIXED checkbox
/// hover shows `ENABLED_FOR_SOME` alone — the reference's GlueTooltip split, on the same box.
#[allow(clippy::too_many_arguments)]
fn spawn_tooltip(
parent: &mut ChildSpawnerCommands,
art: &GlueArt,
+1 -1
View File
@@ -88,7 +88,7 @@ struct DialogUi;
/// Okay (enabled only while the typed text matches) sends the delete; Cancel/Esc close; Enter
/// confirms when armed. Runs before the list refresh so a successful delete's roster update
/// repaints the same frame it lands.
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
#[allow(clippy::type_complexity)]
pub(super) fn drive_delete_dialog(
mut commands: Commands,
mut dialog: ResMut<DeleteDialog>,
+1 -1
View File
@@ -24,7 +24,7 @@ const DOUBLE_CLICK_SECS: f32 = 0.4;
/// back/escape (return to the login screen — decision 0539, retiring 0465 §6's exit-the-client
/// collapse), arrow-key cycling. Inert while the delete dialog is up (it owns the keyboard and
/// sits over the buttons).
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
#[allow(clippy::type_complexity)]
pub(super) fn select_input(
buttons: Query<(Entity, &SelectAction)>,
clicks: Res<crate::glue::GlueClicks>,
+57 -14
View File
@@ -65,6 +65,20 @@ pub(crate) enum ClientState {
InWorld,
}
/// **The one "only while in the world" gate** (decision 2265 §C3). A system that has no business
/// at a glue screen — an input reader, a world-packet feed, a per-character file watcher — joins
/// this set instead of carrying its own `run_if(in_state(ClientState::InWorld))`: twenty-odd
/// copies of that condition said one thing in twenty-odd places, and the condition a set carries
/// ANDs with a member's own, so a member keeps whatever else it needs (the controller's
/// capture-mode gate, say) and the conversion is exact.
///
/// Configured once, in [`CharSelectPlugin`] — the state's home — for `Update`, the only schedule
/// a member lives in. A member in another schedule means configuring the set *there* as well
/// (a set's conditions are per schedule; an unconfigured set gates nothing), never a fresh
/// `run_if` at the site.
#[derive(SystemSet, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct InWorldGated;
/// The character-select subsystem: the state machine + the select screen.
pub(crate) struct CharSelectPlugin {
/// The screen this session opens on.
@@ -94,6 +108,8 @@ fn publish_world_live(
impl Plugin for CharSelectPlugin {
fn build(&self, app: &mut App) {
app.insert_state(self.start)
// The in-world gate, configured beside the state it reads (see [`InWorldGated`]).
.configure_sets(Update, InWorldGated.run_if(in_state(ClientState::InWorld)))
.init_resource::<Roster>()
// **The engine's world-existence bit** (1160's wire (b)): the session owner is this
// module, so this module tells the world whether there is one. Ordered ahead of every
@@ -105,8 +121,14 @@ impl Plugin for CharSelectPlugin {
)
.init_resource::<dialog::DeleteDialog>()
.init_resource::<addons::AddonsPanel>()
.add_systems(OnEnter(ClientState::CharSelect), screen::enter_select)
.add_systems(OnExit(ClientState::CharSelect), screen::exit_select)
.add_systems(
OnEnter(ClientState::CharSelect),
(screen::enter_select, stamp_select_entry),
)
.add_systems(
OnExit(ClientState::CharSelect),
(screen::exit_select, clear_select_entry),
)
.add_systems(Update, (debug_glue_roundtrip, debug_logout_smoke))
.add_systems(
Update,
@@ -783,7 +805,6 @@ fn debug_glue_roundtrip(
/// hand-written `AppExit` from `Update` skipped that question for this smoke's whole life, and the
/// bug it would have caught — every saved variable, every addon file and the camera pose lost on
/// every window close — shipped underneath it.
#[allow(clippy::too_many_arguments)] // a smoke test that drives the whole round trip
fn debug_logout_smoke(
state: Res<State<ClientState>>,
player: Res<crate::player::Player>,
@@ -822,7 +843,7 @@ fn debug_logout_smoke(
// world-live falling edge, not the state edge), so the probe was reading the count before
// the thing it is checking had happened, and reporting the world it had just left.
//
// It cost more than a wrong line: 1291 wrote the reading up as a live contradiction of
// It cost more than a wrong line: 2277 wrote the reading up as a live contradiction of
// 0777's release-world claim, on the strength of it reproducing identically on an older
// commit — which it did, because an instrument that measures too early does that reliably.
// A number that is always wrong teaches everyone to skip the line (0777's own lesson about
@@ -844,7 +865,7 @@ fn debug_logout_smoke(
},
4 if *state.get() == ClientState::InWorld && player.active && now - *mark > 3.0 => {
// **The suppressor reading is the point of the second entry, not a decoration on it**
// (B306, decision 1542). This leg has crossed the boundary on every run since 1291 and
// (B306, decision 1542). This leg has crossed the boundary on every run since 2277 and
// could only ever report that it *happened* — tiles, UI rebuilds, error counts — none
// of which a character who re-entered unable to move would disturb. `scripts/smoke.sh`
// fails on anything but `none`, and separately reports whether the run's logout was
@@ -954,6 +975,31 @@ fn debug_select_walk(
}
}
/// When the select screen came up: `Time::elapsed_secs()` at `OnEnter(CharSelect)`, gone again
/// at `OnExit`. The one clock the screen's "a few seconds after the screen is up" instruments
/// ([`debug_select_dialog`], [`debug_select_addons`], [`debug_select_shot`]) measure from, in
/// place of a `Local` each stamped on its own first run (decision 2265 §C3). Those three run
/// under `in_state(CharSelect)` in `Update`, so the resource is always there when they read it.
#[derive(Resource, Clone, Copy)]
struct CharSelectEnteredAt(f32);
impl CharSelectEnteredAt {
/// Seconds the screen has been up.
fn elapsed(&self, time: &Time) -> f32 {
time.elapsed_secs() - self.0
}
}
/// `OnEnter(CharSelect)`: stamp the screen's entry.
fn stamp_select_entry(mut commands: Commands, time: Res<Time>) {
commands.insert_resource(CharSelectEnteredAt(time.elapsed_secs()));
}
/// `OnExit(CharSelect)`: the screen is down, so is its clock.
fn clear_select_entry(mut commands: Commands) {
commands.remove_resource::<CharSelectEnteredAt>();
}
/// The shot instrument's delete-dialog dial (`WOW_CHARSELECT_DIALOG=<typed>`): open the
/// typed-confirm dialog for the selected character a few seconds after the screen is up, with
/// `<typed>` pre-typed (may be empty) — so the dialog's geometry (the ChatInputBorder edit box,
@@ -963,7 +1009,7 @@ fn debug_select_dialog(
roster: Res<Roster>,
mut dialog: ResMut<dialog::DeleteDialog>,
time: Res<Time>,
mut entered_at: Local<Option<f32>>,
entered_at: Res<CharSelectEnteredAt>,
mut done: Local<bool>,
) {
if *done {
@@ -973,8 +1019,7 @@ fn debug_select_dialog(
*done = true;
return;
};
let start = *entered_at.get_or_insert(time.elapsed_secs());
if time.elapsed_secs() - start < 4.0 {
if entered_at.elapsed(&time) < 4.0 {
return;
}
let Some(c) = roster.selected_char() else {
@@ -995,7 +1040,7 @@ fn debug_select_addons(
roster: Res<Roster>,
mut panel: ResMut<addons::AddonsPanel>,
time: Res<Time>,
mut entered_at: Local<Option<f32>>,
entered_at: Res<CharSelectEnteredAt>,
mut done: Local<bool>,
) {
if *done {
@@ -1005,8 +1050,7 @@ fn debug_select_addons(
*done = true;
return;
}
let start = *entered_at.get_or_insert(time.elapsed_secs());
if time.elapsed_secs() - start < 4.0 {
if entered_at.elapsed(&time) < 4.0 {
return;
}
if roster.chars.is_empty() {
@@ -1041,7 +1085,7 @@ const SELECT_SHOT_AT: f32 = 8.0;
fn debug_select_shot(
mut commands: Commands,
time: Res<Time>,
mut entered_at: Local<Option<f32>>,
entered_at: Res<CharSelectEnteredAt>,
mut done: Local<bool>,
) {
if *done {
@@ -1055,8 +1099,7 @@ fn debug_select_shot(
.ok()
.and_then(|v| v.parse::<f32>().ok())
.unwrap_or(SELECT_SHOT_AT);
let start = *entered_at.get_or_insert(time.elapsed_secs());
if time.elapsed_secs() - start < at {
if entered_at.elapsed(&time) < at {
return;
}
use bevy::render::view::screenshot::{save_to_disk, Screenshot};
@@ -18,7 +18,7 @@ use super::{class_name, Roster};
/// Refill the row texts + visibility, the selected name, and the realm banner whenever the roster
/// changes (a fresh enum, a selection move) — or when the screen was just (re)spawned (returning
/// from the create screen finds an unchanged roster; the fresh, empty tree must still fill).
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
pub(super) fn refresh_list(
roster: Res<Roster>,
areas: Option<Res<AreaTableRes>>,
@@ -98,7 +98,6 @@ pub(super) fn enter_select(mut preview: ResMut<GluePreview>) {
/// Spawn the screen tree once its prerequisites exist — and upgrade an artless early spawn the
/// moment the client art lands (despawn + respawn; the tree is cheap and static). With no client
/// data at all the artless tree still spawns after a short grace (the graceful-absence posture).
#[allow(clippy::too_many_arguments)]
pub(super) fn materialize_screen(
mut commands: Commands,
existing: Query<(Entity, &CharSelectUi)>,
@@ -148,7 +147,6 @@ pub(super) fn materialize_screen(
}
}
#[allow(clippy::too_many_arguments)]
fn spawn_screen(
commands: &mut Commands,
assets: &AssetServer,
+1 -2
View File
@@ -376,7 +376,7 @@ fn load_bubble_art(
/// pieces, tail, wrapped chatType-colored text — bottom-seated on the projected anchor.
/// Runs after [`VPlateSet`] (the spawn gate reads this frame's plate verdict), inside the
/// [`UiQuadAppend`] window.
#[allow(clippy::too_many_arguments, clippy::type_complexity)] // one Bevy system's full input set
#[allow(clippy::type_complexity)] // one Bevy system's full input set
fn drive_bubbles(
mut queue: ResMut<BubbleQueue>,
mut bubbles: ResMut<Bubbles>,
@@ -609,7 +609,6 @@ struct Pending {
/// Append one bubble's draw list: the Backdrop pieces (bg fill inset by the border-unit +
/// the 8-piece edge), the tail square, and the wrapped, centered, chatType-colored text.
#[allow(clippy::too_many_arguments)] // the draw inputs + the jitter trace's decomposition
fn draw_bubble(
atlas: &mut UiFontAtlas,
quads: &mut UiQuads,
@@ -233,7 +233,6 @@ fn free_slot(texts: &[WorldText], anchor: Entity) -> Option<u8> {
/// living into glyph quads appended to [`UiQuads`] after the script extract. Ordering (the
/// [`UiQuadAppend`] set, after [`UiInput`]) guarantees the mesh rebuild never lands between the
/// script's replace and our append — see `ui_pass`.
#[allow(clippy::too_many_arguments)] // one Bevy system's full input set
pub(crate) fn float_combat_text(
mut spawns: MessageReader<CombatTextSpawn>,
mut texts: ResMut<WorldTexts>,
-1
View File
@@ -175,7 +175,6 @@ impl Cooldowns {
/// used to do made the GO insert of any cooldown-carrying spell (Frost Nova) overwrite its
/// own running GCD node — the whole bar's pie flashed and died ~100 ms after the press,
/// while cooldown-less spells (Arcane Explosion) kept theirs (decision 0947).
#[allow(clippy::too_many_arguments)] // the SPELLHISTORY node's own field list
fn add(
&mut self,
spell_id: u32,
+236
View File
@@ -0,0 +1,236 @@
//! **A crash leaves an artefact** (decision 2266 §B2).
//!
//! Until this module, a panic printed to stderr and the process was gone — and with it everything
//! a reporter could have attached. The only log sink was the terminal; a packaged Windows build
//! has no console; of bug B390's four reporters one attached a backtrace, because he had set
//! `RUST_BACKTRACE=1` on his own. `shutdown.rs` says the other half out loud: *"A crash, a
//! `SIGKILL`, a wedged teardown. Nothing survives those."*
//!
//! [`install`] chains a panic hook in front of Rust's default one. The default still prints to
//! stderr exactly as before (so every log a session already greps keeps reading the same); then
//! the report goes to **`benilla-config/Diagnostics/crash-<unix-seconds>.txt`**, resolved through
//! [`crate::local_state`] like every other file benilla writes (the one-folder rule, 0954/1175 —
//! never a platform log dir, never beside the install). The file carries: the build id (the sha
//! is the version, `build_id`), the time and the uptime, the thread, the panic's location and
//! payload, a backtrace captured **regardless of `RUST_BACKTRACE`**, and the last
//! [`benilla_world::log_ring::CAPACITY`] log lines — the minute before, which is what turns a
//! symptom into a story. The path is printed to stderr as the last line, so "attach the file it
//! names" is the whole instruction to a reporter.
//!
//! **What it does not cover, said out loud.** A non-unwinding panic — an allocation failure's
//! abort, a `panic` inside a `Drop` during unwinding, a foreign frame — never reaches any hook;
//! B390's own was one (`thread caused non-unwinding panic. aborting.`). The hook covers the
//! unwinding class (B88, B130, B260, B383's shape: four of the six panics the ledger holds).
//! Flushing saved variables and the CVar diff from inside a panic is a second, riskier item —
//! the world is half-torn by then — and is deliberately not bundled here (2265 §B2).
//!
//! A hermetic capture run (`local_state::home()` answers `None`) writes no file and loses
//! nothing: the harness already has stderr.
use std::fmt::Write as _;
use std::panic::PanicHookInfo;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use crate::BuildId;
/// Chain the crash-report hook in front of the current panic hook. Called once, from
/// [`crate::run`], before the `App` exists — a panic while plugins build is still a crash.
pub(crate) fn install(build: BuildId) {
let started = Instant::now();
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
previous(info);
report(info, build, started.elapsed());
}));
}
/// `WOW_CRASH_INJECT=<at_secs>` — one deliberate main-thread panic, mid-run: the standing test
/// affordance for this module, in the shape of `perf::stall`'s injectors and armed beside them.
/// The end-to-end falsifier is a run with it set: the process must die with a
/// `crash-<unix>.txt` whose log tail ends in the `crash-inject` line below.
pub(crate) fn arm_injector(app: &mut bevy::prelude::App) {
use bevy::prelude::*;
if let Some(at) = std::env::var("WOW_CRASH_INJECT")
.ok()
.and_then(|v| v.parse::<f32>().ok())
{
app.insert_resource(CrashInject { at });
app.add_systems(Update, crash_inject);
}
}
#[derive(bevy::prelude::Resource)]
struct CrashInject {
at: f32,
}
fn crash_inject(
inject: bevy::prelude::Res<CrashInject>,
time: bevy::prelude::Res<bevy::prelude::Time<bevy::time::Real>>,
) {
if time.elapsed_secs() >= inject.at {
bevy::log::warn!(
"crash-inject: panicking the main thread at {:.1} s",
inject.at
);
panic!("crash-inject: deliberate panic (WOW_CRASH_INJECT)");
}
}
/// Re-entrancy latch: a panic *inside* the hook (a poisoned lock, a failed write) must not
/// recurse into it. Never cleared on purpose — after one report the process is on its way out,
/// and a second panic's report would only overwrite the useful one with a worse one.
static REPORTING: AtomicBool = AtomicBool::new(false);
fn report(info: &PanicHookInfo<'_>, build: BuildId, uptime: Duration) {
if REPORTING.swap(true, Ordering::SeqCst) {
return;
}
let Some(dir) = crate::local_state::diagnostics_dir() else {
return;
};
let payload = if let Some(s) = info.payload().downcast_ref::<&str>() {
(*s).to_owned()
} else if let Some(s) = info.payload().downcast_ref::<String>() {
s.clone()
} else {
"<non-string panic payload>".to_owned()
};
let location = info
.location()
.map(|l| format!("{}:{}:{}", l.file(), l.line(), l.column()))
.unwrap_or_else(|| "<unknown>".to_owned());
let thread = std::thread::current();
let text = render(&Report {
build,
unix: SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0),
uptime,
thread: thread.name().unwrap_or("<unnamed>"),
location: &location,
payload: &payload,
backtrace: &std::backtrace::Backtrace::force_capture().to_string(),
log: &benilla_world::log_ring::recent(),
});
if let Some(path) = write(&dir, &text) {
eprintln!("crash report written to {}", path.display());
}
}
/// Everything the report says, gathered before rendering so the renderer is a pure function the
/// test can drive without panicking.
struct Report<'a> {
build: BuildId,
unix: u64,
uptime: Duration,
thread: &'a str,
location: &'a str,
payload: &'a str,
backtrace: &'a str,
log: &'a [String],
}
fn render(r: &Report<'_>) -> String {
let mut out = String::new();
let _ = writeln!(out, "benilla crash report");
let _ = writeln!(
out,
"build: {} (sha {})",
r.build.summary(),
r.build.sha
);
let _ = writeln!(
out,
"time: {} unix, {:.1} s after launch",
r.unix,
r.uptime.as_secs_f64()
);
let _ = writeln!(
out,
"platform: {} {}",
std::env::consts::OS,
std::env::consts::ARCH
);
let _ = writeln!(out, "thread: {}", r.thread);
let _ = writeln!(out, "at: {}", r.location);
let _ = writeln!(out, "panic: {}", r.payload);
let _ = writeln!(out, "\nbacktrace:\n{}", r.backtrace);
let _ = writeln!(out, "last {} log lines (oldest first):", r.log.len());
for line in r.log {
let _ = writeln!(out, "{line}");
}
out
}
/// Write the report under `dir` as `crash-<unix>.txt`, creating the folder on demand (an empty
/// `Diagnostics/` on every run would be this instrument advertising itself, `perf::stall`'s
/// reason). Plain `fs::write`, not `local_state::write_atomic`: the process is dying, and a
/// half-written report beats none.
fn write(dir: &Path, text: &str) -> Option<PathBuf> {
let unix = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let path = dir.join(format!("crash-{unix}.txt"));
std::fs::create_dir_all(dir).ok()?;
std::fs::write(&path, text).ok()?;
Some(path)
}
#[cfg(test)]
mod tests {
use super::*;
const BUILD: BuildId = BuildId {
sha: "0123456789abcdef0123456789abcdef01234567",
short: "0123456",
date: "2026-09-16",
profile: "debug",
};
/// The report names the build, the place, the payload and the log — the four things a
/// reporter cannot reconstruct after the fact — and lands in the folder it was given.
#[test]
fn a_report_carries_the_build_the_place_the_payload_and_the_log() {
let log = vec!["+ 1.000s INFO benilla_app::net: entered world".to_owned()];
let text = render(&Report {
build: BUILD,
unix: 1_800_000_000,
uptime: Duration::from_millis(12_345),
thread: "main",
location: "crates/benilla-app/src/net/apply.rs:100:5",
payload: "index out of bounds: the len is 3 but the index is 7",
backtrace: " 0: benilla_app::net::apply::apply_net_updates",
log: &log,
});
for needle in [
"sha 0123456789abcdef0123456789abcdef01234567",
"0123456 · 2026-09-16 · debug",
"12.3 s after launch",
"thread: main",
"at: crates/benilla-app/src/net/apply.rs:100:5",
"panic: index out of bounds: the len is 3 but the index is 7",
"apply_net_updates",
"last 1 log lines",
"entered world",
] {
assert!(text.contains(needle), "missing {needle:?} in:\n{text}");
}
let dir = std::env::temp_dir().join(format!("benilla-crash-test-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let path = write(&dir, &text).expect("report written");
assert!(path.starts_with(&dir), "{}", path.display());
assert!(path
.file_name()
.unwrap()
.to_string_lossy()
.starts_with("crash-"));
assert_eq!(std::fs::read_to_string(&path).unwrap(), text);
let _ = std::fs::remove_dir_all(&dir);
}
}
-2
View File
@@ -390,7 +390,6 @@ pub(crate) fn stop_attack_local(
/// `0x6131d9`). `0x5ecb70`'s own validator legs — the target's alive-or-feign + `CanAttack
/// 0x606980` walk, and the `[0xb4b3e4]` world gate — stay with the callers that already compute
/// them (`target::scan`'s `new_attackable`, the drain's `attack_actor_refusal`).
#[allow(clippy::too_many_arguments)] // the tail writes four sinks; the alternative is a bundle
pub(crate) fn start_attack_local(
entity: Entity,
target: u64,
@@ -444,7 +443,6 @@ pub(crate) fn start_attack_local(
/// `[0xb4b3e4]` — the reference's second condition on the stop arm, a world/session global also
/// tested at `0x5ecbc0` — is unmodelled; nothing in benilla can be false there while a press is
/// being drained.
#[allow(clippy::too_many_arguments)] // the seams' write set, minus the bundle a caller can't hold
pub(crate) fn toggle_attack_local(
entity: Entity,
target: u64,
@@ -68,7 +68,6 @@ pub(super) fn load_blood_tables(mut commands: Commands, assets: Option<Res<World
/// `info` and every fired spurt at `debug`, so "I never see blood" localizes to a link in one
/// fight instead of a code audit (no drop lines at all ⇒ the break is upstream, in the
/// [`SwingImpact`] feed itself).
#[allow(clippy::too_many_arguments)] // one Bevy system's full input set
pub(super) fn blood_spurts(
mut swings: MessageReader<SwingImpact>,
transforms: Query<&Transform>,
@@ -141,7 +141,6 @@ pub(super) struct LastPuff(EntityHashMap<f32>);
///
/// A unit seen for the first time has no [`BreathEnv`] and is classified at once, so a unit that
/// streams in inside a cold zone breathes on its next idle loop rather than up to 10 s later.
#[allow(clippy::too_many_arguments)] // one system's full input set
pub(super) fn classify_breath(
mut commands: Commands,
time: Res<Time>,
@@ -184,7 +183,6 @@ pub(super) fn classify_breath(
/// off the mount — so the **puff goes on the event's own entity** (its mouth), while the unit
/// state the ladder reads (drunk, the environment) comes from the composite's **root**, exactly
/// as the footfall visuals split them.
#[allow(clippy::too_many_arguments)] // one system's full input set
pub(super) fn fire_breath(
mut events: MessageReader<AnimSoundEvent>,
time: Res<Time>,
@@ -263,7 +263,7 @@ fn transplant_up(
/// enter/loop/exit the Special states (jump, sit/sleep/kneel) as one-shot-bracketed loops, play the
/// per-packet melee swings as preemptible one-shots, and cross-fade the gaits (the engaged Ready
/// idle among them).
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
pub(super) fn drive_animations(
mut commands: Commands,
mut units: Query<(
@@ -579,7 +579,7 @@ pub(super) fn run(
// gait it WRAPPED to its head at completion — the frames + cross-fade
// against the restarted reach-to-quiver were the director's "jumps
// back to the start the moment it gets fully pulled", in every build
// that replayed a pull (trace-caught, decision 0412). The clamp IS the
// that replayed a pull (trace-caught, decision 2276). The clamp IS the
// drawn pose; nothing follows it (0994).
// Loot 50 is likewise authored clamp — one 0.5 s kneel-down that must
// FREEZE in the rummage pose; as Forever it would wrap back to standing
@@ -237,7 +237,6 @@ pub(super) fn roll_loop<'a>(
/// decision 0123) **and its budget** ([`roll_loop`] — decision 0516), publishing the armed
/// `(node, R)` into `window` for the watchdog's advance; a one-shot arm clears it (its budget is
/// the `Count` repeat — no window outlives the arm).
#[allow(clippy::too_many_arguments)] // the resolve+roll+play primitive's full input set
pub(super) fn play(
lock: &mut BaseAnimLock,
tr: &mut AnimationTransitions,
@@ -382,7 +381,6 @@ pub(super) fn holds_own_clip(
/// and settles through [`Mode::Entering`]; **Fall has no enter** — the client plays the Fall(40)
/// loop directly the tick FALLINGFAR latches (`0x602c40`) — so it goes straight to
/// [`Mode::Looping`] with a looping play.
#[allow(clippy::too_many_arguments)]
pub(super) fn enter_special(
lock: &mut BaseAnimLock,
sp: Special,
@@ -434,7 +432,6 @@ pub(super) fn enter_special(
/// started moving drops straight to the gait, letting the cross-fade carry the half-pose into the
/// walk; an airborne state landing plays its [`jump_land_pick`]; otherwise `sp` plays its graceful
/// exit one-shot, which [`super::drive_animations`] then waits out. Returns the mode to adopt.
#[allow(clippy::too_many_arguments)]
pub(super) fn leave_special(
lock: &mut BaseAnimLock,
sp: Special,
@@ -115,7 +115,6 @@ pub(super) fn wound_evict(
/// sheath reconcile and the event scan. `id` is the wound anim to lay (8–10), already resolved
/// by the caller — melee by severity/engagement ([`select::wound_anim`]), a spell impact by its
/// kit's own column ([`WoundEdge`]).
#[allow(clippy::too_many_arguments)] // the trigger's full live-state + rng input set
pub(super) fn wound_trigger(
entity: Entity,
drv: &mut AnimDriver,
@@ -144,7 +144,6 @@ fn is_whiff(victim_state: u32) -> bool {
/// Cache swings, consume impact tags, dispatch `$CPP` defenses, flush on supersede/attack-stop,
/// silently drop records whose attacker despawned (the client's dtor clears without flushing).
/// Ordered after [`super::events::fire_anim_events`] so a tag fires the same frame it's crossed.
#[allow(clippy::too_many_arguments)]
pub(super) fn route_swing_impacts(
mut swings: MessageReader<SwingMessage>,
mut events: MessageReader<AnimSoundEvent>,
+1 -1
View File
@@ -90,7 +90,7 @@ const FALLBACK_RADIUS: f32 = 6.0;
/// frame. Exempt: the body we drive (the camera rides its attachment-17 pivot, and it must keep
/// animating faded-out in first person) and its mount child. `WOW_NO_ANIM_LOD=1` disables
/// parking — the live-probe A/B lever; `WOW_NO_ROOM_LOD=1` disables only the room leg.
#[allow(clippy::type_complexity, clippy::too_many_arguments)] // one Bevy system's full input set
#[allow(clippy::type_complexity)] // one Bevy system's full input set
pub(super) fn gate_rig_animation(
time: Res<Time>,
cam: Query<&Frustum, With<WorldCamera>>,
@@ -341,7 +341,6 @@ fn arm_leg(
/// gets at most one masked overlay, and [`VisualSheath`] holds that arm's pre-swap placement until
/// its clip reaches the authored `$SHL`/`$SHR` moment. An arm whose leg has no playable clip
/// snaps; no arm playing anything at all → no ceremony, and the whole transition snaps.
#[allow(clippy::too_many_arguments)]
pub(super) fn start_sheath_ceremony(
commands: &mut Commands,
entity: Entity,
@@ -380,7 +379,6 @@ pub(super) fn start_sheath_ceremony(
/// [`sheath_phase2`] so it can reach for the new weapon. The ceremony ends when every arm has run
/// out of legs, at which point [`VisualSheath`] is dropped and the resolver falls back to the
/// committed state.
#[allow(clippy::too_many_arguments)]
pub(super) fn advance_sheath_ceremony(
commands: &mut Commands,
entity: Entity,
@@ -765,7 +765,6 @@ fn play_impact(
/// have no packet, decision 0099): field set → the **channel** kit's anim as the hold + its
/// sound once at start; field cleared → hold drops. The per-entity edge cache is the dedup the
/// client gets from its per-tick armed-id guard — a held channel never restarts its clip.
#[allow(clippy::too_many_arguments)]
pub(super) fn route_cast_visuals(
mut commands: Commands,
mut events: MessageReader<CastEvent>,
@@ -1323,7 +1322,6 @@ pub(super) fn route_cast_visuals(
/// exactly that bug: Stealth's kit 312 carries no models, no anim and no attach at all (its whole
/// visual is one proc-14 CharProc), so an effects-only test dropped it and the character showed
/// nothing.
#[allow(clippy::too_many_arguments)] // one Bevy system's full input set
pub(crate) fn arm_aura_state_fx(
// The slot diff below is a pure function of the store's aura fields, so it re-runs only when
// the store was written — `arm_level_up_fx`'s idiom (decision 1357's sibling gate): at the
@@ -1748,7 +1746,6 @@ pub(super) fn arm_morph_latch(
/// cold-cache shift-in — whose SPELL_GO impact instance was still PENDING at the drain and thus
/// survived — briefly runs that instance and the replay's twin together (one denser cloud, once
/// per session per model; the warm-cache GO instance dies in the drain like the reference's).
#[allow(clippy::too_many_arguments)] // one Bevy system's full input set
pub(super) fn replay_morph_kit(
mut swaps: MessageReader<crate::entities::DisplaySwapped>,
visuals: Option<Res<SpellVisuals>>,
-2
View File
@@ -388,7 +388,6 @@ mod other {
/// Each frame: while a cursor payload with a resolved icon is held, show ITS 32×32 hardware
/// cursor (decoded/downsampled on first use, then cached by path); otherwise swap to the
/// classified mode (base-stem fallback, then Point, then OS) — unchanged from before 0216 §5.
#[allow(clippy::too_many_arguments)]
pub(super) fn drive(
mut commands: Commands,
cursor: Res<super::DisplayedCursor>,
@@ -543,7 +542,6 @@ mod macos {
/// §5, decoded/downsampled to 32×32 on first use and cached by path) if one is held and
/// resolved, else the classified mode (base-stem fallback, then Point); on entering/leaving
/// mouselook, hide/show it via the app-global hide counter.
#[allow(clippy::too_many_arguments)]
pub(super) fn drive(
cursors: Option<NonSend<NativeCursors>>,
mut payload_cursors: NonSendMut<PayloadCursors>,
-1
View File
@@ -177,7 +177,6 @@ const SPIRIT_HEALER_RANGE_SQ: f32 = 5.5556 * 5.5556;
/// Per-frame: derive the death state from the self descriptor, push the countdown/offer snapshot,
/// and fire the reference's death events on the edges (before `UiInput`, so a frame's `OnEvent`
/// sees current values — the [`crate::ui_unit`] feed convention).
#[allow(clippy::too_many_arguments)]
fn feed_death(
script: Option<NonSendMut<UiScript>>,
self_q: Query<(&ObjectStore, &Transform), With<SelfPlayer>>,
@@ -192,7 +192,6 @@ pub(super) struct InspectStores<'w, 's> {
/// The inspector overlay, drawn only while armed: a weak top-centre "armed" pill (so it's obvious the
/// mode is on and how to leave it) and, whenever the cursor is over an identified object, a compact
/// identity card pinned to the cursor. No chrome, no panel — its own lightweight surface.
#[allow(clippy::too_many_arguments)]
pub(super) fn inspect_ui(
mut contexts: EguiContexts,
inspect: Res<InspectMode>,
@@ -156,7 +156,6 @@ fn age(now: f64, at: f64) -> String {
/// The journal overlay: while inspect is armed, the recent casts as a top-left column, newest
/// first — click a row to copy its one-line identity block. Shares the inspector card's style
/// (decision 0025's one overlay look).
#[allow(clippy::too_many_arguments)]
pub(super) fn journal_ui(
mut contexts: EguiContexts,
inspect: Res<InspectMode>,
+2 -2
View File
@@ -37,6 +37,7 @@ use bevy_egui::{
PrimaryEguiContext,
};
use benilla_assets::LockRecover;
use benilla_world::lighting::{ClockSource, GameClock, WowLighting};
use benilla_world::model_render::{ModelKind, ModelPart};
use benilla_world::modkeys::{dev_chord, DEV_CHORD};
@@ -380,7 +381,6 @@ fn toggle_panel(keys: Res<ButtonInput<KeyCode>>, mut debug: ResMut<DebugState>)
/// Draw the panel as a translucent **overlay** on the right — the world renders full-screen
/// underneath (no viewport inset). `ui_script::PointerOverUi` keeps the cursor's panel
/// interactions from leaking into gameplay mouse-look.
#[allow(clippy::too_many_arguments)]
fn debug_panel_ui(
mut contexts: EguiContexts,
stamp: Res<benilla_world::build_id::BuildId>,
@@ -671,7 +671,7 @@ fn debug_panel_ui(
// The LAST sample, not the ring average the meter shows: the panel
// is the instrument, and "what did the most recent pong measure" is
// the question a stuck or spiking meter needs answered.
let last_rtt = ping.0.lock().expect("ping clock").last_rtt_ms;
let last_rtt = ping.0.lock_recover().last_rtt_ms;
ui.label(if net_status.connected {
match last_rtt {
Some(ms) => format!("connected · {ms} ms ping"),
+26 -25
View File
@@ -34,6 +34,15 @@ pub(crate) fn print_scenario_names() {
crate::capture::print_scenario_names();
}
/// `WOW_PROBE=list` — the probe fleet's environment registry (`capture::probe_env`): every
/// `WOW_PROBE*` variable, whether it arms the un-occludable window, and `WOW_PROBE`'s own named
/// values — printed before any window or asset setup. Answers nothing in a player build, which
/// has no probes.
pub(crate) fn print_probe_vars() {
#[cfg(feature = "dev")]
crate::capture::probe_env::print();
}
/// `WOW_HOVER_LOG_REPORT=<csv>` — re-read a recorded run and print its report, no window, no game.
/// New analysis lands on runs already captured (see `hover_log`).
pub(crate) fn report_recorded_hover_log(_path: &str) {
@@ -105,34 +114,26 @@ impl Plugin for DevProbesPlugin {
// Any scripted probe keeps its window un-occludable: a fully covered macOS window drops to
// ~1 fps drawables, and every probe schedule is wall-clock — a throttled run doesn't measure
// slowly, it runs the wrong script (see `capture::ProbeFocusPlugin`, decision 0906).
// Which probe variables count is the registry's `wall_clock` column
// (`capture::probe_env::PROBE_VARS`), not a list kept here: this list was hand-kept
// and had drifted to ten of the twenty-five wall-clock variables when 2265 §A5 read
// it against the code, so a mail or auction probe ran covered. The four below are the
// non-probe instruments that schedule on the wall clock too.
// (`WOW_LIVE_FPS` is in the list because an occluded SETTLE phase streams the world at ~1 fps
// and under-warms the scene before sampling even starts — the assertion has to be live from
// the first tick, not at the uncap.)
if [
"WOW_PROBE",
"WOW_PROBE_CHAT",
"WOW_PROBE_HOVER",
"WOW_PROBE_KEY",
"WOW_PROBE_LUA",
// The swim-pitch aim is a wall-clock timeline like every other probe schedule, and
// it is the one script that can be a run's *only* actuator (a drifting swimmer
// needs no keys) — so it arms the occlusion defence on its own.
"WOW_PROBE_PITCH",
"WOW_PROBE_CHEST",
"WOW_PROBE_CLAM",
"WOW_PROBE_VENDOR_SWAP",
"WOW_PROBE_MODEL_CAMERA",
"WOW_RIG",
"WOW_LIVE_FPS",
// A screenshot burst and a pick burst are wall-clock schedules too, and on an
// occluded window they capture the same stale drawable over and over: a whole
// day of lamppost A/Bs on the Air read identical to the decimal because every
// frame in every burst was one frame (the halo record).
"WOW_LIVE_SHOT",
"WOW_PICK",
]
.iter()
.any(|k| std::env::var(k).is_ok())
if crate::capture::probe_env::wall_clock_vars()
.chain([
"WOW_RIG",
"WOW_LIVE_FPS",
// A screenshot burst and a pick burst are wall-clock schedules too, and on an
// occluded window they capture the same stale drawable over and over: a whole
// day of lamppost A/Bs on the Air read identical to the decimal because every
// frame in every burst was one frame (the halo record).
"WOW_LIVE_SHOT",
"WOW_PICK",
])
.any(|k| std::env::var(k).is_ok())
{
app.add_plugins(crate::capture::ProbeFocusPlugin);
}
-2
View File
@@ -592,7 +592,6 @@ pub(crate) struct DisplayBuildSet;
/// doc for why a clear is always safe mid-session). These caches are get-or-insert at every use
/// site, so a cleared entry rebuilds on the next spawn that wants it; without this, every display
/// id, material key, and composited skin ever seen stayed resident for the life of the process.
#[allow(clippy::too_many_arguments)]
fn evict_display_caches(
mut changes: MessageReader<benilla_world::world_map::MapChange>,
mut composites: ResMut<SkinComposites>,
@@ -1191,7 +1190,6 @@ fn setup_entities(
/// For every display id active among the net entities: ensure its [`DisplayModel`] exists (resolve the
/// catalog + request the model handle), and once the handle has loaded, build its spawn parts (the
/// per-submesh material, with creature skin slots filled from the display's variations).
#[allow(clippy::too_many_arguments)]
fn update_display_models(
// `ObjectStore` rides along for the corpses: which cache holds a corpse's model is a
// descriptor question (`CORPSE_FLAG_BONES`), not a display-id one — decision 1706.
@@ -281,7 +281,7 @@ fn shared_skin_probe() -> bool {
/// available (those parts then keep their built, untextured material). A composited atlas is uploaded once per look ([`super::super::SkinComposites`] cache); a baked or hair
/// BLP loads through the async `mpq://` pipeline (which dedups by path). `parts` supplies the hair
/// batches' blend (hair is alpha-cut, so it can't be forced opaque like the body).
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
#[allow(clippy::type_complexity)]
pub(super) fn build_char_skin_materials(
look: &CharLook,
// The worn armor display ids (bodyslot 2–9) + the cloak's, and the ItemDisplayInfo catalog to
@@ -294,7 +294,7 @@ struct RigBuild {
/// / player body) as submesh children, or a colored cube fallback. The entity's pose is owned by the
/// net bridge — or, for our own avatar, the player controller — we only add the geometry (and bake
/// per-display scale onto the root). Our own avatar is the same streamed entity and renders here too.
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
pub(super) fn attach_entity_visuals(
mut commands: Commands,
pending: Query<
@@ -159,7 +159,6 @@ pub(in crate::entities) struct PreviewCtx<'a, 'w> {
/// entity-visuals chain (after `update_display_models` has built the body model from the
/// want-list), so a fresh look's models are ready within a frame or two; until then it leaves the
/// bake untouched and retries. A bare yaw change never reaches here (the booth handles it).
#[allow(clippy::too_many_arguments)]
pub(in crate::entities) fn build_glue_preview(
preview: Res<GluePreview>,
mut bake: ResMut<GluePreviewBake>,
@@ -351,7 +350,6 @@ pub(in crate::entities) fn build_glue_preview(
/// plus the tried-on substitutions. The body display is the player's own, so it is already built
/// (they are standing in the world); an ITEM model still has to load, which is what the retry latch
/// is for.
#[allow(clippy::too_many_arguments)]
pub(in crate::entities) fn build_dressup_preview(
preview: Res<DressUpPreview>,
mut bake: ResMut<DressUpBake>,
@@ -68,7 +68,7 @@ type PartWrites<'a> = (
/// creature's look never changes this way, and a character-model NPC wears its display's columns —
/// a *display* change is a different model and stays a teardown
/// ([`super::super::live_display::refresh_live_display`]).
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
pub(in crate::entities) fn redress_player_looks(
mut commands: Commands,
mut players: Query<
@@ -240,7 +240,6 @@ fn select_targets(
/// reapable: the reference only ever publishes a flagged node to the owner slots that the channel
/// teardown sweeps (`0x6ecdaa` gates the AddRef on the flag), so a one-shot beam always runs its own
/// clock to the end.
#[allow(clippy::too_many_arguments)]
pub(super) fn spawn_chain_beams(
mut commands: Commands,
time: Res<Time>,
@@ -463,7 +462,6 @@ fn push_strand(
/// Per frame: age the beam, re-resolve every hop's live endpoints, re-jitter and advect its
/// polyline, and write the ribbons into the shared effect-quad stream — the reference's
/// `LightningObject::Update` (`0x6ec460`) and `CLightning::Render` (`0x7afcb0`) in one pass.
#[allow(clippy::too_many_arguments)]
pub(crate) fn simulate_chain_beams(
time: Res<Time>,
mut commands: Commands,
+1 -1
View File
@@ -94,7 +94,7 @@ fn conform_rotation(mode: u8, n_local: Vec3) -> Quat {
/// spawn seats instantly like the reference; and a parked rig (off-frustum, the 0448 anim-LOD
/// gate) skips its ray and drops its vector, re-seeding warm on wake — the 0448 absolute-snap
/// philosophy applied to the stance, bounding the cost at one ray per flagged unit ON SCREEN.
#[allow(clippy::too_many_arguments, clippy::type_complexity)] // one Bevy system's full input set
#[allow(clippy::type_complexity)] // one Bevy system's full input set
pub(super) fn conform_units(
time: Res<Time>,
spatial: SpatialQuery,
@@ -267,7 +267,6 @@ pub(super) fn tick_shard_emitters(
/// Attach model parts to pending instances whose M2 finished building (the missile pattern —
/// free world models, ground-anchored so authored flat quads decal to the terrain), start the
/// one-shot clocks, and run both reapers (one-shot expiry; the loop-repeat override).
#[allow(clippy::too_many_arguments)] // one Bevy system's full input set
pub(super) fn attach_ground_fx_models(
mut commands: Commands,
time: Res<Time>,
@@ -375,7 +375,6 @@ pub(super) fn empty_display() -> DisplayModel {
/// Build a display model's spawn parts once its asset has loaded — each submesh's `WowModelMaterial`,
/// with a creature skin slot filled from the display's variation (`<dir>\<name>.blp`). Returns early
/// (leaving `parts` `None`) while the asset is still loading.
#[allow(clippy::too_many_arguments)]
pub(super) fn build_parts(
dm: &mut DisplayModel,
m2s: &Assets<M2Model>,
@@ -172,7 +172,7 @@ pub(crate) struct DressKey {
/// descriptor changed, its key changed, or a global input moved — the [`Items`] epochs (an
/// object ingest covers the quiver bag walk, a landed template answers every pending ask) and
/// the two client-data load edges. The idle crowd costs one tick check and one small compare.
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
pub(in crate::entities) fn resolve_equipment(
mut commands: Commands,
units: Query<(
@@ -177,7 +177,6 @@ pub(crate) struct ItemGlowAttached;
/// waits, so a two-model visual can't spawn half of itself and then re-enter here. A model that
/// never loads simply never glows (the item is still perfectly drawn); the retry is a handful of
/// roots per frame.
#[allow(clippy::too_many_arguments)]
pub(super) fn attach_item_glows(
mut commands: Commands,
pending: Query<(Entity, &ItemGlow), Without<ItemGlowAttached>>,
@@ -462,7 +462,6 @@ fn arrival_handoff(
/// arrival deadline was fixed at GO). A release already past its deadline arrives on the spot:
/// the arrival hand-off plays now, no flight entity, no flight loop — the melee-range cast
/// shows only the hit, the reference's close-range look.
#[allow(clippy::too_many_arguments)]
fn launch_go(
go: &QueuedGo,
launch: Vec3,
@@ -535,7 +534,6 @@ fn launch_go(
/// is created at queue time (shared with the attach-point effects — the same `.mdx` is one load
/// however many things use it); a chain-less spawn resolves the wire ammo display instead
/// ([`ensure_ammo_model`]).
#[allow(clippy::too_many_arguments)]
pub(super) fn spawn_missiles(
mut commands: Commands,
time: Res<Time>,
@@ -673,7 +671,6 @@ pub(super) fn spawn_missiles(
/// Spawn a missile's model parts + particle emitters once its M2 finishes building (the shared
/// cache's `parts` fill in `super::update_display_models`) — children of the missile entity, so
/// they ride the mover below. An unloadable model just flies invisible and still impacts on time.
#[allow(clippy::too_many_arguments)]
pub(super) fn attach_missile_models(
mut commands: Commands,
mut missiles: Query<(Entity, &mut Missile)>,
@@ -740,7 +737,6 @@ pub(super) fn attach_missile_models(
/// the path. On schedule-end it snaps to the point, runs the [`arrival_handoff`] (a landed
/// target's impact, a missed one's dodge/block), and despawns (children with it; emitters
/// self-release via the owner contract).
#[allow(clippy::too_many_arguments)]
pub(super) fn move_missiles(
mut commands: Commands,
time: Res<Time>,
-1
View File
@@ -156,7 +156,6 @@ pub(super) type MountChildren<'w, 's> = Query<
/// Drive one unit's mount side to the point where the rider can be seated. Spawns the child when
/// it is missing, drops and retries a child built for a display the field has since moved off, and
/// resolves the seat frame once the child's rig is up.
#[allow(clippy::too_many_arguments)] // the seat law's full input set, like its callers
pub(super) fn seat_or_spawn_mount(
commands: &mut Commands,
children: &MountChildren,
@@ -228,7 +228,6 @@ pub(crate) struct EffectHost {
/// rather than off a slot pinned at spawn. `None` for the lanes that are not `CEffect`s and never
/// advance — a missile (the separate `CMissile` TU), an item glow, the `fxview` preview — which
/// keep the pinned single-clip arm they have always had.
#[allow(clippy::too_many_arguments)]
pub(crate) fn attach_effect_visuals(
commands: &mut Commands,
root: Entity,
@@ -916,7 +915,7 @@ fn reap_matching(
/// so the whole effect — meshes and particle emitters alike — rides the animating bone. The one
/// exception is the kit's **world-plant slot** ([`benilla_formats::WORLD_EFFECT_TAG`], kit field
/// 12): its root is a free world entity at the owner's position/facing/scale, [`WorldPlantFx`].
#[allow(clippy::too_many_arguments, clippy::type_complexity)] // one Bevy system's full input set
#[allow(clippy::type_complexity)] // one Bevy system's full input set
pub(super) fn attach_spell_fx(
mut commands: Commands,
mut units: Query<(
@@ -1124,7 +1123,6 @@ pub(super) fn attach_spell_fx(
/// the world position; an instance root's own is joint-local). Unlike a streamed creature, an
/// instance is born under our eyes at t = 0, so first sight fires the head window `[0, cur]` —
/// the level-up pillar's `$SND(888)` sits at 0.033 s and depends on it.
#[allow(clippy::too_many_arguments)] // the scan's own params plus the frame's two reads
pub(super) fn fire_fx_anim_events(
units: Query<(Entity, &FxAttached)>,
players: Query<&AnimationPlayer>,
@@ -186,7 +186,6 @@ pub(super) fn resolve_wmo_gameobject_props(
/// Spawn each pending prop as its M2 lands, parented under the gameobject entity. The whole ship's
/// set spawns within a few frames of the models landing — no per-frame budget (transports are a
/// handful of instances per map, not a city's worth of placements).
#[allow(clippy::too_many_arguments)] // one Bevy system's full input set
pub(super) fn spawn_wmo_gameobject_props(
mut commands: Commands,
m2s: Res<Assets<M2Model>>,
-1
View File
@@ -159,7 +159,6 @@ type RootState = (
/// Spawn a print for each per-foot plant on a footprint surface: the state gates (hover /
/// stealth / ghost / distance), the terrain-flags gate, the unit's ink + params, the event
/// marker's live bone position, yaw to the facing, project once, cache the triangles.
#[allow(clippy::too_many_arguments)]
fn spawn_footprints(
mut events: MessageReader<AnimSoundEvent>,
time: Res<Time>,
-1
View File
@@ -312,7 +312,6 @@ fn empty_tip(e: &mut EntityCommands, vis: &mut Visibility) {
///
/// Two jobs in one system because they are one mechanism seen at two moments: the reference picks
/// the row inside `EnterWorld` and lays it out once per raise, then draws that layout every frame.
#[allow(clippy::too_many_arguments)]
fn drive_game_tip(
mut screen: ResMut<crate::loading_screen::LoadingScreen>,
mut tips: ResMut<GameTips>,
-1
View File
@@ -394,7 +394,6 @@ pub(crate) fn spawn_dialog(
///
/// Runs inside each glue screen's own system chain (again: the header says why it cannot be
/// hoisted out with an ordering pair).
#[allow(clippy::too_many_arguments)]
pub(crate) fn drive_glue_dialog(
mut commands: Commands,
mut dialog: ResMut<GlueDialog>,
-5
View File
@@ -162,7 +162,6 @@ pub(crate) fn outlined_text<W: Bundle, T: Bundle>(
/// Every *other* wrapped glue string in the shipped XML sets `justifyH="LEFT"` explicitly
/// (`CharacterCreate.xml`'s race/class/faction bodies, `AddonList.xml`'s title/notes/deps), which
/// is why [`outlined_text`] stays left and this is the exception rather than the default.
#[allow(clippy::too_many_arguments)]
pub(crate) fn outlined_text_centered<W: Bundle, T: Bundle>(
parent: &mut ChildSpawnerCommands,
node: Node,
@@ -257,7 +256,6 @@ pub(crate) fn markup_spans(text: &str, base: Color, wrap: bool) -> Vec<(String,
///
/// **Private**: [`outlined_text`] is the one door in, so no caller can hand-build spans and skip
/// the markup decode (B273's shape — see [`markup_spans`]).
#[allow(clippy::too_many_arguments)]
fn outlined_spans<W: Bundle, T: Bundle>(
parent: &mut ChildSpawnerCommands,
node: Node,
@@ -355,7 +353,6 @@ fn outlined_spans<W: Bundle, T: Bundle>(
/// visual), and the name label along the bottom (the ref's `HighlightText`, `GlueFontNormalSmall`,
/// anchored BOTTOM +1 — over the icon's bottom edge). `dyn_icon`/`label_dyn` are the screen's
/// refresh markers, spawned onto the face / real label text.
#[allow(clippy::too_many_arguments)]
pub(crate) fn icon_button<A: Component, I: Bundle, L: Bundle>(
parent: &mut ChildSpawnerCommands,
font: &Handle<Font>,
@@ -712,7 +709,6 @@ pub(crate) fn paint_glue_field<'a>(
/// onto all five row items ([`GlueFieldPart`]) so the screen's refresh can query them as a set and
/// hand them to [`paint_glue_field`]. Plain-fill fallback without art. Focus and typing are the
/// owning screen's systems — this is chrome only, so the screens' boxes can never fork.
#[allow(clippy::too_many_arguments)]
pub(crate) fn glue_edit_box<E: Bundle, T: Bundle + Clone>(
parent: &mut ChildSpawnerCommands,
art: &GlueArt,
@@ -821,7 +817,6 @@ pub(crate) fn glue_edit_box<E: Bundle, T: Bundle + Clone>(
/// Returns the button's entity, so a screen can reach back into what it just built — the login
/// screen marks its realmlist button [`GlueDisabled`] when `$WOW_HOST` owns the session (1667).
/// Ignoring the return is the norm; nothing is `#[must_use]`.
#[allow(clippy::too_many_arguments)]
pub(crate) fn glue_button<A: Component>(
parent: &mut ChildSpawnerCommands,
art: &GlueArt,
+5 -5
View File
@@ -1,8 +1,8 @@
//! GameObject open/close animation (decision 0242; chest lid folded in by 0250) — a **client-side**
//! GameObject open/close animation (decision 0242; chest lid folded in by 2271) — a **client-side**
//! `GAMEOBJECT_STATE` drives a skeletal M2 sequence, so a **door** swings, a **button** depresses, and a
//! **chest lid** opens/closes on its §243 state machine.
//!
//! **The model (0250, §5-VERIFIED):** the real client keeps *one* stored state per GameObject (the
//! **The model (2271, §5-VERIFIED):** the real client keeps *one* stored state per GameObject (the
//! binary's `go+0x27c`) and *one* `SetGoState` that all callers funnel through; a change of that state
//! plays the §243 transition. benilla mirrors that exactly — [`GoAnim::state`] is the single source of
//! truth, written by the **three callers** the RE census pinned:
@@ -88,7 +88,7 @@ const GO_STATE_READY: u32 = 1;
/// object plays this once and *then* goes away; see [`DespawnAnimAnnounced`].
const ANIM_DESPAWN: u16 = 157;
/// Marker + client-side state for an animated GameObject (decisions 0242/0250). Instanced by
/// Marker + client-side state for an animated GameObject (decisions 0242/2271). Instanced by
/// [`crate::entities::attach`] on an animatable GO type whose model authors sequences; driven by
/// [`drive_go_anim`]. Distinct from creatures' `AnimDriver` so the two drivers never touch one entity.
#[derive(Component, Default)]
@@ -304,7 +304,7 @@ fn collider_is_solid(wire_state: Option<u32>) -> bool {
}
/// A cast launched at a GameObject (`SMSG_SPELL_GO` carrying a `TARGET_FLAG_GAMEOBJECT`), bridged from the
/// net apply layer to this module (decision 0250). [`open_go_lid`] opens the target's lid/door iff the
/// net apply layer to this module (decision 2271). [`open_go_lid`] opens the target's lid/door iff the
/// spell carries an open-lock effect and the GO is an animated type — the client's `Spell_C` open path.
#[derive(Message, Clone, Copy)]
pub(crate) struct GoLidOpen {
@@ -1784,7 +1784,7 @@ mod tests {
#[test]
fn chest_animates_but_keeps_its_collider() {
// A chest (3) is on the animation machine (0250) but off the collision gate (0249): you see the
// A chest (3) is on the animation machine (2271) but off the collision gate (0249): you see the
// lid move, but you never walk through an open chest.
assert!(go_animates(3));
assert!(!collision_follows_state(3));
+11
View File
@@ -59,6 +59,7 @@ mod chr_classes;
mod cinematic;
mod combat_text;
mod cooldowns;
mod crash;
mod creature_anim;
mod cursor;
mod cvars;
@@ -302,6 +303,16 @@ pub fn run(build: BuildId) -> AppExit {
dev::print_scenario_names();
return AppExit::Success;
}
// `WOW_PROBE=list` prints the probe fleet's environment registry (`capture::probe_env`) the
// same way — before any window, so a session can ask the binary what the fleet takes.
if std::env::var("WOW_PROBE").as_deref() == Ok("list") {
dev::print_probe_vars();
return AppExit::Success;
}
// From here on a panic leaves `benilla-config/Diagnostics/crash-<unix>.txt` behind (decision
// 2266 §B2) — armed before the `App` exists, so a panic while plugins build is a report too.
crash::install(build);
let mut app = App::new();
// The stamp is plain data from here on — the panel footer and preflight banner read it back.
+1 -1
View File
@@ -549,7 +549,7 @@ fn setup_loading_screen(
/// Per-frame: observe the lifecycle edges, run the trigger/clear state machine, resolve backdrop
/// art, and push the progress fraction into the bar.
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
#[allow(clippy::type_complexity)]
fn drive_loading_screen(
mut screen: ResMut<LoadingScreen>,
// The streamer's two published facts, bundled into one param (Bevy's 16-element system-param
+1 -2
View File
@@ -447,7 +447,6 @@ fn logon_refusal_text(strings: &GlueStrings, code: Option<u8>) -> &str {
/// The policy tick + the net-message reactions. Runs in every state (the reconnect path fires
/// while `InWorld`); the screen's own submit comes through [`login_input`], which calls
/// [`send_login`] with `announced = true`.
#[allow(clippy::too_many_arguments)]
fn drive_policy(
mut attempt: Attempt,
realm_list_up: Res<crate::realm_select::Realms>,
@@ -792,7 +791,7 @@ fn enter_login(mut form: ResMut<LoginForm>, mut preview: ResMut<GluePreview>) {
/// The screen's input: typing into the focused box (the ref's 16-letter cap), Tab cycling, Enter
/// submits, Esc quits (dialog-first — an open dialog's Esc is its Cancel/Okay), clicks focus the
/// boxes / press the buttons / toggle the checkbox.
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
#[allow(clippy::type_complexity)]
fn login_input(
realms: Res<crate::realm_select::Realms>,
presses: Query<(Entity, &LoginAction, Ref<Interaction>)>,
-2
View File
@@ -81,7 +81,6 @@ pub(super) struct RealmlistReadout;
/// Spawn the screen tree once its prerequisites exist (the select screen's boot-order pattern:
/// the INITIAL state's `OnEnter` fires before the MPQ chain / booth slots do) — and upgrade an
/// artless early spawn the moment the client art lands.
#[allow(clippy::too_many_arguments)]
pub(super) fn materialize_screen(
mut commands: Commands,
existing: Query<(Entity, &LoginUi)>,
@@ -137,7 +136,6 @@ pub(super) fn materialize_screen(
}
}
#[allow(clippy::too_many_arguments)]
fn spawn_screen(
commands: &mut Commands,
assets: &AssetServer,
-1
View File
@@ -38,7 +38,6 @@ pub(crate) fn smoke_character(spec: &str) -> Option<String> {
/// `WOW_UNATTENDED=1` still needs a way to name a character without taking it. 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)]
pub(super) fn debug_login_smoke(
state: Res<State<ClientState>>,
mut attempt: Attempt,
@@ -91,7 +91,6 @@ fn unit_dot_eligible(store: Option<&ObjectStore>, me: Option<u64>) -> bool {
/// Walks the candidate set rather than the status map — the classifier's own shape (it is a
/// per-object callback, not a per-status one), and the only way to reach each object's descriptor,
/// which [`unit_dot_eligible`] needs.
#[allow(clippy::too_many_arguments)]
pub(in crate::minimap) fn emit_quest_dots(
ctx: &BlipCtx,
statuses: &HashMap<u64, u32>,
@@ -233,7 +232,6 @@ fn creature_type_of(
/// byte-verified `0x4eac31`). Same hard 3-D radius cull, cross-interior grey, and hover law
/// as the quest dots; drawn just before them (the draw walks the cell lists in order, so
/// cells 0/1 sit under a same-spot quest or party dot).
#[allow(clippy::too_many_arguments)]
pub(in crate::minimap) fn emit_tracking_dots(
ctx: &BlipCtx,
tracking: SelfTracking,
@@ -398,7 +398,6 @@ fn poi_icon_cell(icon: u32) -> Option<[f32; 4]> {
/// Draw the landmark layer: in-range POI icons at position, then the nearest-3 rim arrows,
/// each rotated to point at its POI. Records a hover hit (later-drawn wins, matching draw
/// order).
#[allow(clippy::too_many_arguments)]
pub(super) fn emit_landmarks(
ctx: &BlipCtx,
cat: Option<&AreaPoiCatalog>,
-2
View File
@@ -400,7 +400,6 @@ fn minimap_interior<'a>(
/// Fills the extracted widget hole: the visible tile quads (clipped to the widget, masked to the
/// circle) and the player arrow, appended at the widget's own z (stable sort keeps append order
/// within a key, so the arrow rides above the tiles and below the widget's children).
#[allow(clippy::too_many_arguments)]
fn emit_minimap(
widget: Res<MinimapWidget>,
assets: Option<Res<MinimapAssets>>,
@@ -935,7 +934,6 @@ fn emit_minimap(
/// — the client's own signal for "the effective zoom changed" (FrameXML `Minimap_OnEvent`). Without it
/// the buttons keep the level you left (e.g. `ZoomIn` greyed from an outdoor max-zoom, still greyed
/// indoors at level 3), which is the director's report (2026-07-09).
#[allow(clippy::too_many_arguments)] // one Bevy system's full input set
fn feed_minimap_inside(
script: Option<bevy::ecs::system::NonSendMut<benilla_ui::script::UiScript>>,
world: benilla_world::world_point::WorldPoint,
-1
View File
@@ -134,7 +134,6 @@ pub(super) fn seat_click(ctx: &BlipCtx, ping: &mut MinimapPing, click: Option<(f
/// Runs before the script tick so the `MINIMAP_PING` event and the position behind
/// `Minimap:GetPingPosition()` land in the same tick, and so an addon's handler sees a ping that
/// is already seated (the renderer seated it at the end of the previous frame).
#[allow(clippy::too_many_arguments)] // one Bevy system's full input set
pub(super) fn drive_minimap_ping(
script: Option<bevy::ecs::system::NonSendMut<UiScript>>,
mut ping: ResMut<MinimapPing>,
+1 -1
View File
@@ -384,7 +384,7 @@ fn build_name_mesh(atlas: &mut UiFontAtlas, lines: &[String]) -> Mesh {
/// Update (the schedule the mesh pipelines support). The per-frame *placement* is
/// [`place_nameplates`] (PostUpdate, off this frame's propagated pose) — a fresh plate spawned
/// here gets its first seat there, same frame (Update commands flush before PostUpdate).
#[allow(clippy::too_many_arguments, clippy::type_complexity)] // one Bevy system's full input set
#[allow(clippy::type_complexity)] // one Bevy system's full input set
pub(crate) fn drive_nameplates(
mut commands: Commands,
units: Query<(

Some files were not shown because too many files have changed in this diff Show More