Comments name no chat reporter, channel or message id, and quote no report (#90)
Comments that named a chat reporter, cited a chat channel or message id, or quoted a report word for word now state the symptom in their own words. The test fixtures that used real character names use invented ones. Verified: 52 of the 56 files strip to identical code with their comments removed; the other 4 differ only in the fixture names, and with those mapped back they strip identical too. `cargo fmt --check` is clean, and the `ui_party`, `unit_frame_tests` and `capture` tests pass with the install present and no skips. Record numbers still in these comments go with the comment pass (#41). Fixes #34 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
co-authored by
Claude Opus 5.5
parent
7b66dac9db
commit
1ee056fc53
@@ -723,7 +723,7 @@ pub(super) fn seed_ui_fixture(
|
||||
// Raised through the real registry entry, not a hand-built frame: the text comes from
|
||||
// the chain's own `INVITATION` GlobalString and the two buttons from ACCEPT/DECLINE,
|
||||
// so the capture exercises the same Show path a real invite takes.
|
||||
if let Err(e) = script.run(r#"StaticPopup_Show("PARTY_INVITE", "Thalyn")"#) {
|
||||
if let Err(e) = script.run(r#"StaticPopup_Show("PARTY_INVITE", "Brisca")"#) {
|
||||
warn!("capture: ui-partyinvite seed failed to raise the dialog: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
//! The world-book live probe (`WOW_PROBE_BOOK=1`) — B240's instrument: what does having the item-
|
||||
//! text reader open actually cost per frame, on the real object the report names?
|
||||
//!
|
||||
//! Reported on Discord, 2026-08-09: *"page text with html text absolutely
|
||||
//! annihilates performance"* — ~50% fps drop (62 → 36 fps, 16.0 → 28.0 ms) while the *Alliance
|
||||
//! Military Ranks* plaque's reader is up, recovering the moment it closes. That is a **frame-cost
|
||||
//! The symptom: a ~50% fps drop (62 → 36 fps, 16.0 → 28.0 ms) while the *Alliance Military Ranks*
|
||||
//! plaque's HTML page is up in the reader, recovering the moment it closes. That is a **frame-cost
|
||||
//! A/B**, and eyeballing an fps counter is exactly the way not to settle one (docs/METHOD.md's
|
||||
//! "timing and feel are measured, never eyeballed"). So this probe teleports to the plaque, samples
|
||||
//! the UI pass's own per-phase meter ([`crate::ui_script::UiFrameCost`]) with the reader CLOSED,
|
||||
@@ -13,7 +12,7 @@
|
||||
//!
|
||||
//! The object: `GameObject` 3011 (`gameobject_template` entry 2857, `GAMEOBJECT_TYPE_TEXT` = 9)
|
||||
//! in Stormwind's Old Town, whose `data[0]` is `page_text` 2676 — a 647-byte HTML body. The
|
||||
//! `.go xyz` below is Goudy's own reported position (his debug panel: `-8760.2 402.3 103.9`).
|
||||
//! `.go xyz` below is the reported standing position, off the debug panel: `-8760.2 402.3 103.9`.
|
||||
//!
|
||||
//! ## The run recipe
|
||||
//!
|
||||
@@ -146,7 +145,7 @@ fn drawn_strings(script: &UiScript) -> Vec<String> {
|
||||
}
|
||||
|
||||
/// **B240's render half, checked where it was reported.** The page body is HTML; if the parse ever
|
||||
/// falls back, the reader draws the markup itself — which is what Goudy photographed. So: no drawn
|
||||
/// falls back, the reader draws the markup itself — the reported symptom. So: no drawn
|
||||
/// string may contain a tag, and the page's own lines must each be there as their own block.
|
||||
fn report_render(script: &UiScript) -> u32 {
|
||||
let drawn = drawn_strings(script);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! The chest live probe (`WOW_PROBE_CHEST=1`) — **B84's instrument**: does the player kneel at an
|
||||
//! open chest?
|
||||
//!
|
||||
//! Reported on Discord, 2026-07-26: *"No looting/opening animation when using a
|
||||
//! chest"*, with a benilla/reference pair at the same Mirror Lake Orchard chest — the reference
|
||||
//! The symptom: no looting animation when opening a chest. At the same Mirror Lake Orchard chest,
|
||||
//! benilla and the reference side by side show the reference
|
||||
//! gnome kneeling over the open loot window, ours standing bolt upright. Decision 0515 had shipped
|
||||
//! the kneel keyed to the `CMSG_LOOT` send alone and recorded a chest's silence as *correct*; 1471
|
||||
//! is the correction (the real client also arms the latch in `OnLootResponse 0x5eb900`, and a chest
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
//! the body saying what the wire asked for, what we resolved, and what is actually hanging off the
|
||||
//! skeleton right now.
|
||||
//!
|
||||
//! It exists because those are three different things and a screenshot conflates all three. B123 —
|
||||
//! *"show helm and cloak preferences are not taken into account"* — is exactly a gap between the
|
||||
//! It exists because those are three different things and a screenshot conflates all three. A
|
||||
//! helm or cloak the player chose to hide, drawn anyway, is exactly a gap between the
|
||||
//! first and the third: the descriptor carried `PLAYER_FLAGS_HIDE_HELM`, the resolver ignored it,
|
||||
//! and a helm model hung off the head. Nothing in a picture separates that from "this character
|
||||
//! simply has a helm equipped", which is why the report needed a reader rather than an eye.
|
||||
|
||||
@@ -89,7 +89,7 @@ fn fire_particle_census(
|
||||
// included — has already been discarded by the wall. Before 0678 it was routinely non-zero,
|
||||
// because `doodad_fade_alpha` admits any owner over `NEVER_FADE_RADIUS` at *every* distance
|
||||
// and nothing else bounded depth; that is precisely "all effects render at unlimited
|
||||
// distance", and precisely the reporter's "the terrain is not even rendered that far".
|
||||
// distance", out past the point where the terrain itself stops drawing.
|
||||
// It must now be **0**: past the wall the gate hides the emitter and freezes its pool.
|
||||
//
|
||||
// `beyond_wall` (verdict ignored) stays as the denominator — emitters *exist* out there and
|
||||
|
||||
@@ -1264,8 +1264,8 @@ fn a_kit_with_a_chain_char_proc_asks_for_a_beam_from_both_dispatcher_sites() {
|
||||
|
||||
// ── The real-chain shooter pin (bug B307) ────────────────────────────────────────────────────
|
||||
//
|
||||
// Reported 2026-08-22: "characters don't use reload anim when using autoshot with any ranged
|
||||
// weapons (bows/crossbows/guns)" — the shooter fires from a still pose. Every link above this
|
||||
// The symptom: on Auto Shot, with any ranged weapon (bow, crossbow, gun), a shooter plays no
|
||||
// reload animation and fires from a still pose. Every link above this
|
||||
// point is tested on SYNTHETIC tables, and every router test spawns a bare unit with no
|
||||
// `NetEntity`/`ObjectStore` — so [`super::WeaponVisualSrc::caster`], the one lookup that turns a
|
||||
// PLAYER's equipped ranged weapon into the substitute visual the whole merge hangs on, returns
|
||||
@@ -1485,8 +1485,8 @@ fn a_real_gun_or_crossbow_shooters_auto_shot_go_plays_attackrifle() {
|
||||
}
|
||||
}
|
||||
|
||||
/// The START arm of the same chain — the **pull**, which is what the report calls the "reload
|
||||
/// anim": `SMSG_SPELL_START` for Auto Shot arms `CastHold { anim_id: 105 }` (LoadBow) on a bow
|
||||
/// The START arm of the same chain — the **pull**, the reload animation itself:
|
||||
/// `SMSG_SPELL_START` for Auto Shot arms `CastHold { anim_id: 105 }` (LoadBow) on a bow
|
||||
/// shooter, plus the ranged-slot marks the driver reads ([`RangedHold`], the sheath snap). One
|
||||
/// START per auto-repeat activation, so this is the clip that opens a volley.
|
||||
#[test]
|
||||
|
||||
@@ -807,7 +807,7 @@ impl Plugin for EntitiesPlugin {
|
||||
.add_message::<live_display::DisplaySwapped>()
|
||||
.add_systems(Startup, setup_entities.after(AssetSet::Open))
|
||||
// The map-scope teardown (`world_map::MapChange`): drop every display/material dedup
|
||||
// so a map's assets actually die with it — the #bugs teleport leak.
|
||||
// so a map's assets actually die with it — the teleport leak.
|
||||
.add_systems(Update, (evict_display_caches, scope_entity_art))
|
||||
// Every streamed unit's collision height, the frame after `apply_net_updates` spawns it
|
||||
// (that stage's Commands are what create the entity, so this cannot be earlier). Its
|
||||
|
||||
@@ -215,7 +215,7 @@ fn arm_sequence_clock(
|
||||
// callers, so no M2 instance in the client ever exists with nothing armed. For a door/chest
|
||||
// the object-layer arm (`0x5f3930`) lands *after* it and overrides it; seeding first is what
|
||||
// stops the one-frame BIND POSE our state GOs used to render on their first displayed frame,
|
||||
// before `go_anim` had a chance to run — the "explodes for a split second" report. The seed
|
||||
// before `go_anim` had a chance to run, which reads as a one-frame explosion. The seed
|
||||
// is played THROUGH the transitions object so that first arm cleanly fades out of it;
|
||||
// playing it bare on the player would leave two clips live at once.
|
||||
let mut player = AnimationPlayer::default();
|
||||
|
||||
@@ -753,7 +753,7 @@ fn assemble(spec: &PreviewSpec, ctx: &mut PreviewCtx<'_, '_>) -> Option<Assemble
|
||||
// An item model is not just meshes, and for a while this loop treated it as if it were — it kept
|
||||
// the batches with no billboard flag and dropped everything else on the floor, so at character
|
||||
// select a worn item's own **effects** (its emitters, its camera-facing batches) simply did not
|
||||
// exist. That is `#bugs` B118 as nazriel filed it: the R14 PVP shoulders' sparkle is the item
|
||||
// exist. The R14 PVP shoulders' sparkle is the item
|
||||
// model's own emitters (decision 0813), and no part of it reached the booth. So each want
|
||||
// contributes up to four things at one seat: plain meshes ([`PreviewRider`]), camera-facing
|
||||
// batches ([`PreviewBillboard`]), its emitters ([`PreviewEffects`]), and — held weapons only —
|
||||
@@ -803,7 +803,7 @@ fn assemble(spec: &PreviewSpec, ctx: &mut PreviewCtx<'_, '_>) -> Option<Assemble
|
||||
}
|
||||
}
|
||||
// The item model's OWN particle emitters — the R14 PVP pauldron's `SPARKLE` twinkle
|
||||
// (`#bugs` B118, decision 0813), the held torch's flame. 95 `Item\` models hang one on a
|
||||
// (decision 0813), the held torch's flame. 95 `Item\` models hang one on a
|
||||
// billboard bone alone; the booth spawns them off a host at the attach point, each
|
||||
// billboard-chain emitter through a booth-camera frame.
|
||||
if !item.emitters.is_empty() {
|
||||
|
||||
@@ -746,7 +746,7 @@ fn spawn_slot(
|
||||
// The booth mirror for those same emitters (decision 0822): they spawn as FREE entities
|
||||
// below (the owner contract), never unit descendants, so a booth that mirrors the dressed
|
||||
// tree cannot see them — which is why the R14 pauldron's sparkle was absent from the paper
|
||||
// doll exactly as it was from the select screen (`#bugs` B118). One marker per item, on
|
||||
// doll exactly as it was from the select screen. One marker per item, on
|
||||
// the root that already reaps with it.
|
||||
if !dm.emitters.is_empty() {
|
||||
commands
|
||||
@@ -1407,7 +1407,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// **What a booth can see of an equipped item** (decision 0822, `#bugs` B118's paper-doll half).
|
||||
/// **What a booth can see of an equipped item** (decision 0822, the paper-doll half).
|
||||
/// An item model's camera-facing batch spawns as a world-ROOT card and its emitters as free
|
||||
/// owner-followed entities — neither is a unit descendant, so the portrait / paper-doll booths,
|
||||
/// which mirror the dressed tree, could not see either one and a worn item's effects were absent
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! The mount transition's law: **nothing on the rider is destroyed by mounting**.
|
||||
//!
|
||||
//! B199 (Frostshake, 08-03) is the report these pin: "mounting removes Ice Barrier's shield visual
|
||||
//! from the character — and dismounting does not bring it back". The old transition
|
||||
//! The case these pin: Ice Barrier's shield visual stays on the character through a mount, and is
|
||||
//! still there after the dismount. The old transition
|
||||
//! `despawn_related::<Children>()`'d the whole rider; the aura's persistent kit instance died with
|
||||
//! it and, because `FxAttached` lives on the *unit* and kept a dangling root while the aura never
|
||||
//! left its slot, nothing ever noticed. So these fixtures hang the two things that matter off a
|
||||
|
||||
@@ -203,11 +203,11 @@ pub(crate) fn screen_scale(window: Option<&Window>) -> f32 {
|
||||
/// 1619): past the aspect its art can fill, the booth camera renders into a centred box with black
|
||||
/// bars either side, and a chrome node anchored `right: 0` against the *window* lands out in the
|
||||
/// bar — the logo, the version line, Realmlist/Quit, the character list and Delete/Back all did, at
|
||||
/// 21:9 (Henhouse's 3440×1440 report). 1619 wrote that residue down and left it; this is the
|
||||
/// 21:9 (3440×1440). 1619 wrote that residue down and left it; this is the
|
||||
/// answer. Anchor the chrome to the **box** — the window's height, the box's width — and the glue
|
||||
/// canvas is exactly what a reference client of the box's own aspect would lay out on, which is
|
||||
/// what the reporter's own 1.12 shots are: a 16:9 client, centred by the monitor, everything
|
||||
/// inside it.
|
||||
/// what a 1.12 client on a 21:9 monitor looks like: a 16:9 client, centred by the monitor,
|
||||
/// everything inside it.
|
||||
///
|
||||
/// The screen's root stays full-window: the black bars are the booth camera's own *output* clear
|
||||
/// inside a window-sized render target (1619 §3), so the full-bleed scene pane that samples that
|
||||
|
||||
@@ -599,8 +599,8 @@ pub(in crate::net) fn extrapolate_remote_units(
|
||||
// open-loop for a remote, which has no fall election at all. So the resolve walked a
|
||||
// watched player through the floor at 1/36 yd a frame — 1.67 yd/s at 60 fps, 3.3 at 120 —
|
||||
// onto the terrain below, where the (now deleted) settled memo froze them for the session.
|
||||
// A standing player sends no packets, so nothing ever re-seated them: B197's fourth site,
|
||||
// and nazriel_0's "until I move around, then it pops up back to normal".
|
||||
// A standing player sends no packets, so nothing ever re-seated them: they stayed sunk
|
||||
// until they moved, and then popped back up.
|
||||
//
|
||||
// Deleting the memo with the gate is the point, not a side effect: it existed only to skip
|
||||
// this resolve for a flag-still mover whose answer was proven identical (1473 §3), and the
|
||||
|
||||
@@ -2376,7 +2376,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// **B364** (MarcusAga): right-clicking a row of the Who list swung the camera round to
|
||||
/// Right-clicking a row of the Who list must not swing the camera round to
|
||||
/// behind the character — over ~180°, on a body that never turned. The click itself was the
|
||||
/// UI's (1816's no-fall-through hit test opened the dropdown); only the camera's command word
|
||||
/// saw it, because the word's two mouse bits were built from `ButtonInput` with no UI term.
|
||||
|
||||
@@ -447,9 +447,9 @@ pub(super) fn swim_step(
|
||||
// `Some(own feet Y)` at the caller, never `None`). 0644 added this second arm *after* 0726 and
|
||||
// read the liquid straight from `surface_at` instead, so the exemption covered the cap and not
|
||||
// the settle: flying over a lake, `excess` is the entire altitude, the down-cast through open
|
||||
// air hits nothing, and one frame teleports the avatar onto the waterline. That is both
|
||||
// reporters' wording exactly — "from any height", and "*when moving* from land to water", the
|
||||
// `input_vel != ZERO` gate right here.
|
||||
// air hits nothing, and one frame teleports the avatar onto the waterline. That is the symptom
|
||||
// exactly: from any height, and only when moving from land to water, the `input_vel != ZERO`
|
||||
// gate right here.
|
||||
if surface_y.is_some() && input_vel != Vec3::ZERO {
|
||||
if let Some(surface_now) = surface_at(c - half_h) {
|
||||
let excess = settle_to_rest(c.y - half_h.y, surface_now, player.collision_height.0);
|
||||
|
||||
@@ -697,7 +697,7 @@ fn apply_self_move(
|
||||
/// controller driving nothing, which is the shape of the free camera this arc is about.
|
||||
///
|
||||
/// **Everything on this resource belonged to the mover that just ended, so all of it dies here**
|
||||
/// (decision 1542, B306 — reported and diagnosed by Liho). It used to clear a hand-picked six
|
||||
/// (decision 1542). It used to clear a hand-picked six
|
||||
/// fields, and the field it did not name was `modes`: `/logout` has vmangos root us for the
|
||||
/// countdown (`MiscHandler.cpp` `SetRooted(true)`), we ack the grant, and the next session never
|
||||
/// hears an unroot — the fresh server-side `Player` was never rooted, so there is nothing for it
|
||||
@@ -773,7 +773,7 @@ mod session_end_tests {
|
||||
p.wedged = true;
|
||||
}
|
||||
|
||||
/// **B306, at its own edge** (decision 1542; reported and diagnosed by Liho). `/logout` has
|
||||
/// **B306, at its own edge** (decision 1542). `/logout` has
|
||||
/// vmangos root the player for the countdown (`MiscHandler.cpp:329 SetRooted(true)`), which
|
||||
/// reaches us as the ack'd `SMSG_FORCE_MOVE_ROOT` above; the next login gets no unroot, because
|
||||
/// server-side the fresh `Player` was never rooted (`SendInitialPacketsBeforeAddToMap` re-sends
|
||||
|
||||
@@ -222,7 +222,7 @@ impl BoothBillboard {
|
||||
///
|
||||
/// The two lanes that feed it are the two lanes that mirror a dressed look — a live unit's
|
||||
/// [`crate::portrait::PortraitEffects`] markers, and the glue bake's [`super::PreviewEffects`] —
|
||||
/// and both mean the same two things: an equipped item's own emitters (decision 0813, `#bugs` B118)
|
||||
/// and both mean the same two things: an equipped item's own emitters (decision 0813)
|
||||
/// and the `ItemVisuals` glow a held weapon hangs on its own attachment points (decision 0805).
|
||||
pub(super) struct BoothEffects {
|
||||
pub(super) bone: u16,
|
||||
@@ -1300,7 +1300,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// **The booth twin of the equipped-item emitter's billboard frame** (decision 0813, carried to
|
||||
/// the select screen for `#bugs` B118). Same real numbers as
|
||||
/// the select screen). Same real numbers as
|
||||
/// `billboard::tests::an_item_emitters_billboard_frame_puts_it_behind_the_pivot`
|
||||
/// (`LShoulder_Mail_PVPAlliance_C_01`: billboard bone 1 pivot `(-0.012, 0.162, -0.060)`, sparkle
|
||||
/// emitter `(-0.252, 0.178, -0.046)`, raw WoW model space), but through THIS system — and here the
|
||||
|
||||
@@ -559,7 +559,7 @@ pub(crate) struct PreviewBillboard {
|
||||
/// Two sources produce these, and the booth treats them identically:
|
||||
///
|
||||
/// - **An equipped item's own emitters** — the R14 PVP pauldron's `SPARKLE` twinkle, the held
|
||||
/// torch's flame (decision 0813; `#bugs` B118 is *this* case, and the select screen showed
|
||||
/// torch's flame (decision 0813; the select screen showed
|
||||
/// nothing at all until it was carried here). Seat = the body's attach point for that slot.
|
||||
/// - **An item glow** (decision 0805) — the `Spells\Enchantments\*.mdx` effect a held weapon's
|
||||
/// `ItemVisuals` id hangs on the weapon's own attachment point; all but three of the 35 shipped
|
||||
@@ -1608,7 +1608,7 @@ pub(super) fn sync_glue_booth(
|
||||
},
|
||||
);
|
||||
// The worn items' effects: an equipped item model's OWN emitters (decision 0813 — the R14
|
||||
// pauldron's sparkle, the torch's flame; `#bugs` B118) and the held weapons' `ItemVisuals`
|
||||
// pauldron's sparkle, the torch's flame) and the held weapons' `ItemVisuals`
|
||||
// glows (decision 0805). One host per effect model at its seat, emitters owned by it — the
|
||||
// scene-brazier recipe (0539 §5), so they draw against THIS camera and fog on the scene's own
|
||||
// light. Children of the booth root through their joint, so the next re-bake's
|
||||
|
||||
@@ -98,7 +98,7 @@ pub(super) struct BoothLight {
|
||||
/// Reap the booth twins whose world source material died (`AssetEvent::Removed` — e.g. the
|
||||
/// map-scope teardown, `world_map::MapChange`). A twin is its own asset pinned only by this
|
||||
/// cache, so without the reap every world material ever baked through a booth would survive
|
||||
/// the teardown forever (the #bugs teleport leak, multiplied per rig). A live bake keeps its
|
||||
/// the teardown forever (the teleport leak, multiplied per rig). A live bake keeps its
|
||||
/// twin through its own `MeshMaterial3d` clone; only the dedup entry drops.
|
||||
pub(super) fn reap_dead_variants(
|
||||
mut events: MessageReader<AssetEvent<WowModelMaterial>>,
|
||||
|
||||
@@ -384,8 +384,8 @@ impl PortraitSeat {
|
||||
}
|
||||
|
||||
/// Stamped on an **effect-bearing model** riding a unit — the equipped item whose own emitters are
|
||||
/// its whole look (the R14 PVP pauldron's `SPARKLE` twinkle, the held torch's flame — decision 0813,
|
||||
/// `#bugs` B118) and the `ItemVisuals` glow a held weapon hangs on its own attachment points
|
||||
/// its whole look (the R14 PVP pauldron's `SPARKLE` twinkle, the held torch's flame — decision
|
||||
/// 0813) and the `ItemVisuals` glow a held weapon hangs on its own attachment points
|
||||
/// (decision 0805). The world emitters are *free* entities the owner contract walks
|
||||
/// ([`benilla_world::particles::spawn_emitter`]), never unit descendants, so — like [`PortraitBillboard`] —
|
||||
/// this marker is how a booth learns they exist at all: the mirror carries the emitter records plus
|
||||
@@ -706,8 +706,8 @@ struct Booth {
|
||||
/// **skipped** — silently, with no error and no missing asset ([`PipeWatch::compiling`] has
|
||||
/// the byte references). A live view redraws it a few frames later and nobody ever sees it.
|
||||
/// A one-shot portrait bake does not: [`BOOTH_SETTLE_FRAMES`] elapse, the camera sleeps, and
|
||||
/// the still keeps the hole for the rest of the session — which is exactly the report
|
||||
/// ("*sometimes hair missing, sometimes face, totally random*"): hair is the alpha-key
|
||||
/// the still keeps the hole for the rest of the session — which is exactly the symptom (the
|
||||
/// hair missing one time, the face the next, at random): hair is the alpha-key
|
||||
/// pipeline, the body the opaque one, the eye-glow card the additive one, and which of the
|
||||
/// three had landed by the fourth frame is a race. It is invisible on macOS, where the same
|
||||
/// compile `block_on`s the render thread instead: the whole class exists on the reporters'
|
||||
@@ -874,7 +874,7 @@ fn log_bake(
|
||||
) {
|
||||
if booth_log() {
|
||||
// The **attach ids** the bake is dressed at, sorted and de-duplicated. Counts alone could
|
||||
// not answer the question `#bugs` B324 asked — *what is hanging on this doll?* — and the
|
||||
// not answer the question — *what is hanging on this doll?* — and the
|
||||
// answer is one line: a hunter mid-shot read `at=[2,35]` (bow at HandLeft, arrow at
|
||||
// HandArrow) where the reference can only ever show `[2]`. It is also the retest readout
|
||||
// for the reference's attach reset ([`attach_reset`]): no id in the cut family may appear.
|
||||
@@ -909,7 +909,7 @@ fn log_bake(
|
||||
/// Both bake sites used to paper over that with `unwrap_or(PortraitAnchors { .., pivot_height: 0.0,
|
||||
/// .. })`. That is not a neutral default: the retired body fit floored the head signal at `0.1` for
|
||||
/// "a hypothetical bounds-less display", so zero anchors aimed the camera at a 0.1-unit-tall
|
||||
/// subject — the paper doll "zoomed into the max" and the wrong-size portrait of `#bugs` B106. (1089
|
||||
/// subject — the paper doll zoomed all the way in, and a portrait at the wrong size. (1089
|
||||
/// retired that fit, and zero anchors are no longer a *zoom* — but they are still the wrong camera:
|
||||
/// an unbuilt display has no `cameras[1]` to read, so the pane would latch the fixed fallback rig
|
||||
/// aimed at a zero bbox centre.) And it latches: the camera is aimed once per bake, and the parts
|
||||
@@ -1323,8 +1323,8 @@ pub(crate) fn new_target_image_sized(width: u32, height: u32) -> Image {
|
||||
// FLOAT, not `Rgba8Unorm`, is B126 (decision 0804). Quantizing *un-encoded* values to 8 bits
|
||||
// is a precision collapse exactly where the eye is most sensitive: the only display levels
|
||||
// reachable below display byte 100 are `srgb(k/255)` = 0, 13, 22, 28, 34, 38, … — ~25 steps
|
||||
// where the 8-bit gamma backbuffer this feeds has 100. That is the reported "colors are 16
|
||||
// bit instead of 32" banding on the glue screens' trees and skybox, measured on the
|
||||
// where the 8-bit gamma backbuffer this feeds has 100. That is the banding that reads as
|
||||
// 16-bit colour on the glue screens' trees and skybox, measured on the
|
||||
// histogram of a char-select capture (15 of 16 predicted ladder values hit). The pipeline's
|
||||
// *semantics* are unchanged by this — same values, same single encode downstream, just not
|
||||
// rounded to a 256-step linear grid on the way through.
|
||||
@@ -1604,7 +1604,6 @@ fn test_mode(cached: &mut Option<bool>) -> bool {
|
||||
/// one Lua round-trip later. Every *snap* sheath change — the combat auto-draw, the stand-state
|
||||
/// stow, the descriptor apply — takes `bInstant != 0` and reaches the queue only through the
|
||||
/// enchant-gated `0x5eed50`, which is why drawing a bow on a mob does **not** move the doll.
|
||||
/// That split is `#bugs` B324.
|
||||
/// - **An item's glow instances landing** — ours, not the reference's. Its widget duplicates a
|
||||
/// model the world had already finished building; our `ItemVisuals` models stream in, and a key
|
||||
/// blind to their arrival would leave a permanently-glowing weapon glowing nothing in the
|
||||
@@ -1642,7 +1641,7 @@ fn bump_model_revision(
|
||||
/// re-attaches), and so does a melee weapon whose `SheatheType` is 0. The worn
|
||||
/// quiver rides `0x1a` on the same gate.
|
||||
///
|
||||
/// [`SnapKey`] is blind to every one of them, which is the whole mechanism of `#bugs` B324: a
|
||||
/// [`SnapKey`] is blind to every one of them, which is the whole mechanism: a
|
||||
/// widget's duplicate must not notice that the world drew a weapon.
|
||||
fn sheath_lane(attach: Option<u16>) -> bool {
|
||||
matches!(attach, Some(0..=2 | 26..=28 | 30..=33))
|
||||
@@ -1765,7 +1764,7 @@ impl SnapKey {
|
||||
/// breath, the two nameplates, Base, Head, the two spell hands, Special1–3, Chest — and
|
||||
/// **HandArrow (`0x23`)**, the nocked arrow. Equipment survives; effects do not.
|
||||
///
|
||||
/// `0x23` is the one that shows up in a bug report (`#bugs` B324): our booths mirrored the
|
||||
/// `0x23` is the one that shows up in a bug report: our booths mirrored the
|
||||
/// nocked arrow onto the character-window doll, where the reference can never draw one.
|
||||
fn attach_reset(attach: Option<u16>) -> bool {
|
||||
matches!(attach, Some(0xf..=0x19 | 0x1d | 0x22 | 0x23))
|
||||
@@ -2244,7 +2243,7 @@ fn sync_portraits(
|
||||
/// The key is **not** the mirrored geometry. A `<PlayerModel>` duplicates the unit's model once and
|
||||
/// renders a copy the world can no longer reach, and it re-takes that copy on four things — the
|
||||
/// pane showing, a change of dress, an explicit model event, a resize. Mirroring live put a bow
|
||||
/// drawn in combat straight onto the character sheet (`#bugs` B324); [`SnapKey`] carries the whole
|
||||
/// drawn in combat straight onto the character sheet; [`SnapKey`] carries the whole
|
||||
/// law and its byte provenance.
|
||||
fn sync_paperdoll(
|
||||
mut commands: Commands,
|
||||
@@ -2617,7 +2616,7 @@ fn sync_body_booth(
|
||||
.map_or(1.0, |a| framing::pane_root_scale(a, display_aspect));
|
||||
// A changed pane aspect re-runs the same path: the camera's projection depends on it, and it
|
||||
// only ever moves once — the first frame the window is drawn.
|
||||
// **The snapshot compare** — the whole of `#bugs` B324. This used to be `LookKey`, the
|
||||
// **The snapshot compare.** This used to be `LookKey`, the
|
||||
// mirrored geometry, which moved the instant the world drew a weapon. It is now the
|
||||
// reference's own re-`SetUnit` set, and a draw is not in it.
|
||||
let parts_changed = booth.snap.as_ref() != Some(&key);
|
||||
@@ -2667,8 +2666,8 @@ fn sync_body_booth(
|
||||
twins: BoothTwins::default(),
|
||||
})
|
||||
.collect();
|
||||
// The worn items' effects (decision 0822) — an equipped item's own emitters (0813, `#bugs`
|
||||
// B118) and a held weapon's `ItemVisuals` glow (0805). Collected BEFORE the teardown for the
|
||||
// The worn items' effects (decision 0822) — an equipped item's own emitters (0813) and a
|
||||
// held weapon's `ItemVisuals` glow (0805). Collected BEFORE the teardown for the
|
||||
// same reason as everything else here; spawned after the model, which is what hands us the
|
||||
// joints they seat on.
|
||||
let booth_effects: Vec<BoothEffects> = effects
|
||||
@@ -3356,7 +3355,7 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
/// **`#bugs` B324, the half that removes the bow.** In combat the auto-draw is a *snap*
|
||||
/// **The half that removes the bow.** In combat the auto-draw is a *snap*
|
||||
/// (`SetSheatheState(…, bInstant != 0)`), which reaches `UNIT_MODEL_CHANGED`'s one fire site
|
||||
/// only through the enchant-gated `0x5eed50` — so the reference's doll never hears about it.
|
||||
/// Ours re-baked on the mirrored geometry, and a drawn bow IS mirrored geometry.
|
||||
@@ -3449,7 +3448,7 @@ mod tests {
|
||||
assert!(bare != helmed);
|
||||
}
|
||||
|
||||
/// **`#bugs` B324 — the nocked arrow reached the character-window doll.** A hunter shooting in
|
||||
/// **The nocked arrow never reaches the character-window doll.** A hunter shooting in
|
||||
/// combat has a bow at HandLeft(2) and an arrow at HandArrow(0x23); our booths mirrored both,
|
||||
/// and the reference's doll can never draw the arrow: every model widget duplicates the unit's
|
||||
/// attachment tree and then detaches `0x23` with fourteen other transient ids
|
||||
@@ -3601,8 +3600,8 @@ mod tests {
|
||||
}
|
||||
|
||||
/// **A still is never committed while the render world is still building pipelines** —
|
||||
/// report B331, the player's own portrait baking with the face (or the hair, or the shoulder)
|
||||
/// simply absent, "totally random", on the reporter's Windows machine.
|
||||
/// the symptom: the player's own portrait baking with the face (or the hair, or the shoulder)
|
||||
/// simply absent, at random, on Windows.
|
||||
///
|
||||
/// The mechanism is not a missing asset and never shows up as one: off macOS Bevy builds each
|
||||
/// pipeline variant on the async pool, and `SetItemPipeline` answers a not-yet-built variant
|
||||
|
||||
@@ -37,9 +37,8 @@
|
||||
//! **Two deliberate divergences, both recorded in 1487:**
|
||||
//!
|
||||
//! 1 · **PNG, where the reference writes TGA.** An uncompressed 32-bit Targa of a modern window is
|
||||
//! ~8 MB and no ordinary tool on a 2026 desktop previews one. This is the "mandatory unfaithful
|
||||
//! change" the report came in with; the mechanism, the folder and the naming are otherwise the
|
||||
//! reference's.
|
||||
//! ~8 MB and no ordinary tool on a 2026 desktop previews one. The mechanism, the folder and the
|
||||
//! naming are otherwise the reference's.
|
||||
//! 2 · **`benilla-config/Screenshots/`, not `<install>/Screenshots/`** — decision 1486's rule:
|
||||
//! benilla reads a WoW install and never writes to one. Same folder name, different parent.
|
||||
//!
|
||||
|
||||
@@ -1292,7 +1292,7 @@ fn stop_all_channels(mut out: NonSendMut<SoundOutput>) {
|
||||
|
||||
/// Drop the decoded-SFX cache on a cross-map transition (`world_map::MapChange` — see its doc):
|
||||
/// kit variations are decoded on demand, so a new map's soundscape rebuilds its own working set
|
||||
/// while the old map's decodes stop occupying RAM forever (the #bugs teleport leak). Playing
|
||||
/// while the old map's decodes stop occupying RAM forever (the teleport leak). Playing
|
||||
/// channels own their frames (`StaticSoundData` clones share them), so nothing audible cuts.
|
||||
fn evict_kit_cache(
|
||||
mut changes: MessageReader<benilla_world::world_map::MapChange>,
|
||||
|
||||
@@ -782,8 +782,8 @@ fn set_lua_music(
|
||||
/// this way they can be asserted without standing up a device.
|
||||
///
|
||||
/// **It does not touch the zone track.** `[0xb06cc4]`'s only mentions in the reference's function
|
||||
/// sit *past* the NULL branch, which is exactly what the reporter said from the outside: *"not
|
||||
/// used to stop normal music played by being in a zone"*.
|
||||
/// sit *past* the NULL branch, so `StopMusic` ends a `PlayMusic` track and never the zone's own
|
||||
/// music.
|
||||
fn take_lua_music_slot(zone: &mut ZoneAudio, now: f64) {
|
||||
if let Some(mut h) = zone.lua_music.take() {
|
||||
h.stop(mixer::fade(MUSIC_FADE_OUT_MS));
|
||||
@@ -1300,9 +1300,8 @@ mod tests {
|
||||
/// ahead of the NULL branch — so a stop from **cold** still moves the clock, and pushes out
|
||||
/// a start that was already due.
|
||||
/// * **The zone track is not what it stops.** `[0xb06cc4]` is mentioned only past that
|
||||
/// branch. This is the reporter's own sentence from the outside — *"not used to stop normal
|
||||
/// music played by being in a zone"* — and it is the half a "stop the music" verb would
|
||||
/// most naturally get wrong.
|
||||
/// branch: the zone's own music plays on through a `StopMusic`, which is the half a
|
||||
/// "stop the music" verb would most naturally get wrong.
|
||||
#[test]
|
||||
fn the_lua_music_verb_rearms_the_zone_pump_at_six_seconds_even_from_cold() {
|
||||
// Its own number, and neither of the two it sits between: not the cinematic's 3.000 s,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! **The re-shape a `bevy_ui` text root loses when its LAST span is despawned** — an upstream
|
||||
//! change-detection hole, netted here because falling into it is a hard crash whose panic names
|
||||
//! none of our code (decision 2212; bug B383, Goudy: *"if you enter world in window mode and then
|
||||
//! maximize window on linux, the client will crash"*).
|
||||
//! none of our code (decision 2212). The way in: enter the world in window mode, then maximize the
|
||||
//! window on Linux.
|
||||
//!
|
||||
//! ## The hole
|
||||
//!
|
||||
@@ -291,7 +291,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// **The crash itself.** The same sequence with the net taken out is the client Goudy ran:
|
||||
/// **The crash itself.** The same sequence with the net taken out:
|
||||
/// the resize frame re-lays-out the stale buffer and `bevy_text` indexes `glyph_info[1]` of a
|
||||
/// one-entry list. Pinned as a `should_panic` because it is the whole justification for the
|
||||
/// net — when a bevy release closes the hole this fails, and the net (and this test, and the
|
||||
|
||||
@@ -206,8 +206,7 @@ impl Spells {
|
||||
///
|
||||
/// The world cursor's skin leg then reads `[0xb700e4 + 4×isPlayerTarget]` as a hard precondition
|
||||
/// (`0x482589`): **a corpse flagged `UNIT_FLAG_SKINNABLE` shows no skin cursor at all to a player
|
||||
/// who never learned Skinning.** Without it the ladder offers the knife to everyone, which is
|
||||
/// what the channel reported.
|
||||
/// who never learned Skinning.** Without it the ladder offers the knife to everyone.
|
||||
#[derive(Resource, Default)]
|
||||
pub(crate) struct LearnedAbilities {
|
||||
/// `[0xb700e4]` — our known `SPELL_EFFECT_SKINNING` spell (creature skinning), `None` if we
|
||||
|
||||
@@ -174,7 +174,7 @@ impl Plugin for UiChatPlugin {
|
||||
// call drains the queue. `SMSG_GUILD_EVENT` 0x02 has no such latch: it fires
|
||||
// `GUILD_MOTD` synchronously in its own handler (`0x5e7288`). So the reference paints
|
||||
// the guild line FIRST and the realm's welcome lines after it, inverting the wire
|
||||
// order — and that is what the report asked for ("before server messages").
|
||||
// order.
|
||||
//
|
||||
// Our held `ChatLog` queue IS that latch: the early return above `mem::take` is what
|
||||
// keeps the login burst waiting. This says when it drains. Without these two edges
|
||||
|
||||
@@ -781,8 +781,8 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// A revisit (record cached) opens immediately with no query — the half of B292 the reporter
|
||||
/// saw as "never seen this before": the empty frame is a first-visit-only state.
|
||||
/// A revisit (record cached) opens immediately with no query: the empty frame is a
|
||||
/// first-visit-only state.
|
||||
#[test]
|
||||
fn revisit_opens_immediately_from_the_cache() {
|
||||
let mut state = GossipState::default();
|
||||
|
||||
@@ -1564,13 +1564,13 @@ mod tests {
|
||||
///
|
||||
/// Two things this pins, and both were wrong before it. The bars come off the record at all
|
||||
/// (they used to read the wire-only snapshot and map an absent field to `0`, which is the
|
||||
/// blanked frame Goudy reported); and the power pair is **divided** by the raw→display scale
|
||||
/// blanked frame); and the power pair is **divided** by the raw→display scale
|
||||
/// on this leg exactly as on the live one — a warrior's rage rides the wire ×10, so without
|
||||
/// the divide an out-of-range warrior reads ten times an in-range one.
|
||||
#[test]
|
||||
fn an_out_of_range_member_reads_the_record_and_divides_its_rage() {
|
||||
let m = GroupMemberEntry {
|
||||
name: "Thalyn".into(),
|
||||
name: "Brisca".into(),
|
||||
guid: 0x1234,
|
||||
status: member_status::ONLINE,
|
||||
flags: 0,
|
||||
@@ -1618,7 +1618,7 @@ mod tests {
|
||||
#[test]
|
||||
fn an_out_of_range_members_dead_and_ghost_come_off_the_record() {
|
||||
let m = GroupMemberEntry {
|
||||
name: "Thalyn".into(),
|
||||
name: "Brisca".into(),
|
||||
guid: 0x1234,
|
||||
// The stale roster echo: online, alive.
|
||||
status: member_status::ONLINE,
|
||||
|
||||
@@ -440,7 +440,7 @@ mod tests {
|
||||
&mut quest,
|
||||
0,
|
||||
0,
|
||||
vec![member(leader, "Frostshake"), member(far, "Thalyn")],
|
||||
vec![member(leader, "Aldwyn"), member(far, "Brisca")],
|
||||
leader,
|
||||
None,
|
||||
&names,
|
||||
@@ -462,14 +462,14 @@ mod tests {
|
||||
);
|
||||
|
||||
// The answer lands for one of them; the re-sent roster asks for neither.
|
||||
names.insert_player(leader, "Frostshake".into(), Some((1, 4, 1)));
|
||||
names.insert_player(leader, "Aldwyn".into(), Some((1, 4, 1)));
|
||||
list(
|
||||
&mut group,
|
||||
&mut errors,
|
||||
&mut quest,
|
||||
0,
|
||||
0,
|
||||
vec![member(leader, "Frostshake"), member(far, "Thalyn")],
|
||||
vec![member(leader, "Aldwyn"), member(far, "Brisca")],
|
||||
leader,
|
||||
None,
|
||||
&names,
|
||||
@@ -523,7 +523,7 @@ mod tests {
|
||||
let (tx, rx) = crossbeam_channel::unbounded();
|
||||
let net = NetCommands(tx);
|
||||
let guid = 0x1234;
|
||||
let mut group = grouped(&[member(guid, "Thalyn"), member(0x99, "Frostshake")]);
|
||||
let mut group = grouped(&[member(guid, "Brisca"), member(0x99, "Aldwyn")]);
|
||||
let _ = asked(&rx); // the roster's own seat-time asks (they are unstreamed here)
|
||||
|
||||
// A warrior: rage rides the wire ×10, and the record stores it raw exactly as the
|
||||
@@ -565,7 +565,7 @@ mod tests {
|
||||
fn a_despawn_that_is_not_a_party_member_asks_nothing() {
|
||||
let (tx, rx) = crossbeam_channel::unbounded();
|
||||
let net = NetCommands(tx);
|
||||
let mut group = grouped(&[member(0x1234, "Thalyn")]);
|
||||
let mut group = grouped(&[member(0x1234, "Brisca")]);
|
||||
let _ = asked(&rx);
|
||||
// A live object, so only the roster gate can stop it.
|
||||
let store = ObjectStore(benilla_protocol::messages::ObjectFields::from_pairs(&[(
|
||||
@@ -602,7 +602,7 @@ mod tests {
|
||||
let mut world = bevy::ecs::world::World::new();
|
||||
index.0.insert(near, world.spawn_empty().id());
|
||||
|
||||
let roster = vec![member(near, "Frostshake"), member(far, "Thalyn")];
|
||||
let roster = vec![member(near, "Aldwyn"), member(far, "Brisca")];
|
||||
let mut send = |group: &mut GroupState, members: Vec<GroupMemberEntry>| {
|
||||
list(
|
||||
group,
|
||||
|
||||
@@ -2579,7 +2579,7 @@ mod extract_gate_tests {
|
||||
/// half that can silently rot: the shader's `select(a, k, premultiplied)` is only correct if
|
||||
/// exactly the render-target quads carry the flag. Drop it and every additive effect the pane
|
||||
/// draws over EMPTY space is multiplied by its own zero alpha again — the R14 pauldrons' fire
|
||||
/// gone, a weapon glow chopped at the model's silhouette (Goudy, `#bugs` 2026-07-27).
|
||||
/// gone, a weapon glow chopped at the model's silhouette.
|
||||
#[test]
|
||||
fn a_booth_bake_quad_is_flagged_premultiplied_and_a_plain_one_is_not() {
|
||||
let mut app = app_with_marker();
|
||||
|
||||
@@ -784,8 +784,8 @@ fn selecting_a_row_reads_back_in_the_same_tick() {
|
||||
.contains(&SocialRequest::SelectFriend(2)));
|
||||
}
|
||||
|
||||
/// **B363 — the who list reaches its last rows.** Liho's `/who` found 49, showed 17, and the knob
|
||||
/// travelled while the rows stayed. On the stock window the mechanism is a one-row loss:
|
||||
/// **B363 — the who list reaches its last rows.** The symptom: a `/who` of 49 shows 17, and the
|
||||
/// knob travels while the rows stay. On the stock window the mechanism is a one-row loss:
|
||||
/// `WhoListScrollFrame` is 287 tall (stock `FriendsFrame.xml` l.1661) against seventeen rows of
|
||||
/// sixteen, so the child's overflow past the frame, `n × 16 − 287`, sits fifteen pixels under the
|
||||
/// bar's `(n − 17) × 16`, and an engine that clamped `SetVerticalScroll` into that overflow
|
||||
|
||||
@@ -633,8 +633,8 @@ pub(crate) fn load_ingame_ui_on_world_entry(world: &mut World) {
|
||||
});
|
||||
// **Say it out loud when an addon didn't load** (decision 1495). Every failure the walk found
|
||||
// is retained now, but a log nobody knows to open does not fix silence — and silence is the
|
||||
// actual defect B293 reports: *"there are a lot of addons that still doesn't work"*, with
|
||||
// nothing on screen to say which or why. Counted off the retained log rather than the walk's
|
||||
// actual defect: addons that do not load, with nothing on screen to say which or why. Counted
|
||||
// off the retained log rather than the walk's
|
||||
// `failures` vec so the number matches what `/errors` will show: the log deduplicates, and one
|
||||
// broken addon that fails four files should not read as four broken addons. The VM is fresh
|
||||
// per world entry, so every `Load` row here is this load's.
|
||||
|
||||
@@ -670,7 +670,7 @@ fn the_keybind_pair_pushes_and_casts_without_the_clicks_forks() {
|
||||
|
||||
/// A hunter bar hovered through the REAL gesture, with the real binding registry and the real CVar
|
||||
/// table behind it — `mouse_move` runs the shipped `<OnEnter>` with `this` bound, which is the only
|
||||
/// way this fork gets exercised the way nazriel exercised it.
|
||||
/// way this fork gets exercised the way a player's hover exercises it.
|
||||
///
|
||||
/// Buttons are 30 px chained +8 from the bar's own origin: button 1 spans x[72,102] y[56,86] ⇒
|
||||
/// centre (87,71), button 4's left = 72 + 3·38 ⇒ centre (201,71) — the geometry
|
||||
@@ -701,8 +701,7 @@ fn tooltip_line1(s: &UiScript) -> String {
|
||||
|
||||
/// **B230 — Attack/Follow/Stay name their keybinding; the pet's own spells do not.**
|
||||
///
|
||||
/// nazriel: *"On 1.12 when you hover over Attack, Follow, Stay pet action buttons they show the
|
||||
/// keybinding - which is not the case with Benilla"*. The reference agrees with him and is
|
||||
/// On 1.12, hovering Attack, Follow or Stay shows the button's keybinding in its tooltip. That is
|
||||
/// narrower than "the pet bar": `PetActionButton_OnEnter` (PetActionBarFrame.lua l.285-305) forks
|
||||
/// on `isToken or UberTooltips == "0"`, and only THAT branch concatenates
|
||||
/// `NORMAL_FONT_COLOR_CODE.." ("..GetBindingText(GetBindingKey("BONUSACTIONBUTTON"..id), "KEY_")..")"`.
|
||||
|
||||
@@ -537,11 +537,11 @@ fn clicking_a_bar_opens_the_detail_popup_on_that_faction() {
|
||||
/// gates its hide-old step on the new state having a texture, so the `UI-CheckBox-Up` box stays up
|
||||
/// and the row reads "a greyed **At War** beside an empty box". Ours resolved the shown texture as
|
||||
/// a pure function of the state, hid it, and left a bare grey label with nothing beside it — which
|
||||
/// is what MarcusAga photographed on Ironforge.
|
||||
/// is how the Ironforge row drew.
|
||||
///
|
||||
/// Three rows, because the tick is the half that made the report confusing: a peace-forced faction
|
||||
/// that is NOT at war shows the empty box (the shot), a peace-forced faction that IS at war shows
|
||||
/// the box plus its grey `DisabledCheckedTexture` (*"the tick renders, however"*), and a
|
||||
/// the box plus its grey `DisabledCheckedTexture` (the tick still draws), and a
|
||||
/// toggleable one is the control that must not move.
|
||||
#[test]
|
||||
fn the_at_war_box_keeps_its_art_while_it_is_disabled() {
|
||||
|
||||
@@ -507,8 +507,8 @@ fn the_expand_tab_fits_its_label_at_load() {
|
||||
assert!(s.errors().is_empty(), "errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// **B370 — the list reaches its last rows.** MarcusAga's Skills tab stopped three rows short of
|
||||
/// the end: the knob mid-track, Maces the last row shown. The reference's own
|
||||
/// **B370 — the list reaches its last rows.** The symptom: the Skills tab stops three rows short
|
||||
/// of the end, the knob mid-track, Maces the last row shown. The reference's own
|
||||
/// `FauxScrollFrame_Update` sizes the bar to `(n − 12) × 15` and the scroll child to `n × 15`, and
|
||||
/// `SkillListScrollFrame` is 220 tall (stock `SkillFrame.xml` l.468) against twelve rows of
|
||||
/// fifteen — so the child's overflow past the frame, `n × 15 − 220`, is forty pixels short of
|
||||
|
||||
@@ -1364,7 +1364,7 @@ fn a_feigning_target_paints_empty_bars_and_the_dead_text() {
|
||||
Some(UnitState {
|
||||
exists: true,
|
||||
is_connected: true, // CheckDead's second term — a feign is not a link-drop
|
||||
name: Some("Nazriel".into()),
|
||||
name: Some("Corvane".into()),
|
||||
health,
|
||||
max_health: 1500,
|
||||
level: 60,
|
||||
@@ -1991,7 +1991,7 @@ fn the_player_frame_wears_the_leader_and_master_looter_icons() {
|
||||
"player",
|
||||
Some(UnitState {
|
||||
exists: true,
|
||||
name: Some("Frostshake".into()),
|
||||
name: Some("Aldwyn".into()),
|
||||
health: 100,
|
||||
max_health: 100,
|
||||
level: 60,
|
||||
@@ -2013,7 +2013,7 @@ fn the_player_frame_wears_the_leader_and_master_looter_icons() {
|
||||
|
||||
let party = |leader_index: u32, master_looter: Option<u32>, method: &str| PartyState {
|
||||
members: vec![PartyMemberInfo {
|
||||
name: "Thalyn".into(),
|
||||
name: "Brisca".into(),
|
||||
guid: 0x7A17,
|
||||
}],
|
||||
leader_index,
|
||||
|
||||
@@ -938,8 +938,7 @@ fn the_login_one_shots_wait_for_the_in_game_ui() {
|
||||
/// ship — used to `warn!` to the terminal and vanish. Nothing raised, so 1305's dialog could not
|
||||
/// fire; the walk's failure list was dropped on the floor at `load_ingame_ui_on_world_entry`; and
|
||||
/// the per-frame drain kept no history. From the player's chair the addon simply was not there and
|
||||
/// the client said nothing, which is the literal content of *"there are a lot of addons that still
|
||||
/// doesn't work"*.
|
||||
/// the client said nothing.
|
||||
///
|
||||
/// Three claims, and the third is the one that makes the first two reachable: the failure is
|
||||
/// **retained**, it is **readable from Lua** (so the window is a view of it, not a second copy),
|
||||
@@ -1121,9 +1120,9 @@ fn a_repeating_error_is_one_row_with_a_count_not_a_flood() {
|
||||
|
||||
// ── B353 · the layout cache is a resident of the shutdown tail ───────────────────────────────
|
||||
//
|
||||
// st1rk, 2026-09-01: *"Unlock a chat window (right-click tab → Unlock Window), resize or drag it,
|
||||
// `/logout` or `/reload`, log back in. It's back at the original size. `benilla-config/layout/`
|
||||
// isnt created."* The engine seam and the file round trip were already proven by
|
||||
// The symptom: an unlocked chat window, resized or dragged, comes back at its original size after
|
||||
// a `/reload` or a relog, and `benilla-config/layout/` is never created. The engine seam and the
|
||||
// file round trip were already proven by
|
||||
// [`crate::ui_script::chat_resize_tests::the_geometry_round_trips_through_the_save_file`]; what
|
||||
// was wrong is which edge writes. [`crate::ui_layout`] hung its saver off `OnExit(InWorld)`, and a
|
||||
// `/reload` never leaves `InWorld` — [`super::run_pending_reload`] calls the shutdown and the
|
||||
@@ -1278,7 +1277,7 @@ fn window_geometry(world: &World) -> (f32, f32, String, f32, f32) {
|
||||
.expect("read the probe window back")
|
||||
}
|
||||
|
||||
/// **The whole loop, on the root that reported it** — st1rk's retest, in one test: place a window,
|
||||
/// **The whole loop, on the root that reported it**, in one test: place a window,
|
||||
/// `/reload`, meet a fresh tree that has it on its authored anchors, and let the loader seat the
|
||||
/// saved geometry back over the top.
|
||||
///
|
||||
|
||||
@@ -908,7 +908,7 @@ mod ellipsis_cost {
|
||||
use std::time::Instant;
|
||||
|
||||
/// The reported page verbatim — vmangos `page_text` 2676, the *Alliance Military Ranks* plaque
|
||||
/// in Stormwind's Old Town (`GameObject` 3011, the object in Goudy's screenshots). 647 bytes.
|
||||
/// in Stormwind's Old Town (`GameObject` 3011). 647 bytes.
|
||||
const PAGE: &str = concat!(
|
||||
"<HTML>\n",
|
||||
"<BODY>\n",
|
||||
@@ -934,8 +934,8 @@ mod ellipsis_cost {
|
||||
);
|
||||
|
||||
/// The longest body vmangos actually ships (`page_text` 2880, a Hearthglen letter, 928 bytes)
|
||||
/// — **plain prose, no markup at all**. It is here because the report's framing ("html text")
|
||||
/// names the loudest case, not the boundary: the seam is armed by OVERFLOW, and the longest
|
||||
/// — **plain prose, no markup at all**. It is here because HTML is the loudest case, not
|
||||
/// the boundary: the seam is armed by OVERFLOW, and the longest
|
||||
/// pages in the world are plain. `$b` arrives expanded (`npc_text::substitute`, the feed).
|
||||
const PLAIN: &str = concat!(
|
||||
"Reuben,\n\nI write this letter knowing you may never see it; I simply can't remain idle, ",
|
||||
|
||||
@@ -249,7 +249,7 @@ fn closing_queues_the_close_intent() {
|
||||
assert!(s.take_errors().is_empty());
|
||||
}
|
||||
|
||||
/// **B240's render half, on the reported page.** Goudy's plaque body (`page_text` 2676, the
|
||||
/// **B240's render half, on the reported page.** The plaque body (`page_text` 2676, the
|
||||
/// *Alliance Military Ranks* wall plaque in Stormwind's Old Town) went through the reader and came
|
||||
/// out as its own source — `<HTML><BODY><H1 align="center">…` drawn literally, and cut off with
|
||||
/// "..." partway down. Both were the page being a plain FontString where the reference has a
|
||||
@@ -314,10 +314,9 @@ fn the_reported_html_page_draws_as_blocks_not_as_its_own_markup() {
|
||||
);
|
||||
}
|
||||
|
||||
/// **B342, on the reported page, through the real archives.** Reported on Discord,
|
||||
/// 2026-08-27: *"html images in books are not scaled correctly"* — the Alliance crest
|
||||
/// on *A Treatise on Military Ranks* drawn several times the reference's size with the page's own
|
||||
/// text over it, beside a 1.12.1 shot of the same page for comparison.
|
||||
/// **B342, on the reported page, through the real archives.** The symptom: the Alliance crest
|
||||
/// on *A Treatise on Military Ranks* draws several times the reference's size, with the page's own
|
||||
/// text over it.
|
||||
///
|
||||
/// The body is `page_text` 2654, quoted verbatim below, and its one `<IMG>` carries **no `width=`
|
||||
/// and no `height=`**. In the reference that is the CONTENT-derived span: the resolver's size call
|
||||
@@ -443,7 +442,7 @@ fn the_reported_book_crest_draws_at_the_blps_own_size() {
|
||||
}
|
||||
}
|
||||
|
||||
/// **B288, closed at the reported symptom** (CarlG, decision 1507): the Verdant Note open from
|
||||
/// **B288, closed at the reported symptom** (decision 1507): the Verdant Note open from
|
||||
/// the bag, then a quest giver's gossip — both frames drew at the same TOPLEFT 0,-104 anchor,
|
||||
/// page text and greeting interleaved. The cause was the reader's missing `UIPanelWindows` row:
|
||||
/// registered (the ref's own `{ area = "left", pushable = 0 }`, UIParent.lua l.20), the two are
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//! benilla used to decode every one of them to `Rgba8Unorm` on the CPU and upload that: **8x the
|
||||
//! bytes of DXT1, 4x of DXT3/DXT5**, on the whole resident working set and on every texture fetch
|
||||
//! that misses the cache. On a bandwidth-starved integrated GPU that is the frame's dominant cost,
|
||||
//! and it is the divergence Liho's Steam Deck reading exposed (decision 1626).
|
||||
//! and a Steam Deck is one (decision 1626).
|
||||
//!
|
||||
//! Two things have to be true before the blocks can go straight over, and this module owns both:
|
||||
//!
|
||||
|
||||
@@ -1371,8 +1371,8 @@ mod tests {
|
||||
}
|
||||
|
||||
/// **The billboard host of a particle emitter's bone chain** (decision 0813), on the two real
|
||||
/// assets that opened it. `Field Marshal's Chain Spaulders` (display 32092 — nazriel's B118
|
||||
/// item) authors a 4-bone chain whose only billboard bone is bone **1** (flags `0x08`,
|
||||
/// assets that opened it. `Field Marshal's Chain Spaulders` (display 32092)
|
||||
/// authors a 4-bone chain whose only billboard bone is bone **1** (flags `0x08`,
|
||||
/// spherical), with the two sparkle emitters hanging off its children 2 and 3. Neither emitter
|
||||
/// bone carries the flag, so a consumer that asks only about the emitter's OWN bone sees an
|
||||
/// ordinary rest-pose frame and puts the sparkle ~0.24 yd from where the reference draws it.
|
||||
@@ -1460,8 +1460,8 @@ mod tests {
|
||||
/// (flags `0x08`), **zero** particle emitters and zero ribbons — the whole model is a single
|
||||
/// additive quad that faces the camera. `ItemVisuals` 28 hangs it on slot 4, and 10 of the 29604
|
||||
/// `ItemDisplayInfo` rows carry that visual, so a preview path that drops billboard batches and
|
||||
/// keeps only emitters renders *nothing whatsoever* for those items (the select-screen half of
|
||||
/// `#bugs` B118). Pinned here because the mistake is invisible in code that reads correct.
|
||||
/// keeps only emitters renders *nothing whatsoever* for those items at character select.
|
||||
/// Pinned here because the mistake is invisible in code that reads correct.
|
||||
#[test]
|
||||
fn a_real_item_glow_model_is_pure_billboard_geometry_with_no_emitters() {
|
||||
let data = crate::wow_data_or_skip!();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! The cold-breath puff, pinned against the shipped assets (`#bugs` B233, decision 1149).
|
||||
//! The cold-breath puff, pinned against the shipped assets (decision 1149).
|
||||
//!
|
||||
//! B233 was reported as a **player character in Dun Morogh** with no visible breath, and the fix
|
||||
//! rests on three asset facts that a mechanism note alone cannot establish — the trap decision
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Every book in the world opened and closed constantly (`#bugs` B36) — because the loader-idle
|
||||
//! Every book in the world opened and closed constantly — because the loader-idle
|
||||
//! seed armed the model's **file-order-first** sequence, and on a book that sequence is the *Close
|
||||
//! motion*, not the closed pose.
|
||||
//!
|
||||
|
||||
@@ -5,11 +5,8 @@
|
||||
//! 1305 gave script errors a *screen*: the engine dispatches every caught error to
|
||||
//! `geterrorhandler()`, and FrameXML's `_ERRORMESSAGE` puts the red `ScriptErrors` dialog up. That
|
||||
//! is the reference's behaviour and it stays exactly as it is. It is also, on its own, not enough,
|
||||
//! and the reporters said so within a day of it shipping — Goudy, *minutes after screenshotting
|
||||
//! that very dialog working*: *"some kind of lua error frame probably needs to be implemented, as
|
||||
//! there are a lot of addons that still doesn't work"*; nazriel_0, two days later: *"probably we
|
||||
//! should start using something like Bug Grabber to report those stack traces better"* (ledger
|
||||
//! B293, split from B271).
|
||||
//! and it leaves two asks: a Lua error frame that says which addons still fail, and a
|
||||
//! BugGrabber-style collector that keeps their stack traces for a bug report.
|
||||
//!
|
||||
//! Both asks are the same three gaps, and all three are about *memory*:
|
||||
//!
|
||||
@@ -19,8 +16,7 @@
|
||||
//! 2. **An addon can fail to load without ever raising**, and those failures had no channel at all
|
||||
//! — a manifest entry the package does not ship, a document that will not parse, a dependency
|
||||
//! cycle, a missing hard dependency, a broken `Bindings.xml`. They logged to the terminal and
|
||||
//! stopped; the addon simply was not there and the client said nothing. That is the literal
|
||||
//! content of *"a lot of addons that still doesn't work"*.
|
||||
//! stopped; the addon simply was not there and the client said nothing.
|
||||
//! 3. **Nothing retained anything.** [`super::UiScript::take_errors`] drains to the host log every
|
||||
//! frame, so once a frame passed, the terminal held the only copy. A player cannot read a
|
||||
//! terminal, and a bug report written from one is what B271 was debugged off.
|
||||
|
||||
@@ -302,7 +302,7 @@ fn a_region_resolves_even_when_its_owner_frame_has_no_rect() {
|
||||
/// a wrong position — and a template whose textures chain off each other turns it into real,
|
||||
/// visible geometry a few links down.
|
||||
///
|
||||
/// Reported as B264 (carni, 2026-08-13): opening the social pane drew a stray dropdown capsule at
|
||||
/// The symptom: opening the social pane draws a stray dropdown capsule at
|
||||
/// the bottom of the screen next to the action bar. `FriendsDropDown` carries no anchors —
|
||||
/// *exactly* as the reference's own `FriendsDropDown` does (`FriendsFrame.xml` l.598), and the
|
||||
/// reference draws nothing — so every texture of `UIDropDownMenuTemplate` hung off a phantom rect
|
||||
|
||||
@@ -416,7 +416,7 @@ fn the_page_text_body_renders_the_block_list_a_reader_would_draw() {
|
||||
|
||||
/// **The exact string the reader builds**, on the exact body B240 reports — `ItemTextFrame.xml`'s
|
||||
/// READY handler pads an authorless page as `"\n" .. ItemTextGetText() .. "\n"`, and that padding
|
||||
/// is the one thing standing between a formatted page and the raw markup Goudy photographed.
|
||||
/// is the one thing standing between a formatted page and its raw markup.
|
||||
/// Whitespace before and after the root element is legal XML, so this must take the MARKUP path;
|
||||
/// if it ever takes the fallback, every book in the world silently renders as its own source.
|
||||
///
|
||||
@@ -542,9 +542,8 @@ fn an_unfloated_image_reserves_height_without_becoming_the_anchor() {
|
||||
);
|
||||
}
|
||||
|
||||
/// **B342, at the engine.** Goudy, 2026-08-27 (`#bugs` `1542371921486811236`): *"html images in
|
||||
/// books are not scaled correctly"* — the Alliance crest on *A Treatise on Military Ranks* drawn
|
||||
/// several times the reference's size, with the page's text over the top of it.
|
||||
/// **B342, at the engine.** The symptom: the Alliance crest on *A Treatise on Military Ranks*
|
||||
/// draws several times the reference's size, with the page's text over the top of it.
|
||||
///
|
||||
/// The body is `page_text` 2654 and its `<IMG>` carries **no `width=` and no `height=`**, so the
|
||||
/// block is sized `0 × 0`. In the reference that is not "no size": the resolver's size call is
|
||||
|
||||
@@ -530,7 +530,7 @@ fn the_duration_ladder_ceils_every_arm_but_seconds() {
|
||||
/// Live, that is the item tooltip's SET block: `ClearLines` keeps the cache (the hover re-enter
|
||||
/// loop depends on it), so the two blank gold spacers land on cells that carried real text on an
|
||||
/// earlier hover. Each drew an uncounted row and the set bonuses hung below the backdrop —
|
||||
/// Frostshake's Field Marshal's Raiment shot, two blanks, two rows.
|
||||
/// on Field Marshal's Raiment, two blanks, two rows.
|
||||
#[test]
|
||||
fn an_emptied_pooled_line_drops_its_stale_box_and_the_plate_still_contains_the_chain() {
|
||||
let mut s = script();
|
||||
|
||||
@@ -746,8 +746,8 @@ fn instance_tail_creator_and_readable() {
|
||||
}
|
||||
|
||||
/// **The bind line's Soulbound override** — the bind line reads the INSTANCE, not only the template
|
||||
/// (B310, Frostshake: an equipped Maiden's Circle still said *Binds when equipped*, and B309's
|
||||
/// own shot showed *Binds when picked up* on the equipped pants).
|
||||
/// (an equipped Maiden's Circle reads *Soulbound*, not *Binds when equipped*; equipped pants
|
||||
/// likewise, not *Binds when picked up*).
|
||||
///
|
||||
/// The reference's rule: Bonding `[record+0x194]` ∈ {1..5}
|
||||
/// decides whether a line prints at all; a **runtime-bound instance** (`0x5da2c0` — soulbound
|
||||
|
||||
@@ -289,10 +289,9 @@ fn player_buff_hover_is_the_aura_variant() {
|
||||
});
|
||||
assert!(gold_name, "aura name is gold, not white");
|
||||
// …and so is the DURATION-REMAINING line — the same `0xffffd200`, not the description's
|
||||
// white. Report B62: measured on the reporter's own 1.12.1 reference shot
|
||||
// (`media/1530672450247856148`), the "29 minutes remaining" glyphs are exactly
|
||||
// white. On a 1.12.1 reference shot the "29 minutes remaining" glyphs are exactly
|
||||
// `(255, 210, 0)`, the same pixels as that shot's title row, while its description rows are
|
||||
// `(255, 255, 255)`. Ours drew the line white, which is the whole report.
|
||||
// `(255, 255, 255)`.
|
||||
let gold_remaining = quads.iter().any(|q| {
|
||||
matches!(&q.content, QuadContent::Text { text: Some(t), color: Some(c), .. }
|
||||
if t == "<2 mm>" && *c == [1.0, 210.0 / 255.0, 0.0, 1.0])
|
||||
|
||||
@@ -260,7 +260,7 @@ pub(super) fn render_view(
|
||||
// pick 1→picked up · 2→equipped · 3→used · 4/5→Quest Item.
|
||||
//
|
||||
// Before this arm an equipped Binds-when-equipped piece kept saying *Binds when equipped*
|
||||
// forever (B310, Frostshake): the template's `bonding` never changes when the item binds —
|
||||
// forever: the template's `bonding` never changes when the item binds —
|
||||
// the instance's flag is the only thing that does.
|
||||
match v.bonding {
|
||||
4 | 5 => keyed("ITEM_BIND_QUEST", &[], WHITE, false)?,
|
||||
|
||||
@@ -277,11 +277,10 @@ fn render_spell(
|
||||
}
|
||||
}
|
||||
// The duration-remaining line (`SetPlayerBuff` only) is GOLD `0xffffd200` — the same gold as
|
||||
// the aura title it sits under, NOT the description's white. Measured off the reporter's own
|
||||
// 1.12.1 reference shot for B62 (`media/1530672450247856148`, Ice Armor in Wetlands): the
|
||||
// the aura title it sits under, NOT the description's white. Measured off a 1.12.1 reference
|
||||
// shot of Ice Armor in Wetlands: the
|
||||
// "29 minutes remaining" glyphs read exactly `(255, 210, 0)`, pixel-identical to that shot's
|
||||
// "Ice Armor" / "Magic" title row, while its description rows read `(255, 255, 255)`. Ours
|
||||
// rendered it white (`media/1530672304877469696`, `(255, 255, 255)`) — that white *is* B62.
|
||||
// "Ice Armor" / "Magic" title row, while its description rows read `(255, 255, 255)`.
|
||||
if let Some(rem) = remaining {
|
||||
append_line(lua, this, (rem, GOLD), None, false)?;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ impl Plugin for AssetPlugin {
|
||||
|
||||
/// Drop the world-art dedup on a cross-map transition (`world_map::MapChange` — see its doc for
|
||||
/// why a clear is always safe): `textures` + `model_materials` pin every map's world art forever
|
||||
/// otherwise (the #bugs teleport leak). The UI sprite caches (`sprites`/`tiled_sprites`/
|
||||
/// otherwise (the teleport leak). The UI sprite caches (`sprites`/`tiled_sprites`/
|
||||
/// `portraits`/`masks`) stay — they are game-global UI scope, and their negative entries exist
|
||||
/// precisely to stop per-frame re-walks of the chain.
|
||||
fn evict_world_art(
|
||||
|
||||
@@ -923,7 +923,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// **The billboard FRAME an equipped item's emitter rides** (decision 0813), with the real
|
||||
/// numbers of nazriel's B118 item (`LShoulder_Mail_PVPAlliance_C_01`: billboard bone 1 pivot
|
||||
/// numbers of the R14 PVP shoulder (`LShoulder_Mail_PVPAlliance_C_01`: billboard bone 1 pivot
|
||||
/// `(-0.012, 0.162, -0.060)`, sparkle emitter position `(-0.252, 0.178, -0.046)`, both raw WoW
|
||||
/// model space — pinned in `benilla_formats`' `real_pvp_shoulder_emitters_ride_a_billboard_bone`).
|
||||
///
|
||||
|
||||
@@ -1288,7 +1288,7 @@ fn register_wmo(placements: &mut Placements, asset_server: &AssetServer, w: &Wmo
|
||||
/// One body, two triggers, because they are the same event seen from different sides: a cross-map
|
||||
/// teleport ends the world you were in, and so does leaving for character select. The material
|
||||
/// dedup goes with the placements it deduped for — its strong handles are what kept every
|
||||
/// previous map's materials (and their textures) resident forever (the #bugs teleport leak), so it
|
||||
/// previous map's materials (and their textures) resident forever (the teleport leak), so it
|
||||
/// is cleared *here*, sharing the exact trigger of the teardown it belongs to, rather than hanging
|
||||
/// off `world_map::MapChange`.
|
||||
fn drop_streamed_world(
|
||||
|
||||
@@ -501,14 +501,14 @@ mod tests {
|
||||
}
|
||||
|
||||
/// B129 end to end, on the shipped bytes and through the real cast: a body walking east from
|
||||
/// Goudy's pin (`.go xyz -6601.98 -531.87 335.60 0`) is stopped where 1.12.1 stops it, at the
|
||||
/// the pin (`.go xyz -6601.98 -531.87 335.60 0`) is stopped where 1.12.1 stops it, at the
|
||||
/// chunk boundary 1.46 yd away — and the SAME world built without the walls carries it straight
|
||||
/// through, which is the report. Both halves matter: the second is the symptom reproduced, the
|
||||
/// first is it gone, and a wall built at the wrong offset would satisfy neither. Skips without
|
||||
/// client data.
|
||||
#[test]
|
||||
fn a_body_walking_east_from_the_b129_pin_is_stopped_at_the_wall() {
|
||||
/// Goudy's pin, and the MCNK boundary the flagged chunk starts at (WoW y; east is −y).
|
||||
/// The reported pin, and the MCNK boundary the flagged chunk starts at (WoW y; east is −y).
|
||||
const PIN: [f32; 3] = [-6601.98, -531.87, 335.60];
|
||||
const WALL_Y: f32 = 32.0 * benilla_formats::TILE_SIZE - 528.0 * CHUNK_SIZE;
|
||||
const R: f32 = 0.5;
|
||||
|
||||
@@ -42,7 +42,7 @@ pub struct CurrentMap(pub u32);
|
||||
|
||||
/// A cross-map transition, observed as a [`CurrentMap`] flip — the **world-scope teardown
|
||||
/// signal**. Every module that dedups map-scoped assets behind a strong-handle cache reads this
|
||||
/// and clears its own cache (the #bugs teleport leak: those caches pinned every map ever visited,
|
||||
/// and clears its own cache (the teleport leak: those caches pinned every map ever visited,
|
||||
/// and each retained uv/tint-animated material re-uploaded per frame, forever). A clear is always
|
||||
/// safe mid-session — live users hold handle clones, so it only drops the *dedup*; the assets die
|
||||
/// when their last user despawns, and the next spawn rebuilds under the loading screen. Carries
|
||||
|
||||
Reference in New Issue
Block a user