- the raid and reputation panes are the reference's own files: two manifest lines replace our `RaidFrame.xml` (83 of 84 frame names were already the reference's; the tear-off pullouts arrive) and one replaces our `ReputationFrame.xml`; the raid swap surfaced a loader defect (a font object inherited one level down was the 26 unexplained warnings), rehomed `RaidFrame_LoadUI` and two popup dialogs, and reproduces one reference DEFECT: an emptied lockout list keeps its last row painted (1874); the reputation swap moved the WATCH BAR out of `ActionBar.xml`, where it would have shadowed the chain's own, and exposed an ENGINE gap: `CollapseFactionHeader`/`ExpandFactionHeader` fire `UPDATE_FACTION` (`0x4d5dab`) and ours fired nothing, so with the stock pane repainting on that event alone, a fold painted nothing and the header kept its minus — invisible while our own file repainted itself (1875) - a wanted poster is a questgiver on the wire as a creature is, and benilla never asked about one; the RE round came back in two halves: the reference DOES send `CMSG_QUESTGIVER_STATUS_QUERY` for a GameObject, gated on `GAMEOBJECT_FLAGS` bit 2, seen in 20 of 62 sniffed sessions — and it can never render the answer (the status handler's typemask excludes a GameObject, its status slot lies past the object's end, and no poster, sign or plaque carries attachment 18); vmangos DOES answer, so the sweep gains a GameObject arm on the reference's own gate and the apply side refuses a non-unit guid, without which a gold `!` would hang over a poster no 1.12 client draws; two shipped claims fall with it (`0x5eb3c0` is the FULL re-query sweep; a status never suppresses a GameObject's minimap dot), and `WOW_PROBE_GOQUEST` at the Goldshire wanted poster reads the server's 1 then 5, both refused (1872) - the money entry comes off the chain: Trade, Mail and Auction switch to the reference's `MoneyInputFrameTemplate` and its verbs, and our `BenillaMoneyInput_*` kit retires — it was the exact answer while benilla fired `OnTextChanged` synchronously inside `SetText`, and the engine now raises it deferred and coalesced, matching `0x77d3e0` (1831), so the reference's `expectChanges` guard works and our bracket flag was a duplicate; most of the 430 deleted lines are verbatim art — all three windows carried an inline copy of the template's three edit boxes, AuctionFrame a whole second copy; one visible change: TradeFrame now makes the reference's own `MoneyInputFrame_SetMode(this, "compact")` call, which tightens the trade money boxes (Gold 58→56 wide, Silver's offset 26→22, Copper's 16→11), and two stale claims in the kit's comments are corrected, and a verb nothing called goes (1882) - the `UIFrameFlash` family is transcribed — six FrameXML names from `UIParent.lua`, which we do not load — unblocking SpellBookFrame and HelpFrame, which called it into silence; re-arming a flashing frame is a RETURN and `-1` flashes forever (1879); UIParent is recounted — `UpdateNameplates` and the `*_Show`/`*_Hide` family around it are FrameXML, not engine, missed by a survey that skipped the LoadOnDemand addons — so its engine surface is four verbs, not a wall: `GetLocale` answers `enUS`, `IsMacClient` answers nil (the whole verb on the PC build), and `CheckReadyCheckTime` and `RequestBattlefieldPositions` are the two it waits on (1880); and `BasicControls.xml` comes off the chain by pure deletion — ours differed by nothing but line-wrapping and one comment — with `FailedAddOnLoad` and `UIParentLoadAddOn` moving to `UIParent.xml`, their reference home, below their four callers (1881) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
412 lines
27 KiB
Rust
412 lines
27 KiB
Rust
//! **The dev/player seam** — the two instrument groups, and the one rule that governs them
|
|
//! (decision 0026 set the target, 1173 mandated the build, 1174 is what got built).
|
|
//!
|
|
//! ```text
|
|
//! Dev may see anything; nothing may depend on dev.
|
|
//! ```
|
|
//!
|
|
//! That is the whole boundary, and it is one-directional on purpose. An instrument's job *is* to
|
|
//! see everything — 1160 measured 266 references from the instruments into 63 of the app's 142
|
|
//! modules — so a crate wall here would mean publishing hundreds of internals as permanent public
|
|
//! API shaped by whatever a panel wanted to poke at. A `cfg` seam has the opposite property: the
|
|
//! instruments keep reaching in exactly as far as they like, and only the *direction* is
|
|
//! constrained.
|
|
//!
|
|
//! **The mechanism is not this comment.** It is `cargo build -p benilla --no-default-features` in
|
|
//! `scripts/gates.sh`. 0026 wrote this same rule down in June 2026 with no failing build behind
|
|
//! it, and by August there were 24 references from non-dev code into the instruments across 12
|
|
//! files (1173) — not through carelessness, but because a rule with no failing test is a wish.
|
|
//! When gameplay needs a fact an instrument happens to know, the fact goes to
|
|
//! [`crate::run_mode`] (always present, player-faithful default) and the instrument writes it
|
|
//! there. It does not get a `use` line from gameplay.
|
|
//!
|
|
//! **What is deliberately NOT in here:** `benilla_world::dev_state` (the always-present config
|
|
//! layer — its defaults *are* the player behaviour, so it ships), `pipe_warm` (a player on macOS
|
|
//! eats every synchronous pipeline stall without it — 0837/1116), and `art_scope` (within-map art
|
|
//! residency: engine, and it travels with `WorldPlugins` — 0729).
|
|
|
|
use bevy::prelude::*;
|
|
|
|
/// `WOW_CAPTURE=list` — the harness scenario names `scripts/visual.sh` reads, printed before any
|
|
/// window or asset setup. Answers nothing in a player build, which has no scenarios.
|
|
pub(crate) fn print_scenario_names() {
|
|
#[cfg(feature = "dev")]
|
|
crate::capture::print_scenario_names();
|
|
}
|
|
|
|
/// `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) {
|
|
#[cfg(feature = "dev")]
|
|
crate::hover_log::report_recorded_file(_path);
|
|
}
|
|
|
|
/// **The instruments.** One group, added where the debug panel has always sat — first among them,
|
|
/// because `PerfPlugin` needs the egui plugin and context it sets up.
|
|
pub(crate) struct DevToolsPlugin;
|
|
|
|
impl Plugin for DevToolsPlugin {
|
|
#[cfg_attr(not(feature = "dev"), allow(unused_variables))]
|
|
fn build(&self, app: &mut App) {
|
|
#[cfg(feature = "dev")]
|
|
{
|
|
app.add_plugins(crate::debug_panel::DebugPanelPlugin)
|
|
.add_plugins(crate::perf::PerfPlugin)
|
|
// `WOW_FX_CENSUS=1`: where this frame's particle draws are addressed, and whether
|
|
// the view they name is switched on (decision 0775). An instrument, and one that
|
|
// reads the portrait booths — so it belongs on this side of the line.
|
|
.add_plugins(crate::capture::fx_draw_census_plugin)
|
|
// The hover-cost recorder (`WOW_HOVER_LOG`) and the asset-churn meter
|
|
// (`WOW_ASSET_CHURN`) — both no-ops without their variable.
|
|
.add_plugins(crate::hover_log::HoverLogPlugin)
|
|
.add_plugins(crate::asset_churn::AssetChurnPlugin)
|
|
// The session preflight (decision 0649): one banner per world entry naming the body
|
|
// we logged into, and loud warnings for the states — dead/ghost, GM mode,
|
|
// server-blocked movement — that silently invalidate a session's readings. Never
|
|
// env-gated; a warning nobody switches on isn't one.
|
|
.add_plugins(crate::preflight::PreflightPlugin)
|
|
// The probe shield (decision 0677): a body on a probe account is put into vmangos's
|
|
// `.cheat god` on every world entry — damage clamps at 1 hp instead of killing —
|
|
// and GM mode is turned OFF, because the shield replaces the only reason it was
|
|
// ever on. Inert on any other account.
|
|
.add_plugins(crate::probe_shield::ProbeShieldPlugin);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// **The probe fleet** — the capture harness and every scripted live probe, each armed by its own
|
|
/// environment variable and inert without it. Added last so they observe the fully-built app.
|
|
pub(crate) struct DevProbesPlugin;
|
|
|
|
impl Plugin for DevProbesPlugin {
|
|
#[cfg_attr(not(feature = "dev"), allow(unused_variables))]
|
|
fn build(&self, app: &mut App) {
|
|
#[cfg(feature = "dev")]
|
|
{
|
|
// The capture harness drives one deterministic screenshot then exits — added last so it observes
|
|
// the fully-built app. Inert unless `$WOW_CAPTURE` is set.
|
|
if crate::run_mode::scenario_active() {
|
|
app.add_plugins(crate::capture::CapturePlugin);
|
|
}
|
|
// The LIVE probe shot (orthogonal to the harness): `WOW_LIVE_SHOT=<png>` on a NORMAL connected
|
|
// run writes one screenshot `WOW_LIVE_SHOT_AT` seconds (default 12) after startup and keeps
|
|
// running — the agent-side instrument for seeing a live server scene (NPCs, GameObjects, event
|
|
// spawns) without a scenario. Pair with `WOW_USER`/`WOW_CHAR` + an outer `timeout`.
|
|
if std::env::var("WOW_LIVE_SHOT").is_ok() {
|
|
app.add_plugins(crate::capture::LiveShotPlugin);
|
|
}
|
|
// The probe RIG (decision 0651): `WOW_RIG="tauren druid 60 gear:heal-preraid-bis"` finds-or-
|
|
// creates that body on this slot's probe account, logs in as it, and applies level/spells/gear/
|
|
// spec/place — the one verb that replaces the hand-assembled GM recipe every session used to
|
|
// re-derive (see `capture::ProbeRigPlugin`).
|
|
if std::env::var("WOW_RIG").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeRigPlugin);
|
|
}
|
|
// 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).
|
|
// (`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_RIG",
|
|
"WOW_LIVE_FPS",
|
|
]
|
|
.iter()
|
|
.any(|k| std::env::var(k).is_ok())
|
|
{
|
|
app.add_plugins(crate::capture::ProbeFocusPlugin);
|
|
}
|
|
// The probe-chat one-shot: `WOW_PROBE_CHAT=".go xyz …"` sends GM/chat lines once in-world —
|
|
// the "park the probe character anywhere" instrument (see `capture::ProbeChatPlugin`).
|
|
if std::env::var("WOW_PROBE_CHAT").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeChatPlugin);
|
|
}
|
|
// The probe-lua one-shot: `WOW_PROBE_LUA="CastSpell(…)"` runs a chunk in the live UI VM once
|
|
// in-world — the "press the button headlessly" instrument (see `capture::ProbeLuaPlugin`).
|
|
if std::env::var("WOW_PROBE_LUA").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeLuaPlugin);
|
|
}
|
|
// The probe-drag driver: `WOW_PROBE_DRAG="RaidGroupButton7>RaidGroup6Slot1"` drags one
|
|
// named frame onto another through the real pointer path, one gesture step per frame —
|
|
// the "do what the hand on the mouse does" instrument (see `capture::ProbeDragPlugin`).
|
|
if std::env::var("WOW_PROBE_DRAG").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeDragPlugin);
|
|
}
|
|
// The probe-hover sweep: `WOW_PROBE_HOVER="ActionButton1;ActionButton2;…"` crosses each
|
|
// named frame's centre through the real pointer path, pressing nothing — the "hand on
|
|
// the mouse" `WOW_HOVER_LOG` was built to record and could not supply itself.
|
|
if std::env::var("WOW_PROBE_HOVER").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeHoverPlugin);
|
|
}
|
|
// The probe-key taps: `WOW_PROBE_KEY="Space@14"` presses keys once in-world — the "press
|
|
// space headlessly" instrument for input-gated behavior (see `capture::ProbeKeyPlugin`).
|
|
if std::env::var("WOW_PROBE_KEY").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeKeyPlugin);
|
|
}
|
|
// The probe self-termination: `WOW_PROBE_EXIT_AT=<secs>` bounds any scripted live probe's
|
|
// lifetime — its own knob, not a rider on the Lua probe (see `capture::ProbeExitPlugin`).
|
|
if std::env::var("WOW_PROBE_EXIT_AT").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeExitPlugin);
|
|
}
|
|
// The ray pick: `WOW_PICK="<x>,<y>"` names every surface along the ray through a screenshot
|
|
// pixel, nearest first — "what is at the spot `benilla-visual hotspot` flagged, and what is
|
|
// right behind it" (see `capture::PickProbePlugin`).
|
|
if std::env::var("WOW_PICK").is_ok() {
|
|
app.add_plugins(crate::capture::PickProbePlugin);
|
|
}
|
|
// The render-phase census: `WOW_PHASE=<uniqueId>` reports, per frame, which phase each of one
|
|
// placement's batches landed in and where in the draw order — the one thing every scene-side
|
|
// instrument is blind to, namely whether a surface was submitted at all (see
|
|
// `capture::PhaseProbePlugin`).
|
|
if std::env::var("WOW_PHASE").is_ok() {
|
|
app.add_plugins(crate::capture::PhaseProbePlugin);
|
|
}
|
|
// The depth readback: `WOW_DEPTH="<x>,<y>"` reports what depth actually won each named pixel,
|
|
// per frame, as a distance in yards — the link past submission that decides the pixel. Pair it
|
|
// with `WOW_PICK` at the same pixels to turn "what won" into "whose it was" (see
|
|
// `capture::DepthProbePlugin`).
|
|
// `WOW_DEPTH_QUADS=<bone>…` is the same readback taken at a particle quad's OWN pixels — the
|
|
// moving-subject form, which no hand-written pixel list can hold (see `capture::depth_probe`).
|
|
if std::env::var("WOW_DEPTH").is_ok() || std::env::var("WOW_DEPTH_QUADS").is_ok() {
|
|
app.add_plugins(crate::capture::DepthProbePlugin);
|
|
}
|
|
// The bevy_ui node census — "who owns this rectangle" for UI outside the FrameXML quad pass
|
|
// (see `capture::NodeProbePlugin`).
|
|
if std::env::var("WOW_NODE_PROBE").is_ok() {
|
|
app.add_plugins(crate::capture::NodeProbePlugin);
|
|
}
|
|
// The mid-run window resize: `WOW_PROBE_RESIZE="<secs>:<W>x<H>"` — the headless fullscreen-
|
|
// toggle stand-in for resize-reactive layout (see `capture::ProbeResizePlugin`).
|
|
if std::env::var("WOW_PROBE_RESIZE").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeResizePlugin);
|
|
}
|
|
// The particle census: `WOW_PARTICLE_CENSUS=<secs>` prints per-emitter live counts once —
|
|
// the trace-comparable coverage number (see `capture::ParticleCensusPlugin`).
|
|
if std::env::var("WOW_PARTICLE_CENSUS").is_ok() {
|
|
app.add_plugins(crate::capture::ParticleCensusPlugin);
|
|
}
|
|
// The under-floor census: `WOW_GROUND_CENSUS=<secs>[,<every>]` prints one line per
|
|
// streamed unit near the body — the server's Z for it, the Z we drew it at, the drop
|
|
// between them, and the floor over its head. The instrument B197 was missing: it says
|
|
// whether a unit below a floor was put there by the server or pulled there by us (see
|
|
// `capture::GroundCensusPlugin`).
|
|
if std::env::var("WOW_GROUND_CENSUS").is_ok() {
|
|
app.add_plugins(crate::capture::GroundCensusPlugin);
|
|
}
|
|
// The transport census: `WOW_LIFT_CENSUS=<secs>[,<every>]` prints one line per
|
|
// type-11/15 transport GameObject on the map — its arm stage, its cycle, its
|
|
// visibility pair and its render-descendant count. The instrument B168 was missing: a
|
|
// lift that isn't on screen is either unsent, unarmed (and so still wearing its spawn
|
|
// hide), model-less, or simply elsewhere in its cycle, and only numbers tell those
|
|
// four apart (see `capture::LiftCensusPlugin`).
|
|
if std::env::var("WOW_LIFT_CENSUS").is_ok() {
|
|
app.add_plugins(crate::capture::LiftCensusPlugin);
|
|
}
|
|
// The frame-stall injector: `WOW_STALL="<ms>[,<every_s>[,<after_s>]]"` blocks the
|
|
// main loop on a schedule — the reproduction for "I tabbed away and came back to X",
|
|
// which no probe can otherwise stage (every scripted probe asserts `AlwaysOnTop`
|
|
// precisely so the OS cannot throttle it). `WOW_STALL=0` injects nothing and leaves
|
|
// the frame-delta/occlusion monitor, which is how the premise gets checked rather
|
|
// than assumed (see `capture::StallPlugin`).
|
|
if std::env::var("WOW_STALL").is_ok() {
|
|
app.add_plugins(crate::capture::StallPlugin);
|
|
}
|
|
// The ribbon-trail census: `WOW_TRAIL_CENSUS=<secs>[,<every>]` prints one line per live
|
|
// weapon/spell streak — its committed extent in WORLD space, which is the observable:
|
|
// a rider standing still on a moving deck must draw a SHORT streak, because its edges
|
|
// are stored on the deck and re-projected through the deck's live pose. A long one is
|
|
// a streak drawn against the world while its owner stands still (see
|
|
// `capture::TrailCensusPlugin`).
|
|
if std::env::var("WOW_TRAIL_CENSUS").is_ok() {
|
|
app.add_plugins(crate::capture::TrailCensusPlugin);
|
|
}
|
|
// The unit-visual census: `WOW_UNIT_VISUALS=<secs>[,<every>]` prints one line per
|
|
// streamed entity near the body — whether it got a debug cube, real geometry, or
|
|
// nothing at all. The instrument B13 was missing: a black slab in a screenshot cannot
|
|
// say whether the display named no model (our gap) or named one that draws nothing
|
|
// (an invisible trigger creature — see `capture::UnitVisualsPlugin`, decision 1403).
|
|
if std::env::var("WOW_UNIT_VISUALS").is_ok() {
|
|
app.add_plugins(crate::capture::UnitVisualsPlugin);
|
|
}
|
|
// The motion-jitter meter: `WOW_JITTER=<name-substr>[,<start_s>]` prints one line per
|
|
// frame for the nearest matching unit — the camera, root and pose terms of its
|
|
// rendered position, each as a first AND second difference, in mm and in pixels at
|
|
// its own distance. Δ alone cannot tell a slow idle from noise; Δ² can, and Δ²/dt²
|
|
// says whether a ragged Δ is bad arithmetic or merely uneven frame times (see
|
|
// `capture::JitterMeterPlugin`).
|
|
if std::env::var("WOW_JITTER").is_ok() {
|
|
app.add_plugins(crate::capture::JitterMeterPlugin);
|
|
}
|
|
// The dress census: `WOW_DRESS_CENSUS=<secs>[,<every>]` prints one line per streamed
|
|
// PLAYER — what the wire asked for (`PLAYER_FLAGS`' hide bits), what we resolved
|
|
// (helm/cloak display ids) and what is actually attached — plus a `contradictions=`
|
|
// count for a body dressed in a piece its own preference asked us to hide. B123's
|
|
// instrument (see `capture::DressCensusPlugin`, decision 1472).
|
|
if std::env::var("WOW_DRESS_CENSUS").is_ok() {
|
|
app.add_plugins(crate::capture::DressCensusPlugin);
|
|
}
|
|
// The reveal audit: `WOW_REVEAL=<frames>` prints one line per frame from a snap —
|
|
// the cover, every residency term behind `presentable()`, the settle hold and the
|
|
// retained pass's collected-vs-published regions. The instrument for "the teleport
|
|
// showed me a world with no buildings in it" (see `capture::RevealAuditPlugin`).
|
|
if std::env::var("WOW_REVEAL").is_ok() {
|
|
app.add_plugins(crate::capture::RevealAuditPlugin);
|
|
}
|
|
// The entity census: `WOW_ENTITY_CENSUS=<secs>` prints per-archetype entity counts once —
|
|
// what the resident entity count is made of (see `capture::EntityCensusPlugin`).
|
|
if std::env::var("WOW_ENTITY_CENSUS").is_ok() {
|
|
app.add_plugins(crate::capture::EntityCensusPlugin);
|
|
}
|
|
// The schedule census: `WOW_SCHED_CENSUS=1` prints every schedule's systems with their
|
|
// executor-relevant flags, both worlds, then exits — the structural inventory under
|
|
// the orchestration bands (see `capture::SchedCensusPlugin`, decision 1437).
|
|
if std::env::var("WOW_SCHED_CENSUS").is_ok() {
|
|
app.add_plugins(crate::capture::SchedCensusPlugin);
|
|
}
|
|
// The melee live probe: `WOW_PROBE=melee` auto-fights the nearest enemy so the dbg-trace
|
|
// sink can record the combat-text timeline (see `capture::ProbeMeleePlugin`).
|
|
if std::env::var("WOW_PROBE").as_deref() == Ok("melee") {
|
|
app.add_plugins(crate::capture::ProbeMeleePlugin);
|
|
}
|
|
// The partner live probe: `WOW_PROBE=partner` auto-accepts group invites — the party arc's
|
|
// second-client instrument (decision 0434; see `capture::ProbePartnerPlugin`).
|
|
if std::env::var("WOW_PROBE").as_deref() == Ok("partner") {
|
|
app.add_plugins(crate::capture::ProbePartnerPlugin);
|
|
}
|
|
// The sea-crossing live probe: `WOW_PROBE=crossing` boards a cross-continent boat and reports
|
|
// the map seam surviving — decision 0455's instrument (see `capture::ProbeCrossingPlugin`).
|
|
if std::env::var("WOW_PROBE").as_deref() == Ok("crossing") {
|
|
app.add_plugins(crate::capture::ProbeCrossingPlugin);
|
|
}
|
|
// The taxi-flight live probe: `WOW_PROBE=taxi` opens the flight-master menu on the real wire
|
|
// and rides Stormwind → Sentinel Hill to a measured verdict — decision 0484's end-to-end
|
|
// instrument (see `capture::ProbeTaxiPlugin`).
|
|
if std::env::var("WOW_PROBE").as_deref() == Ok("taxi") {
|
|
app.add_plugins(crate::capture::ProbeTaxiPlugin);
|
|
}
|
|
// The guard-directions live probe: `WOW_PROBE=guardpoi` asks a Stormwind guard where the
|
|
// weapons trainer is and reports the marker `SMSG_GOSSIP_POI` left on the map, checked
|
|
// field by field against the server's own row (see `capture::ProbeGuardPoiPlugin`).
|
|
if std::env::var("WOW_PROBE").as_deref() == Ok("guardpoi") {
|
|
app.add_plugins(crate::capture::ProbeGuardPoiPlugin);
|
|
}
|
|
// The mail-arc live probe: `WOW_PROBE_MAIL=1` GM-mails the probe's own character, opens the
|
|
// Goldshire mailbox on the real wire, and drives the inbox/take/send/delete surface through
|
|
// the live Lua VM — decisions 0544/0548's end-to-end instrument (see `capture::ProbeMailPlugin`).
|
|
if std::env::var("WOW_PROBE_MAIL").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeMailPlugin);
|
|
}
|
|
// The auction-arc live probe: `WOW_PROBE_AUCTION=1` GM-hops to a Stormwind auctioneer,
|
|
// greets it on the wire and drives browse/throttle/sell/owner-list/cancel through the
|
|
// live Lua VM — decision 1511's end-to-end instrument (see `capture::ProbeAuctionPlugin`).
|
|
if std::env::var("WOW_PROBE_AUCTION").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeAuctionPlugin);
|
|
}
|
|
// The bank-arc live probe: `WOW_PROBE_BANK=1` GM-hops to a pure banker, drives the whole
|
|
// six-opcode bank wire (activate/deposit/withdraw/buy-slot/refusal) — decision 0604's
|
|
// end-to-end instrument (see `capture::ProbeBankPlugin`).
|
|
if std::env::var("WOW_PROBE_BANK").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeBankPlugin);
|
|
}
|
|
// The innkeeper-bind live probe: `WOW_PROBE_BINDER=1` GM-hops to Innkeeper Keldamyr, asserts
|
|
// the bind row's icon reads "binder", selects it, and answers the server's confirm through the
|
|
// live VM's own `ConfirmBinder()` — decision 1331's end-to-end instrument, the evidence that
|
|
// closes B249 (see `capture::ProbeBinderPlugin`).
|
|
if std::env::var("WOW_PROBE_BINDER").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeBinderPlugin);
|
|
}
|
|
// The NPC-service ladder live probe: `WOW_PROBE_SERVICE=1` walks four real NPCs, one per
|
|
// interesting `UNIT_NPC_FLAGS` shape, and reports the arm the shipped ladder takes and the
|
|
// window that actually opened — decision 1861's end-to-end instrument, and the standing
|
|
// answer to "does right-clicking a trainer open the trainer window or a gossip menu?"
|
|
// (see `capture::ProbeServicePlugin`).
|
|
if std::env::var("WOW_PROBE_SERVICE").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeServicePlugin);
|
|
}
|
|
// The GM trouble-ticket live probe: `WOW_PROBE_GMTICKET=1` drives the whole five-opcode
|
|
// ticket wire through the live VM's own bindings — queue status, clean slate, file, edit,
|
|
// abandon — and prints the row the server must have stored so the operator can check the
|
|
// map/position the client never reads back (decision 1673's end-to-end instrument; see
|
|
// `capture::ProbeGmTicketPlugin`).
|
|
if std::env::var("WOW_PROBE_GMTICKET").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeGmTicketPlugin);
|
|
}
|
|
// The guild-charter live probe: `WOW_PROBE_CHARTER=1` GM-hops to the Stormwind guild
|
|
// registrar, asserts the charter row's icon reads "petition", buys a charter through the
|
|
// registrar's own window, opens it with a real bag right-click and renames it — decision
|
|
// 1672's end-to-end instrument (see `capture::ProbeCharterPlugin`).
|
|
if std::env::var("WOW_PROBE_CHARTER").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeCharterPlugin);
|
|
}
|
|
// The world-book live probe: `WOW_PROBE_BOOK=1` teleports to the Old Town plaque and
|
|
// measures what having the item-text reader open costs per frame, closed vs open —
|
|
// B240's instrument (see `capture::ProbeBookPlugin`).
|
|
if std::env::var("WOW_PROBE_BOOK").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeBookPlugin);
|
|
}
|
|
// The chest live probe: `WOW_PROBE_CHEST=1` parks at a real chest spawn, opens it on the
|
|
// click's own route and reports the self unit's base anim id before/during/after — B84's
|
|
// instrument, the numeric answer to "does the player kneel at a chest" (decision 1471;
|
|
// see `capture::ProbeChestPlugin`).
|
|
if std::env::var("WOW_PROBE_CHEST").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeChestPlugin);
|
|
}
|
|
// The GameObject-questgiver live probe: `WOW_PROBE_GOQUEST=1` parks at the Goldshire
|
|
// wanted poster and reports the dialog status the server answers for it, below and
|
|
// above the quest's own MinLevel — the numeric answer to "quest objects are never
|
|
// status-queried" (decision 1872; see `capture::ProbeGoQuestPlugin`).
|
|
if std::env::var("WOW_PROBE_GOQUEST").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeGoQuestPlugin);
|
|
}
|
|
// The openable-item live probe: `WOW_PROBE_CLAM=1` stocks a clam, right-clicks it
|
|
// through the live VM's own `UseContainerItem` and reports whether a loot window opens
|
|
// on the item's own guid — the numeric answer to the director's "clams don't open"
|
|
// (decision 1531; see `capture::ProbeClamPlugin`).
|
|
if std::env::var("WOW_PROBE_CLAM").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeClamPlugin);
|
|
}
|
|
// The cast-cancel live probe: `WOW_PROBE=castcancel` hearths and presses W mid-cast — the
|
|
// local self-cancel's end-to-end timing instrument (see `capture::ProbeCastCancelPlugin`).
|
|
if std::env::var("WOW_PROBE").as_deref() == Ok("castcancel") {
|
|
app.add_plugins(crate::capture::ProbeCastCancelPlugin);
|
|
}
|
|
// The char-create live probe: `WOW_PROBE_CHARCREATE="<name>[,race,class,gender,…]"` creates (and
|
|
// cleans up) a character at select to verify the char-create/delete wire (decision 0423 phase 1;
|
|
// see `capture::ProbeCharCreatePlugin`).
|
|
if std::env::var("WOW_PROBE_CHARCREATE").is_ok() {
|
|
app.add_plugins(crate::capture::ProbeCharCreatePlugin);
|
|
}
|
|
// The live FPS probe: `WOW_LIVE_FPS=<frames>` samples frame times on a NORMAL connected run
|
|
// and exits — the harness probe's numbers with the live world in (see `capture::LiveFpsPlugin`).
|
|
if std::env::var("WOW_LIVE_FPS").is_ok() {
|
|
app.add_plugins(crate::capture::LiveFpsPlugin);
|
|
}
|
|
// The scripted probe drivers that live beside `capture` rather than in `player/`
|
|
// (decision 1174) — the mouse-turn (`WOW_PROBE_LOOK`, decision 0621), the swim-pitch aim
|
|
// (`WOW_PROBE_PITCH`) and the camera park (`WOW_PROBE_CAM`, decision 0653). Added
|
|
// unconditionally because each plugin's own `from_env` is its gate, so the variable's name is
|
|
// spelled in exactly one place; all three order themselves before `player::PlayerControlSet`.
|
|
app.add_plugins(crate::capture::ProbeLookPlugin);
|
|
app.add_plugins(crate::capture::ProbePitchPlugin);
|
|
app.add_plugins(crate::capture::ProbeCamPlugin);
|
|
// The FPS journal: `WOW_FPS_JOURNAL=<csv>` appends per-second position + frame-time rows on a
|
|
// director-driven run — "where does it dip" as coordinates (see `perf::FpsJournalPlugin`).
|
|
if std::env::var("WOW_FPS_JOURNAL").is_ok() {
|
|
app.add_plugins(crate::perf::FpsJournalPlugin);
|
|
}
|
|
}
|
|
}
|
|
}
|