Both were named in docs/GRAPHICS.md as nearly free and neither had been built.
They are unrelated to each other; they land together because each is small.
## Sharpening (`sharpening`, 0 Off / 1 Low / 2 Medium / 3 High)
RCAS, FSR 1.0's sharpening half taken on its own. It does not resample, so it
needs none of the upscaler's motion vectors, jitter or history — which is the
whole reason it can land while §7 cannot. One 5-tap cross; the lobe is not a
constant but the widest one that cannot push the result outside the ring's own
range, solved per channel against a peak of 1.0 and taken as the most
conservative of the three. Flat regions get nothing and nothing rings.
The seat is most of the design. `Core3d`, after `FfxGlowLabel` and before
`Node3d::Bloom`:
- After the glow, not before. `FfxGlowNode` builds the blur chain the glow and
the underwater haze read, and a sharpened bloom source is how an unsharp mask
turns a lit window into a ringed halo.
- The world view only. The world's last reader is the FFXGlow combine drawn
inside the UI camera's main pass, which binds `main_texture_view()` at
node-run time per frame, precisely so a `post_process_write` upstream lands.
The interface is drawn afterwards into a different target — and sharpening
text is the one thing this must never do.
- Every tap saturates, as the combine's own scene read does one pass later: the
limiter is solved against a peak of 1.0, so a super-white tap would invert
the bright side's sign. Nothing is lost, the blur chain already took its copy.
- No denoise term. The deband dither it would exist to suppress is added
downstream, in the combine's exit.
Measured, 600 frames: Redridge overlook 2.95 -> 3.02 (Medium) -> 3.01 (High);
Goldshire inn 3.46 -> 3.51 -> 3.29. Inside the noise, the inn reading *faster*
at High being how you know. CPU flat. What it buys: 3.8% / 19.9% / 48.6% of the
frame moved by more than 2/255.
**The first run hit the trap this repo already has a note about.**
`max(4.0 * mx, 1e-5)` where `mx` is a `vec3` is `error: inconsistent type
passed as argument #2 to max`. naga refused the shader, the pass drew nothing,
and the capture came out looking perfectly normal. One `grep -ciE 'error:'` on
the capture log answered it before a single image was diffed.
## Field of view (`fieldOfView`, vertical degrees, 40..70, default 45)
Field of View on the Graphics page under Render Scale, live as you drag.
It is the BASE lens, not a fourth factor. `player::scoped_view` already
multiplied two things in and neither is a field of view: SuperWoW's `FoV` is
the reference's `[camera+0x40]` as a ratio, the spyglass is a zoom off normal.
Both answer "how much wider than normal"; nothing a player could move answered
what normal IS, and the answer was a `const` in `benilla-world`. So the term
order is `base × FoV × zoom`, and an addon writing `FoV` still means the same
fraction of the lens the player chose.
Vertical degrees, not horizontal, which keeps the reference's Hor+ framing: a
fixed vertical lens widens horizontally as the window does, so an ultrawide
player gains picture at the sides instead of losing it off the top.
The default is `CAM_FOVY` to the bit — `45f32.to_radians()` and `FRAC_PI_4` are
both `0x3f490fdb` — which the defaults-mirror test now asserts on `to_bits()`
rather than against a tolerance. An ulp of projection is a pixel in a capture
diff, and a row that reframes the world for a player who never opened Options
is not a row, it is a regression.
## Both lanes off: byte-identical
Against a `git stash`ed build of the same tree: `overlook-noon` and
`water-lake` identical, and `fieldOfView=45` written explicitly identical to
the row never touched (40 and 65 move 97.6% and 99.3% of the frame).
`inn-noon` is excluded WITH PROOF rather than quietly: two runs of the *same*
binary differ on 37.8% of their pixels by up to 199, because that scene's flame
emitters do not reproduce. Its own before/after delta is 0.00% of pixels at a
maximum of 1. A scenario that cannot reproduce itself cannot be a control, and
the number to check first is always the baseline against itself.
## Seats
Six Options edits each, per the recipe, including the silent one
(`OPTIONS_PAGE_ROWS`). Sharpening is a dropdown on Advanced Graphics with the
other screen effects and reuses the existing `qOff/qLow/qMedium/qHigh` labels;
Field of View is a `raw`-format slider on Graphics, whose label is literal
because that page is not localised. Preset ladder: `sharpening` takes
`ambientOcclusion`'s shape (`0 0 1 2 2`) because a look change starts at Medium
in this file's convention; `fieldOfView` is on no rung at all, because it
changes how the game feels to play and that is the player's call.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
A jump on a descending lift (Undercity, Gnomeregan) played the hard-landing grunt and dust. While airborne, benilla carried the rider with the deck but kept the jump's start height in world coordinates. So the lift's drop during the arc, about 24 yd on Undercity's 29 yd/s cars, counted as a fall, and FALLINGFAR latched early.
1.12.1 measures the fall height (`0x7c60c0`) in the mover's own frame: transport-local while aboard, re-mapped when the mover boards or leaves (`0x7c6620`). `Player::carry_to` now moves the start height with the deck. A remote mover's arc is measured in the deck's frame when it takes off and lands on the same transport.
Tests: a 60 fps jump on a deck dropping 28.95 yd/s lands with no fall height and no FALLINGFAR, for us (23.64 yd before) and through packets for a remote (23.88 yd before).
Fixes#1250🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 560d7ff63e8394ca08c97a0867c852155f3e6b52)
# Conflicts:
# crates/benilla-app/src/spell/range_units.rs
# docs/DEVIATIONS.md
Absorbed into the octo fork. spell/range_units.rs is upstream-only (added after our base) and its change here was a test fixture's fall_start_z -> fall_start rename, so the file is dropped rather than orphaned; the rename's real call sites applied unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
`cameraYawSmoothSpeed` was clamped to 90-270 when applied, the Auto-Follow Speed slider's range, so `/console cameraYawSmoothSpeed 360` followed at 270. In 1.12.1 its validator `0x50c000` refuses only outside [0.1, 360] (`0x50b330`, `Value out of range` at `0x50b36c`) and the follow reader takes the stored rate as is (`[0xbe1070]` at `0x512d75`).
The camera now uses the stored value; the registry's existing check, which already matched `0x50b330` and also runs on the config load, does the refusing. `FOLLOW_SPEED_RANGE` is gone.
Test: 270, 360, 0.2 and 30 are stored and applied as given; 360.5, 400, 9000, 0, 0.05 and -30 are vetoed and keep 30. It fails with the clamp put back.
Fixes#323🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9e1b3e9ae9c7fd488ea0addd3f11460678cd61fc)
# Conflicts:
# crates/benilla-app/src/cvars/mod.rs
# crates/benilla-app/src/cvars/table.rs
Absorbed into the octo fork. Ported by hand: our cvars registry is one file, not upstream's cvars/ split, and it has no .checked() validators or SetOutcome::Vetoed, so the clamp cannot simply be removed — without a veto channel that would turn a clamp into an unvalidated accept. FOLLOW_SPEED_RANGE is widened from the slider's 90..=270 to the reference validator's 0.1..=360 instead, so /console cameraYawSmoothSpeed 360 now follows at 360; refusing (rather than clamping) outside that window is recorded as a deviation on the constant.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
The DLL publishes its own **usage strings**, in one block beside the
names it registers, and reading them turned up four things this file had
wrong — three of them bugs rather than gaps.
`CastSpellByName(name [, "unit"])`. A unit ID, which is what every real
call site passes and what `Model::unit` already resolves. It used to fall
through to the ordinary queue, which casts at the player's OWN selection
— a silent wrong target at six sites in grom's corpus: pfUI's /castfocus
(a GUID) and /castmouseover (a token), Decursive's cure-at-guid, BigWigs'
spell requests and two encounter scripts. Each asked for a unit and got
whatever was targeted. The stock `onSelf` meaning keeps its behaviour:
only a string that resolves to a live unit takes the new path.
`CombatLogAdd`'s second argument names a FILE, not the event. There is a
third log literal in the same pool — `Logs\WoWRawCombatLog.txt` — and it
is the GUID-form log, the same lines RAW_COMBATLOG carries as arg2.
`ui_chat/logging.rs` keeps it beside the ordinary one now, opened by the
same `LoggingCombat`, which also makes the arg2 composition worth paying
for when nothing is registered but the log is on (`wants_raw_form`). My
first reading, "also fire the event", was an inference from the name
alone, and the second literal settles it.
`FoV`'s hold-back is resolved, and the answer was already in the tree.
The row was held because 1.57 looked irreconcilable with CAM_FOVY's
0.785. It is the same number: 1.57 is π/2, the reference's own
`[camera+0x40]` default of 90° in radians, and that field is not a
vertical FOV at all — `scoped_view.rs` already divides by it as a
DENOMINATOR, because what it expresses is a ratio. So upstream's own
default is this client's existing lens by construction, which is what the
test asserts; the DLL even registers the literal `1.57079`. Live, where
upstream's panel says "Requires UI Reload".
`NameplateMotion` asked for an investigation that was also already
answered: benilla spreads plates with the reference's own SmartScreenRect
solver. 0 (Overlap) bypasses it, 1 (Default spread) is it; 2 and 3 are
the mod's own algorithms and fall back to 1, a documented silent-equal
rather than a refused row.
Also: `GetTradeSkillItemLink(index[, spellLink])`, the only way to link a
recipe that makes no item — an enchant, whose product_item is 0 and which
answers nothing at all on the stock surface. And four stored-only rows
(SelectionCircleStyle, UncapSounds, HealingText, InterfaceVolume), each
with its own reason, so SuperAPI's options panel round-trips.
Three ghosts retired: outline/shadow text, force-show-auras and XP text
are in no part of the DLL's string pool, nor is NameplateVerticalSpacing.
They came from the early wiki reading, not from the binary.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
The first two rows of the reference's camera table `0x84f7a0` (`0x50b400`, `0x50b450`), and the one
verb the chain's own barbershop, arena and guild-bank windows call that we did not publish —
`Turtle_Barbershop.lua:211` zooms the world camera out two yards as its preview opens.
Read off the binary rather than guessed, because the argument handling is not the one every sibling
in this file uses: both thunks test is-number (`0x6f34d0`), take `tonumber` (`0x6f3620`) when it
holds, and otherwise store `1.0f` outright (`0x50b42d`, `0x50b47d`). So `CameraZoomOut()` steps a
yard out, where `SetView()` and `FlipCameraYaw()` return silently. A test pins that, since it is
exactly what reading the callers would have got wrong.
Deviation, named in the module: the reference turns the yards into a timed glide — divide by the
rate at `[0xbe0fbc]+0x24`, scale, truncate (`0x40a2b0`) into a step count — and a call during a
running glide adds into its pending-zoom field (`0x50fca9`). Our rig holds one target distance and
glides to it every frame, so a second call re-aims the same target instead of queueing behind the
first. The clamp is the rig's own, the one the wheel and the zoom keys already share.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This checkout had no git history at all, so this one commit carries the whole
of the Everwood work as a single import on top of chairmanpow/benilla-octo's
`main` (706cd71). The two trees have genuinely diverged — each has files the
other does not — so this is a fork point, not a catch-up.
Today's work, which is the reason a baseline exists at all:
- Booth previews no longer cast world shadows (`NO_BOOTH_SHADOW`). Portrait
characters were entering the sun's shadow cascades, so every skinned-rig
shadow specialization compiled live mid-frame; the hitch detector logged
"main thread blocked 1316 ms" beside a `compiled LIVE` warning. This was
the Fashion/Tmog stutter.
- Per-pane dress-up: each `DressUpModel` pane assembles and stages its own
tried-on look instead of every pane mirroring the live player.
- Item template cache is append-only JSONL written on the IO pool, replacing
an 11.6 MB whole-map JSON rewrite on the main thread every 10 s.
- `WOW_SYSTEM_TIMING` rebuilt per-thread: it took 2 mutexes per system enter
and 4 per exit, costing 15 ms a frame (590 fps -> 60) and corrupting the
measurements it was taken to produce.
- The visibility-churn sweep moved to its own `WOW_FLASH_CHURN` switch, so a
frame-cost reading no longer pays 2.7 ms a frame for it.
Local state (`benilla-config/`, `logs/`, `target/`) is excluded by the
existing .gitignore; the account credential it holds is not in this commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
Same fork policy: upstream structure wins, local-unique re-instated.
Cargo.toml unions both measured dev-profile philosophies (codegen-units=1 +
lto=off from samwhosung, debug=line-tables-only + split-debuginfo from the
fork's disk measurements).
glue_booth.rs takes samwhosung's typed GlueScene (token/of_race/from_model_path)
and re-adds the fork's TurtleWoW races (Goblin=9, BloodElf=10) as variants;
their glue fog stays on Human's row, exactly as the fork's scene_fog fallback did.
ui script takes samwhosung's mouseover/hyperlink rework (world_mouseover,
minimap_blip, hyperlink.rs) over the fork's inline hover_link/span_at/pointer
path — the rework covers the same OnHyperlinkEnter/Leave behaviour.
game_plugins.rs keeps the fork's re-measured ceilings (466 / 5,509); mail.rs
keeps the explicit 13-tuple with a float daysLeft; MAP.md takes the superset.
Fork policy (docs/METHOD.md): upstream structure wins; local-unique work
re-applied on top. This commit is the upstream half of the reconciliation.
Upstream is the same program at a rewritten history (884dff77 == 4e0e89cb
tree), so the merge base was replaced to keep a real three-way diff. The
large conflicts are upstream moving first: the UI manifest (benilla.toc ->
the binary LAYER_MANIFEST/layer.toc split), the CVar table (cvars.rs ->
cvars/{mod,table}.rs, +24 video rows), the editbox flush model, the
host-side GetTime, the Options* -> BenillaOptions* rename, expanded
ui_unit.rs, and a rebuilt volumetric_light.rs.
Local-unique pieces re-instated on the upstream structure:
- OptionsFrame.xml: the 1.12 OptionsFrameSliders table restored under the
Benilla rename, with the TurtleWoW deviation note (patch-4 replaces
OptionsFrame.lua).
- layer.toc: CharacterFrameAdapters.xml (the sixth TurtleWoW character tab).
- ui script: wipe/sizeof, BenillaGetMerchantItemStats, and the attach/mount
teardown re-walk all survive.
- game_plugins.rs keeps UPDATE_ACTIONABLE_CEILING=5_509 (re-measure pending).
The LFT/Turtle_TransmogUI rows benilla.toc named off the player's install
now come from the chain's own FrameXML.toc via the new core loader.
1.12.1's MSG_MOVE_TELEPORT_ACK handler (0x603f90) reaches none of the
loading screen's raises: 0x406800 is entered only from SMSG_TRANSFER_PENDING
(0x401900), the glue EnterWorld (0x46b6f1) and 0x407de0. benilla raised the
cover on a same-map snap twice over: the "focus not resident" backstop on the
snap frame, and a >=100 yd relocation raise. The relocation raise goes, and
the backstop stays off while a snap's settle hold is up; the body is held over
the destination as before.
Verified live against the local realm (map 0, Gmagent): .tele ironforge from
Northshire (~3960 yd) and back covered 50 and 93 frames (0-916 ms, 0-1680 ms)
before, 0 and 0 after; cross-map .tele valley and back still raise the cover
("worldport"). New tests in loading_screen.rs fail on the old code at the snap
frame. benilla-app and benilla-world lib tests green, install-reading tests
green with BENILLA_REQUIRE_DATA=1 (the addon corpus is absent on this
machine). Built on macOS (arm64).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A title-region press refused while another drag held the slot, `SetLayoutMode` stored nothing, and a press on a chat link made the chat frame itself the target, so every link click started a title move of the window.
In 1.12.1 the drag entry takes the slot unread (`0x7652b0`); a mid-drag press goes to the frozen hover (`0x76637f`). With `SetLayoutMode` (`0x488540`) on, a Ctrl or Alt press drags the nearest movable toplevel (`0x765490`) from a 3×3 grip grid (`0x765320`) and is consumed, world included; its release only ends the drag (`0x7664e0`). Each link span is its own Button (`0x7a3240`) routing `OnHyperlinkEnter`/`Leave`/`Click` to the parent (`0x7a3780`–`0x7a37e0`).
These now hold; a release goes to the capture whatever its button (`0x76642b`), and number arguments keep `_ftol`'s low dword.
Tests bite on the parent for each.
Fixes#951Fixes#952Fixes#957🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Leaving a battleground left the character unable to walk for the rest of the session. The
`settle released` line named it: `WASD held by: control-lost`.
vmangos takes control for the teleport (`SetClientControl(player, 0)`), so `Revoked` sets
`control_lost` and parks the mover, and only a later `SMSG_CLIENT_CONTROL_UPDATE` with
`allow_move = true` clears it. Nothing guarantees that packet, and nothing else clears the flag:
the worldport arm replaces the pose and the settle releases, neither asks, and `seizing` at
`wire_in.rs` wants `reseat`, which a worldport never sets. So the lock on the body we just left
parked the body we came back as.
A transfer is a mover change, so re-claim at the transfer's completion, with the ack, the way
login does — the same teardown as the reference's `SetActiveMover` reset. `reseat` rides along so
the existing seize path resets the outgoing mover's state. A possession is left alone: there the
lock is the point.
Compile-verified only — the live run is the test.
Coming out of a worldport unable to walk was a story, because nothing in the log said whether
the hold was the settle, a lost mover or a pending reseat. `movement_suppressors` named six
states and was only ever printed by the logout smoke test; `settling` was missing from it
although the settle hold is a mover hold (`mover.rs`), and a cross-map worldport is exactly
where it fires.
One line per settle, on the frame it ends, naming the hold or `none` — so the next transfer
reports its own cause. Instrument only; no gate reads either list.
With Click to Move on, benilla walked only to things you interact with. In 1.12.1 a right-click on the ground walks to the point (`0x5e0378` → `0x6102b0`, 0.5 yd short, speed-scaled, no leash, held in a deck's frame). A right-click on the sky with an empty cursor walks along the ray (`0x492dca` → `0x610100`), pitching while swimming. An attack on an enemy out of melee walks to where it stood (`0x60c22b` → `0x60fcc0`, mode 10): the stop is about 1.9 yd because the arm names the player's own guid, the radii are unscaled, and the 80 yd leash says ERR_AUTOFOLLOW_TOO_FAR. Inside melee the player turns to face it.
Deviations, each written where it lives: the melee point keeps the enemy's height, does not snap to the reference's fixed arrival heading, and rides the deck it was armed on.
Tests: the router, the steering, the gates, the leash and the deck frames.
Fixes#758
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Mathih13 <15229325+Mathih13@users.noreply.github.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
- Click to Move: with AutoInteract on, a right-click on an NPC, a body to loot or skin, or a GameObject out of reach walks there and runs the verb on arrival
- Click to Move: the arrival scales by the mover's current speed, and a Use still out of range walks again
- Click to Move: a Loot walk stops at the square root of the melee reach
- Put the meeting-stone test's doc comment back on its test
- A GameObject click out of range raises ERR_USE_TOO_FAR unless the walk starts
- Wrap the finish's doc lines at the column limit
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
A warrior chasing a running mob was refused Hamstring at 6-7 yd with "Out of range.", where 1.12.1 lets it go out.
After both arms, `GetMinMaxRange 0x6e3480` adds 2.6667 yd (`[0x811914]`) to the max when all of these hold (`0x6e3648`-`0x6e36a2`):
- the caster and the target unit both move, with a bit of `0x200d` set;
- both run faster than twice their walk speed (`0x5fc350`);
- the row is melee, or the target is a player.
On the melee row, the auto-attack target stands in when no unit is passed.
`min_max_range` now takes each unit's motion and adds the bonus. Every caller passes what its reference call site passes. The ambiguity ceiling rises by the 11 pairs this adds, with the reason beside it.
Test: Hamstring goes out at 7.5 yd with both units running and is refused standing still.
Fixes#678
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The core now loads the chain's `Interface\FrameXML\Bindings.xml` after the `FrameXML.toc` rows, as the reference's UI load does. The load checks the file at `0x48fff9` and loads it through the addons' loader `0x4b6f70` at `0x490018`. The default keys come from the chain's `WTF\DefaultBindings.wtf` (`0x4b62b0`). Every command now runs the stock file's own body, and the registry compiled into benilla, with its 91 retyped bodies, is deleted.
- **The loader's rules (`0x4b6f70`).** It reads the root's children only and skips empty, `debug="true"` and non-Windows rows. A second definition of a name is skipped, and duplicate headers are refused.
- **Keys and commands are separate tables, as in 1.12.** `SetBinding` never reads the command (`0x4b7490`). `RunBinding` runs the body in the VM. `RunCommand` sets `keystate` around the body and clears it after (`0x4b7c42`).
- **Engine behaviour sits behind the Lua globals the bodies call.** These are the movement, turn and mouse-steer bits. Focus loss clears the direction bits and keeps the mouse pair (`0x514490`). One key's stop clears its bit, as `0x514b70` does, so releasing W while UP is held stops the character.
- **Behaviour changes that follow the stock bodies.**
- The nameplate keys (V, Shift-V, Ctrl-V) now run the stock bodies.
- ALT-Z runs `CloseAllWindows(); UIParent:Hide()`.
- The minimap, sound, stance and bonus-bar keys call their stock functions.
- The wheel zooms one `CameraZoomIn(1.0)` per notch.
- **Platform.** benilla answers as the PC build, so a `platform="mac"` row does not register on any OS.
Verified:
- New tests: a fixture `Bindings.xml` (reordered, edited, and with debug, empty, nested and Mac rows) drives the command list, defaults and bodies. The V keys follow the stock bodies. No stock body's text remains in the tree. Every stock body's calls resolve after a production load. Each test fails with its change undone.
- `scripts/gates.sh` and `scripts/smoke.sh` are green.
- A live run with `WOW_GM=off`:
- W and Space moved and jumped.
- B toggled the backpack.
- 1 reached `UseAction`.
- V, Shift-V and Ctrl-V behaved as the stock bodies do.
Built on macOS; no platform seam touched.
Fixes#570Fixes#563🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`MouselookStart()` and `MouselookStop()` now start and end mouse look with no button held, as in 1.12.1, so an addon such as CustomNameplates can steer with the mouse; `IsMouselooking()` reads the same state. Before, both did nothing.
- `MouselookStart` (`0x514210`, ungated) disarms a pending world click (`0x514810(0)`) and sets the turn channel the held right button drives (`0x515090(1, 1, …)`), entering the right button's look session: the same cursor lock and hide, the camera and the character turning with the mouse. `MouselookStop` (`0x514240`) clears it; `IsMouselooking` (`0x514270`) tests it. A right-button release ends a script-started look, as its `TurnOrActionStop` clears the same bit; the loading cover clears it (`0x5144c0`); a window deactivate does not (`0x5144a3 and eax,0xfffff00f` keeps the bit).
- A look session, right-drag or script-started, outlives losing window focus, as 1.12.1's does, and the OS cursor is handed back while the window is unfocused and taken again on focus, with no turn from the pointer's travel away. On macOS winit's cursor lock is the global `CGAssociateMouseAndMouseCursorPosition(false)`, which a deactivate leaves in place, so a held lock would freeze the pointer in other apps; 1.12.1's Windows client gives the cursor back while inactive.
- While focused, the plain right- and left-button paths are unchanged.
Fixes#487
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`SendChatMessage`'s language argument is spoken, as in 1.12.1: a racial language picked from the chat menu (`ChatFrame.lua:2404`, passed on by `ChatEdit_SendText` at `:1950-1954`) or named by an addon goes out in `CMSG_MESSAGECHAT`, where before every line went in the faction tongue.
- `0x49f1e0` starts from the speaker's default language (`0x5ec890`); when argument 3 passes `lua_isstring` (a string or a number) it is matched by name against every `Languages.dbc` row, case-insensitively (`0x49f8a0`, `SStrCmpI`), and a miss raises "Unknown language" (`0x49f2e1`). nil, an absent argument, a table or a boolean keep the default. A language the character has not learned is sent and left to the server, which drops it (vmangos `ChatHandler.cpp:175`).
- The packet builder writes the type, the language and a target for a whisper or a channel only, then the text (`0x49f6c3`-`0x49f72a`); fourteen per-type writers become one, with byte-exact goldens.
- Each new UI state is seeded with the language table.
Checked live against the local vmangos: a say, a yell, a whisper to self and a GM command round-trip through the new builder; a channel line reaches the server's channel lookup; an unknown language raises in Lua.
Fixes#164
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The 48 functions the stock `Bindings.xml` bodies call are now Lua globals, registered from the reference's three tables (`0x8500b8`, `0x84f7a0`, `0x83de68`), so an addon, an addon's `Bindings.xml` body or a `/script` line that calls `TargetNearestEnemy`, `TargetLastTarget`, `AttackTarget` or the rest no longer raises. Each runs the code its native binding runs.
- **Targeting** goes through the ordered script-call queue, so `/script TargetNearestEnemy()` then `/cast` casts at the unit the cycle picked. `TargetNearestEnemy`, `TargetNearestFriend`, `TargetNearestPartyMember` and `TargetNearestRaidMember` share the Tab cycler (`0x493f60`, modes 1-4; party and raid modes are new: `0x493eed`, `0x493f15`), reading the reverse flag as `0x6f1c10` does. `TargetLastTarget` (`0x489b00`) swaps to the last-target pair `SetSelection` stamps, and deselects when it is empty. `AttackTarget` (`0x489b50`) shares the ATTACKTARGET key's body.
- **Movement:** 23 of the 26 movement-table functions open with the gate `0x494a50`, which lets a call through only while a binding runs; here that is a binding body running, stock or an addon's. Inside one, `MoveForwardStart`/`Stop` and the rest set and clear the same held state the key does (`0x515090`); a Start with no Stop keeps moving, and the window deactivate (`0x514490`) and the loading cover (`0x5144c0`) clear it. `Jump`, `ToggleRun`, `ToggleAutoRun`, `SitOrStand` (`0x48b920`) and `ToggleSheath` (`0x48a070`) fire their key's command. `IsMouselooking` (`0x514270`) answers 1 or nil.
- **Camera:** `CameraZoomIn`/`Out([yards])` (`0x50b400`, `0x50b450`) take a number or numeric string, 1.0 when absent, into the wheel's zoom.
- Not built, so they answer and do nothing: the pitch pair, `TurnOrAction*`, `CameraOrSelectOrMove*`, `MouselookStart`/`Stop`, the `MoveView*` pairs and `ToggleMouseMove`; each is named with its address where it is registered.
Fixes#334
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The farthest zoom is now 1.12.1's `cameraDistanceMax × cameraDistanceMaxFactor`, held to 0 and 50 yd by the reference's two ordered compares (`0x5112d6`–`0x51131e`), and a NaN factor no longer crashes the first wheel notch.
- `cameraDistanceMax` is registered, default "15.0" (`0x84fbd0`), with its validator's range: `0x50b310` → `0x50b330(v, 0.0, 50.0)` accepts 0 to 50 and refuses the rest, NaN included, keeping the old value.
- `cameraDistanceMaxFactor` takes any value, as the reference registers it with no validator: `/console cameraDistanceMaxFactor 3` zooms to 45 yd. The options slider keeps its 1 to 2 range (`UIOptionsFrame.lua:90`).
- Below 0 the limit is 0; at or above 50 it is 50; a NaN product compares unordered and gives 50, as the x87 flags do. The wheel bounds the distance with `max`/`min`, never `f32::clamp` on a CVar-fed bound.
- A distance read back from the saved pose is capped at 50, not 30.
- The zoom moves to its own module, `player/camera_zoom.rs`, keeping `camera.rs` under the line budget.
Fixes#460
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With Follow Terrain on, a `cameraTerrainTiltTimeMin` above `cameraTerrainTiltTimeMax`, or a NaN in either, no longer panics the client on the first slope. The ground channel bounded its tween duration with `f32::clamp(lo, hi)`, which panics when `lo > hi` or a bound is NaN.
- **The bound** is now the reference's two ordered compares (`0x50dd29`-`0x50dd83`, `lo = TimeMin × factor`, `hi = TimeMax × factor`): below `lo` takes `lo`, else at or above `hi`, or NaN, takes `hi`, else the duration stands. Nothing orders the pair, so an inverted one is an odd duration and never a fault. `lo` is compared unrounded, as it stays on the x87 stack (`0x50dd4c`), and `hi` as the f32 it is stored as (`0x50dd40`).
- **In flight too.** The armer `0x512490` returns 1 when the request matches the one in flight (`0x512555`), and the caller bounds the duration on any nonzero return (`0x50dd21`), so the bound runs again every frame a tilt is in flight. For an ordered pair that changes nothing; an inverted pair leans for `TimeMax × factor`, and a TimeMax changed mid-tilt bounds the tilt in flight.
- **The floor** that keeps a zero or NaN duration from stalling the tween moved from the stored duration to the divide, so the in-flight bound reads the raw value the reference reads.
Checked: nine tests, each derived from the reference's rule rather than the code, covering an inverted pair below, between and above, a NaN in each input, the unrounded `lo` compare, the in-flight bound, and the issue's case end to end (Min 20, Max 1 on a 20° slope). Each fails with its piece undone, the first ones with the old `clamp` panic (`min > max, or either was NaN`). The bound matches the reference's bytes over 20M random inputs with no bit difference. Gates green on macOS; no platform seam touched.
Fixes#304🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With Follow Terrain on, a `cameraTerrainTiltTimeMin` above `cameraTerrainTiltTimeMax`, or a NaN in either, no longer panics the client on the first slope. The ground channel bounded its tween duration with `f32::clamp(lo, hi)`, which panics when `lo > hi` or a bound is NaN.
- **The bound** is now the reference's two ordered compares (`0x50dd29`-`0x50dd83`, `lo = TimeMin × factor`, `hi = TimeMax × factor`): below `lo` takes `lo`, else at or above `hi`, or NaN, takes `hi`, else the duration stands. Nothing orders the pair, so an inverted one is an odd duration and never a fault. `lo` is compared unrounded, as it stays on the x87 stack (`0x50dd4c`), and `hi` as the f32 it is stored as (`0x50dd40`).
- **In flight too.** The armer `0x512490` returns 1 when the request matches the one in flight (`0x512555`), and the caller bounds the duration on any nonzero return (`0x50dd21`), so the bound runs again every frame a tilt is in flight. For an ordered pair that changes nothing; an inverted pair leans for `TimeMax × factor`, and a TimeMax changed mid-tilt bounds the tilt in flight.
- **The floor** that keeps a zero or NaN duration from stalling the tween moved from the stored duration to the divide, so the in-flight bound reads the raw value the reference reads.
Checked: nine tests, each derived from the reference's rule rather than the code, covering an inverted pair below, between and above, a NaN in each input, the unrounded `lo` compare, the in-flight bound, and the issue's case end to end (Min 20, Max 1 on a 20° slope). Each fails with its piece undone, the first ones with the old `clamp` panic (`min > max, or either was NaN`). The bound matches the reference's bytes over 20M random inputs with no bit difference. Gates green on macOS; no platform seam touched.
Fixes#304🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- A repair the purse cannot cover is refused as in 1.12.1: the not-enough-money error and its voice line, no repair sound, nothing sent; Repair All tests the worn gear alone
- Tidy three comments: the gift-wrap getter's reader, the repair-reset test's claim, the merchant module doc's width
Fixes#412
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- A repair the purse cannot cover is refused as in 1.12.1: the not-enough-money error and its voice line, no repair sound, nothing sent; Repair All tests the worn gear alone
- Tidy three comments: the gift-wrap getter's reader, the repair-reset test's claim, the merchant module doc's width
Fixes#412
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Casting a targeted spell ends repair mode, as 1.12.1's targeting arm writes its cursor over Repair
- A right mouse-down on the world ends repair mode, as 1.12.1's WorldFrame hook resets the cursor to Point
Fixes#420Fixes#422
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Casting a targeted spell ends repair mode, as 1.12.1's targeting arm writes its cursor over Repair
- A right mouse-down on the world ends repair mode, as 1.12.1's WorldFrame hook resets the cursor to Point
Fixes#420Fixes#422
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The comment and string passes' last leftovers, and one lookup made to match the reference.
**Comments that stated a wrong reference fact**, now stating the right one:
- A cast failure clears the GCD, and a failed cooldown-on-event spell also drops its parked record (`0x6e73cc`).
- `SetUnit` is `0x505d70`; `0x476cb0` is the component clone it reaches.
- Interior sound follows the character, where the listener sits, not the camera.
- Template inheritance re-runs `LoadXML` on the one object against each template (`0x76985c`), and reads `name` and `id` from the instance alone (`0x769770`).
- On the ghost-to-alive edge the reference clears its corpse cache and sends nothing (`0x5ee990`, `0x491f50`).
- The name cache has no high-bit miss. An empty-name answer evicts the record in the reference (`0x55f6f0`).
- The combat-log deferred queue (`0xc4e208`) waits only on an item name. A unit name never waits.
- A flying spline's segments are paced by curve length (`0x454320`, `0x453760`), not by chord length.
- A stop within 3 yd of its stop point keeps the arrow nocked (`0x6019e4`).
- A missile's sound id is at `CMissile+0x40`.
- A nil or unresolved `AssistUnit` token is a gap against `0xb8` ERR_GENERIC_NO_TARGET, not a deviation.
**Strings:**
- Test, log and tool messages lose leftover history wording and placeholder labels.
- The extractor's scan summaries describe the current tint and sequence-clock paths.
- The smoke script's failure text carries no private numbers.
**Line continuations:** twelve log, panic and assert strings get back the `\` continuations they had lost. Each printed a run of spaces mid-message. Fixes#106.
**One lookup:** a region's `inherits=` is looked up as one verbatim name, matched case-insensitively (`0x6ee6f0`, `_strnicmp` at `0x6ee747`), as `framexml::expand` already did for frames. The gate before it split on commas and matched case-sensitively, so a texture or font string whose `inherits=` spelled its template in another case never received the template. `a_region_takes_a_mis_cased_template` fails before this change and passes after.
**Tests:** the quest tests stop loading three FrameXML files that no quest file uses.
**In game:** nothing changes for the stock UI. Log lines read as one sentence, and an addon texture that names its template in another case now gets it.
Verified with `scripts/check.sh` on the changed crates, plus the loader and quest tests with the install present.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The comment and string passes' last leftovers, and one lookup made to match the reference.
**Comments that stated a wrong reference fact**, now stating the right one:
- A cast failure clears the GCD, and a failed cooldown-on-event spell also drops its parked record (`0x6e73cc`).
- `SetUnit` is `0x505d70`; `0x476cb0` is the component clone it reaches.
- Interior sound follows the character, where the listener sits, not the camera.
- Template inheritance re-runs `LoadXML` on the one object against each template (`0x76985c`), and reads `name` and `id` from the instance alone (`0x769770`).
- On the ghost-to-alive edge the reference clears its corpse cache and sends nothing (`0x5ee990`, `0x491f50`).
- The name cache has no high-bit miss. An empty-name answer evicts the record in the reference (`0x55f6f0`).
- The combat-log deferred queue (`0xc4e208`) waits only on an item name. A unit name never waits.
- A flying spline's segments are paced by curve length (`0x454320`, `0x453760`), not by chord length.
- A stop within 3 yd of its stop point keeps the arrow nocked (`0x6019e4`).
- A missile's sound id is at `CMissile+0x40`.
- A nil or unresolved `AssistUnit` token is a gap against `0xb8` ERR_GENERIC_NO_TARGET, not a deviation.
**Strings:**
- Test, log and tool messages lose leftover history wording and placeholder labels.
- The extractor's scan summaries describe the current tint and sequence-clock paths.
- The smoke script's failure text carries no private numbers.
**Line continuations:** twelve log, panic and assert strings get back the `\` continuations they had lost. Each printed a run of spaces mid-message. Fixes#106.
**One lookup:** a region's `inherits=` is looked up as one verbatim name, matched case-insensitively (`0x6ee6f0`, `_strnicmp` at `0x6ee747`), as `framexml::expand` already did for frames. The gate before it split on commas and matched case-sensitively, so a texture or font string whose `inherits=` spelled its template in another case never received the template. `a_region_takes_a_mis_cased_template` fails before this change and passes after.
**Tests:** the quest tests stop loading three FrameXML files that no quest file uses.
**In game:** nothing changes for the stock UI. Log lines read as one sentence, and an addon texture that names its template in another case now gets it.
Verified with `scripts/check.sh` on the changed crates, plus the loader and quest tests with the install present.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The strings pass for `benilla-app`, the last crate: string literals and test names lose the private record numbers and the workspace's words that a comment pass cannot touch.
Fixes#92. Fixes#36.
- **Messages.** About 230 assertion, panic, `expect` and log messages, printed tool labels, and comments inside embedded Lua fixtures drop their record numbers; the eight that named "the director" keep their fact in plain words. Stale claims in a few messages are corrected on the way (a cast-bar step, a font line, the every-shot recompute).
- **Names.** Seven test and helper names that carried a record number or "director" now say what they check. `git grep` found no other use of any of them.
- **Player files.** The key-bindings file's header and `config.toml`'s header each lose a record citation. Neither header is compared when the file is read back: the bindings reader skips every `#` line, and `config.toml`'s header is a TOML comment.
- **Fixtures.** Test fixtures use neutral character, account and frame names.
- **#36.** The quest-material test labels GameObject type 10 GOOBER (in 1.12, 3 is CHEST, 9 TEXT, 10 GOOBER); the code under test was already right, and the comments were fixed with the comment pass.
- **Verification.** With comments stripped and every literal's contents masked, each file is identical to main and keeps its literals' line counts, apart from the renamed functions and one rustfmt reflow. `cargo fmt --check` is clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The strings pass for `benilla-app`, the last crate: string literals and test names lose the private record numbers and the workspace's words that a comment pass cannot touch.
Fixes#92. Fixes#36.
- **Messages.** About 230 assertion, panic, `expect` and log messages, printed tool labels, and comments inside embedded Lua fixtures drop their record numbers; the eight that named "the director" keep their fact in plain words. Stale claims in a few messages are corrected on the way (a cast-bar step, a font line, the every-shot recompute).
- **Names.** Seven test and helper names that carried a record number or "director" now say what they check. `git grep` found no other use of any of them.
- **Player files.** The key-bindings file's header and `config.toml`'s header each lose a record citation. Neither header is compared when the file is read back: the bindings reader skips every `#` line, and `config.toml`'s header is a TOML comment.
- **Fixtures.** Test fixtures use neutral character, account and frame names.
- **#36.** The quest-material test labels GameObject type 10 GOOBER (in 1.12, 3 is CHEST, 9 TEXT, 10 GOOBER); the code under test was already right, and the comments were fixed with the comment pass.
- **Verification.** With comments stripped and every literal's contents masked, each file is identical to main and keeps its literals' line counts, apart from the renamed functions and one rustfmt reflow. `cargo fmt --check` is clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Part of #41: the third of five `benilla-app` pieces. This one is the player, the entities, animation and targeting: the mover, the camera, movement on the wire, the body and creature animation drivers, spell visuals, equipment and corpses, and the target, cursor and by-name layers. Their comments are rewritten to the rule in `docs/METHOD.md`, which is what the code does and the reference fact behind it, in a line. 26,066 comment lines become 9,042.
Fixes#104. Fixes#108. Fixes#109.
- **The mechanical strip.** The citations that were only a private record number go first, and each edit is shown to be a pure removal.
- **Wrong facts corrected.** The rewrite corrects the comments that stated wrong facts. Among them:
- The follow gate's `0x606ba0` is `CanCooperate` (a faction-group-mask test), not `CanAssist` (`0x6066f0`).
- The facing report is sent from the move-command drain (`0x615fc6`); `0x617100`/`0x617170` are the transport-GUID heartbeat.
- vmangos turns a corpse into bones under the corpse's own guid (`Map.cpp:3608-3649`), and the bones get new flags and zeroed item slots.
- Two claims about the item models are refuted by the shipped data: not every helm stem ships all sixteen race and sex files, and some item emitters sit under a keyed billboard chain.
- Docs sitting on the wrong item, stale function names, and disassembler labels, which become client addresses.
- vmangos line citations are checked against the pinned vmangos revision.
- **Deviation labels.** "Deviation:" now marks only a choice with a reason. An unbuilt behaviour, or one whose reference side is untraced, says so plainly.
- **Verification.** Every file strips to identical code, and `cargo fmt --check` is clean. Six second readers compared every file old against new, restored the facts the rewrite had dropped, and checked each new citation against the code, the shipped data, vmangos and the reference.
Behaviour differences the rewrite surfaced are filed as their own issues (#293, #294, #301, #308, #316 and #317 among them). The comments here state what the code does. String literals are untouched; they are #92.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Part of #41: the third of five `benilla-app` pieces. This one is the player, the entities, animation and targeting: the mover, the camera, movement on the wire, the body and creature animation drivers, spell visuals, equipment and corpses, and the target, cursor and by-name layers. Their comments are rewritten to the rule in `docs/METHOD.md`, which is what the code does and the reference fact behind it, in a line. 26,066 comment lines become 9,042.
Fixes#104. Fixes#108. Fixes#109.
- **The mechanical strip.** The citations that were only a private record number go first, and each edit is shown to be a pure removal.
- **Wrong facts corrected.** The rewrite corrects the comments that stated wrong facts. Among them:
- The follow gate's `0x606ba0` is `CanCooperate` (a faction-group-mask test), not `CanAssist` (`0x6066f0`).
- The facing report is sent from the move-command drain (`0x615fc6`); `0x617100`/`0x617170` are the transport-GUID heartbeat.
- vmangos turns a corpse into bones under the corpse's own guid (`Map.cpp:3608-3649`), and the bones get new flags and zeroed item slots.
- Two claims about the item models are refuted by the shipped data: not every helm stem ships all sixteen race and sex files, and some item emitters sit under a keyed billboard chain.
- Docs sitting on the wrong item, stale function names, and disassembler labels, which become client addresses.
- vmangos line citations are checked against the pinned vmangos revision.
- **Deviation labels.** "Deviation:" now marks only a choice with a reason. An unbuilt behaviour, or one whose reference side is untraced, says so plainly.
- **Verification.** Every file strips to identical code, and `cargo fmt --check` is clean. Six second readers compared every file old against new, restored the facts the rewrite had dropped, and checked each new citation against the code, the shipped data, vmangos and the reference.
Behaviour differences the rewrite surfaced are filed as their own issues (#293, #294, #301, #308, #316 and #317 among them). The comments here state what the code does. String literals are untouched; they are #92.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
Comments that pointed into the maintainer's private reverse-engineering notes (a note by name, a
path, a section or finding id, the analysis's own name for a client function, a process word) now
carry the fact itself, cited by the reference client's address, by vmangos file and line, or as
"the reference". Nobody outside could follow those pointers.
Comment text only, plus the test-assert and log strings that carried the same pointers. No claim
changes: a comment the notes have since corrected keeps its wording here, and correcting it is
separate work.
The two reference generators, `scripts/gen-reference-globals.py` and
`scripts/gen-reference-verb-events.py`, named the private notes' layout to find their inputs. They
now take the input files as arguments (`--capture`, `--framexml`, `--names`, `--extents`,
`--disasm`). Run on the same inputs, both write the committed tables row for row; only the two
header lines that named the old environment variable change.
Checked per file:
- the code is identical: each file with its comments stripped compares equal before and after; a
changed string literal is compared under a mask that proves only literal text moved and every
literal kept its line count;
- no client address the old text cited was dropped;
- a second reviewer checked every added address against its derivation;
- a detector for the notes' names, paths, ids, function names and process words finds none left;
- `scripts/gates.sh` is green.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comments that pointed into the maintainer's private reverse-engineering notes (a note by name, a
path, a section or finding id, the analysis's own name for a client function, a process word) now
carry the fact itself, cited by the reference client's address, by vmangos file and line, or as
"the reference". Nobody outside could follow those pointers.
Comment text only, plus the test-assert and log strings that carried the same pointers. No claim
changes: a comment the notes have since corrected keeps its wording here, and correcting it is
separate work.
The two reference generators, `scripts/gen-reference-globals.py` and
`scripts/gen-reference-verb-events.py`, named the private notes' layout to find their inputs. They
now take the input files as arguments (`--capture`, `--framexml`, `--names`, `--extents`,
`--disasm`). Run on the same inputs, both write the committed tables row for row; only the two
header lines that named the old environment variable change.
Checked per file:
- the code is identical: each file with its comments stripped compares equal before and after; a
changed string literal is compared under a mask that proves only literal text moved and every
literal kept its line count;
- no client address the old text cited was dropped;
- a second reviewer checked every added address against its derivation;
- a detector for the notes' names, paths, ids, function names and process words finds none left;
- `scripts/gates.sh` is green.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- a Vanish, a stealth or a unit leaving range clears your target at once, not two seconds later, and TAB reaches a mob behind you (2345)
- a fully painted terrain layer covers fully: roads and rock no longer let about 6% of the grass underneath show through (2345)
- companion pets show their names, the saved camera pitch survives a login, and a missed resurrect offer no longer blocks later ones (2345)
- ESC on a quest giver closes it, browsing the auction house by category searches the right items, and abandon removes the quest you chose (2345)
- a profession window open at logout no longer reopens for the next character (2345)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- a Vanish, a stealth or a unit leaving range clears your target at once, not two seconds later, and TAB reaches a mob behind you (2345)
- a fully painted terrain layer covers fully: roads and rock no longer let about 6% of the grass underneath show through (2345)
- companion pets show their names, the saved camera pitch survives a login, and a missed resurrect offer no longer blocks later ones (2345)
- ESC on a quest giver closes it, browsing the auction house by category searches the right items, and abandon removes the quest you chose (2345)
- a profession window open at logout no longer reopens for the next character (2345)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- a creature walking a path no longer cuts its first corner: the path's first waypoint was dropped at decode (2342)
- two characters whose names differ only in accented or non-Latin letters no longer share macros, bindings and SavedVariables; a file that failed to load is not overwritten at logout (2342)
- custom chat channels survive a logout, a collapsed quest header keeps its quests watched, and a trackpad swipe scrolls as far as it adds up to (2342)
- a hearth off a zeppelin no longer lands back on its deck, and an opened lockbox with loot left in it unlocks (2342)
- every snapshot runs on Vulkan, in a container, before it ships (2344)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- a creature walking a path no longer cuts its first corner: the path's first waypoint was dropped at decode (2342)
- two characters whose names differ only in accented or non-Latin letters no longer share macros, bindings and SavedVariables; a file that failed to load is not overwritten at logout (2342)
- custom chat channels survive a logout, a collapsed quest header keeps its quests watched, and a trackpad swipe scrolls as far as it adds up to (2342)
- a hearth off a zeppelin no longer lands back on its deck, and an opened lockbox with loot left in it unlocks (2342)
- every snapshot runs on Vulkan, in a container, before it ships (2344)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- an item is an object in the one index, and its enchant and lifetime countdowns are its own (2334, 2340)
- the server's stand state applies, a bag equipped into a bag slot fires BAG_OPEN, and the inspect echo is dropped on purpose (2339)
- the schedule census counts declared orders only, and the architecture walls gate leaks rather than the API (2337, 2338)
- one queue for every event into the UI is not built: the reference orders its events per mechanism, not by the wire (2341)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- an item is an object in the one index, and its enchant and lifetime countdowns are its own (2334, 2340)
- the server's stand state applies, a bag equipped into a bag slot fires BAG_OPEN, and the inspect echo is dropped on purpose (2339)
- the schedule census counts declared orders only, and the architecture walls gate leaks rather than the API (2337, 2338)
- one queue for every event into the UI is not built: the reference orders its events per mechanism, not by the wire (2341)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- the spell owns its lifecycle state: the in-flight cast, the local cancel, the cooldowns and the spell-modifier tables live in one module (2328)
- a malformed MPQ, DBC, M2 or WDT file is refused instead of crashing the client, nine read sites bounded
- a test that needs the install or the addon corpus fails instead of skipping when the data is there; the Bagnon render suite runs again (2329)
- the dev crash injector is armed on every platform, and no instrument writes a path relative to the working directory
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- the spell owns its lifecycle state: the in-flight cast, the local cancel, the cooldowns and the spell-modifier tables live in one module (2328)
- a malformed MPQ, DBC, M2 or WDT file is refused instead of crashing the client, nine read sites bounded
- a test that needs the install or the addon corpus fails instead of skipping when the data is there; the Bagnon render suite runs again (2329)
- the dev crash injector is armed on every platform, and no instrument writes a path relative to the working directory
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- the CVars are one engine-side table that outlives every interface reload: a change fires its observer, the eight latched rows wait for RestartGx, and /console gains set, cvar_reset, cvar_default, cvarlist and help (2303)
- every push into the interface runs in one feed phase after the net drain and before the tick, held by a structural test on the built schedule (2304)
- subsystems own their packet handlers, dispatched in packet order; the auction house is the first family out of the 273-arm drain (2305)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- the CVars are one engine-side table that outlives every interface reload: a change fires its observer, the eight latched rows wait for RestartGx, and /console gains set, cvar_reset, cvar_default, cvarlist and help (2303)
- every push into the interface runs in one feed phase after the net drain and before the tick, held by a structural test on the built schedule (2304)
- subsystems own their packet handlers, dispatched in packet order; the auction house is the first family out of the 273-arm drain (2305)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- a panic writes benilla-config/Diagnostics/crash-<time>.txt with the build id, the location, a backtrace and the last 200 log lines, and names the file as its last line, so a bug report can attach it (2266)
- every wire-count allocation hint is capped and the compressed update object reads through the cursor, so a malformed packet can no longer ask for gigabytes (2266)
- the 23 in-world run conditions are one set, the 45 probe variables one registry, the install-gated tests say when they skip, and every gate is timed (2266)
- one workspace-level lint allowance replaces 413 per-site attributes, and the decision corpus gets a lint in the gate (2266)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- a panic writes benilla-config/Diagnostics/crash-<time>.txt with the build id, the location, a backtrace and the last 200 log lines, and names the file as its last line, so a bug report can attach it (2266)
- every wire-count allocation hint is capped and the compressed update object reads through the cursor, so a malformed packet can no longer ask for gigabytes (2266)
- the 23 in-world run conditions are one set, the 45 probe variables one registry, the install-gated tests say when they skip, and every gate is timed (2266)
- one workspace-level lint allowance replaces 413 per-site attributes, and the decision corpus gets a lint in the gate (2266)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- the world is the first draw of the UI camera's main pass, not a pass before it: one render-pass boundary fewer, −0.9 ms of GPU on the Air at full render scale (2234)
- the world-map catalog seeds at the VM's birth, before any addon file runs, so Questie's Astrolabe builds its zone table instead of raising on every path (2240)
- the login's own tutorials wait for the interface, and a cross-map worldport fires PLAYER_LEAVING_WORLD, guarded by the reference's once-per-world latch (2232, 2235, 2239)
- WOW_PROBE_BGQUEUE makes "log in while queued for a battleground" reproducible (2237)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- the world is the first draw of the UI camera's main pass, not a pass before it: one render-pass boundary fewer, −0.9 ms of GPU on the Air at full render scale (2234)
- the world-map catalog seeds at the VM's birth, before any addon file runs, so Questie's Astrolabe builds its zone table instead of raising on every path (2240)
- the login's own tutorials wait for the interface, and a cross-map worldport fires PLAYER_LEAVING_WORLD, guarded by the reference's once-per-world latch (2232, 2235, 2239)
- WOW_PROBE_BGQUEUE makes "log in while queued for a battleground" reproducible (2237)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>