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:
Sam Sung
2026-09-24 14:29:56 +07:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 7b66dac9db
commit 1ee056fc53
56 changed files with 135 additions and 150 deletions
+1 -1
View File
@@ -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}");
}
}
+4 -5
View File
@@ -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]
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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.
+3 -3
View File
@@ -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);
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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>>,
+18 -19
View File
@@ -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
+2 -3
View File
@@ -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.
//!
+1 -1
View File
@@ -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>,
+4 -5
View File
@@ -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,
+3 -3
View File
@@ -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
+1 -2
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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();
+3 -3
View File
@@ -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,
+6 -6
View File
@@ -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.
///
+3 -3
View File
@@ -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, ",
+5 -6
View File
@@ -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
+1 -1
View File
@@ -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:
//!
+4 -4
View File
@@ -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.
//!
+3 -7
View File
@@ -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)?;
}
+1 -1
View File
@@ -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(
+1 -1
View File
@@ -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`).
///
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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