Commit Graph
1685 Commits
Author SHA1 Message Date
SamandClaude Fable 5.1 6da7ed3fca the action bars' empty slots go ghost, and a rooted creature stays put
- picking a spell up off the bar no longer blacks out every empty slot: `UI-Quickslot` is a FILLED 60%-black plate, not a hollow ring (decoded off the shipped BLPs — Quickslot2's interior is RGBA 0,0,0,0, Quickslot's 0,0,0,153), and the reference dims it to half alpha in `ActionButton_ShowGrid`, so an empty well now ghosts at 30% black where it read as a hole; the wow-re cross-check then found the reference never UN-dims it (a 3-argument `SetVertexColor` preserves the current alpha at `0x79abd0`, and `SetNormalTexture` reuses the region), so in the real client the first drag leaves every button's border at half alpha for the rest of the session — benilla ends the dim on purpose and records it as the divergence it is (1782)
- the bag bar is the reference's own `MainMenuBarBagButtons.xml` (window 3 of the stock-first migration), and the swap found a real bug rather than a move: the reference's `ItemAnim_OnEvent` matches a push against the button's inventory-slot id (20..23) and benilla emitted its own container numbering (1..4), so all four bag-push cards would have silently never played against the stock body — `push_container` now emits the reference's selector (`bag + 1`), which is what an addon expects anyway; beside it, a readiness probe that asks the LOADER, not a grep, which of the 85 stock windows load off the player's chain today (58 do; the 27 that don't name their own blocker)
- a rooted, hovering, water-walking or walking creature reached benilla as an unhandled opcode: the twelve `SMSG_SPLINE_MOVE_*` (the observer twin of the ack'd force family — bare packed guid, no ack, any unit) now decode into one MOVEMENTFLAGS word per unit, which benilla kept for our mover and for a relayed player and not at all for a creature, the one body this family is ever addressed to; ROOT wipes the direction bits so the dead-reckon stops walking a rooted body on for its next heartbeat (`0x7c7340`), HOVER clears a yard off the floor without climbing a body already inside that yard (`0x636e81`), WATERWALK elects the liquid surface against the terrain, SAFE_FALL's terminal is 7.0 not 60.148 (`0x7c5d23`), and SET_RUN_MODE's bool is inverted against the bit, folded at the parse; the inspect card names a unit's granted modes in words (`granted rooted+hover`) so a creature resting a yard off the floor reads as intended rather than broken

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 16:57:55 +07:00
SamandClaude Fable 5 6356bc8f43 Enter World stops freezing character select, and bags close by the law
- Enter World stops freezing the character screen: from the click, the select image stayed on the glass ~100 ms because the loading cover could not draw until the state-flip frame — which cost 60 ms, 57 of it render+present: the world camera's first render of a 3000-entity world on cold pipelines, plus fifteen booth cameras waking on the very frame that owes the glass a cover; the glue now yields the glass on the click's own frame and the world camera (booths with it) holds off until the cover has presented — flip frame 60 → 43 ms and off the critical path — measured by the new `WOW_FRAME_PHASES` instrument, which stamps every phase of a frame and prints the render+present remainder no in-schedule stamp can see
- switching to a character you haven't clicked before no longer flashes somebody else's body in their stage — the background swapped the frame the selection changed while the character waited for its whole assembly, so whichever won is what the screen showed (53 ms of the human standing in the orc's stage, and the reverse under a stage still loading); stage and character now commit as a PAIR, loads still starting on the click and an empty stage still swapping at once — on the same cold walk, eight selections make eight rebakes, every one the right body in the frame its stage lands, where before it was twelve with four of them wrong
- the bank's six bag buttons repaint again — the stock UI repaints them only on `PLAYERBANKSLOTS_CHANGED`, which benilla fired for the vault's 24 slots alone, so a bag dropped in never appeared and one moved between slots stayed drawn in the one it left; the lock clear becomes its own system ordered ahead of BOTH feeds (resolve, then push, then fire), ending the stuck-grey icon — and `GetInventoryItemCount` is carved as not-the-stack-count: an EMPTY slot answers 1 (a sentinel, not a count), a container past the slot cutoff answers a literal 0 before any lookup (the whole answer to the stray digit on the bank's bag buttons), and an equipped bag answers the SUM of its contents gated on a DBC bit our class-11 guess missed twice — including the Soul Bag, so a warlock's shard count never showed
- the bags close by the law: `BAG_CLOSED` is the only thing in all of FrameXML that hides an open bag window, and benilla never fired it, so a window outlived its bag in both bands (equipped and bank); the carve pins the condition — the bag changed AND the slot was occupied — so a swap fires BAG_CLOSED then BAG_UPDATE with the slot occupied throughout, an unequip fires it with the announce suppressed for exactly that transition, and two identical empty bags exchanged between slots still close both windows, because the diff is on the bag's own guid rather than the container state; `PLAYERBANKSLOTS_CHANGED` meanwhile turns out to carry NO arguments — a census of all 485 dispatcher call sites — the slot travels in how many times it fires, and every bank button repaints from its own slot id

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:20:41 +07:00
SamandClaude Fable 5 631b4a99c1 Enter World stops freezing character select, and bags close by the law
- Enter World stops freezing the character screen: from the click, the select image stayed on the glass ~100 ms because the loading cover could not draw until the state-flip frame — which cost 60 ms, 57 of it render+present: the world camera's first render of a 3000-entity world on cold pipelines, plus fifteen booth cameras waking on the very frame that owes the glass a cover; the glue now yields the glass on the click's own frame and the world camera (booths with it) holds off until the cover has presented — flip frame 60 → 43 ms and off the critical path — measured by the new `WOW_FRAME_PHASES` instrument, which stamps every phase of a frame and prints the render+present remainder no in-schedule stamp can see
- switching to a character you haven't clicked before no longer flashes somebody else's body in their stage — the background swapped the frame the selection changed while the character waited for its whole assembly, so whichever won is what the screen showed (53 ms of the human standing in the orc's stage, and the reverse under a stage still loading); stage and character now commit as a PAIR, loads still starting on the click and an empty stage still swapping at once — on the same cold walk, eight selections make eight rebakes, every one the right body in the frame its stage lands, where before it was twelve with four of them wrong
- the bank's six bag buttons repaint again — the stock UI repaints them only on `PLAYERBANKSLOTS_CHANGED`, which benilla fired for the vault's 24 slots alone, so a bag dropped in never appeared and one moved between slots stayed drawn in the one it left; the lock clear becomes its own system ordered ahead of BOTH feeds (resolve, then push, then fire), ending the stuck-grey icon — and `GetInventoryItemCount` is carved as not-the-stack-count: an EMPTY slot answers 1 (a sentinel, not a count), a container past the slot cutoff answers a literal 0 before any lookup (the whole answer to the stray digit on the bank's bag buttons), and an equipped bag answers the SUM of its contents gated on a DBC bit our class-11 guess missed twice — including the Soul Bag, so a warlock's shard count never showed
- the bags close by the law: `BAG_CLOSED` is the only thing in all of FrameXML that hides an open bag window, and benilla never fired it, so a window outlived its bag in both bands (equipped and bank); the carve pins the condition — the bag changed AND the slot was occupied — so a swap fires BAG_CLOSED then BAG_UPDATE with the slot occupied throughout, an unequip fires it with the announce suppressed for exactly that transition, and two identical empty bags exchanged between slots still close both windows, because the diff is on the bag's own guid rather than the container state; `PLAYERBANKSLOTS_CHANGED` meanwhile turns out to carry NO arguments — a census of all 485 dispatcher call sites — the slot travels in how many times it fires, and every bank button repaints from its own slot id

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:20:41 +07:00
SamandClaude Fable 5 3d0bc38978 trades answer the reference's way, and the message catalog is a table
- an incoming trade request is answered by the reference's eight-leg ladder, not by reflex — before, `CMSG_BEGIN_TRADE` went back the same frame it decoded, so any player in range could put a window on your screen mid-cinematic, mid-loading, while mind-controlled, at an auctioneer, or from your own ignore list; three legs answer with something other than "busy" (the ignore list sends `CMSG_IGNORE_TRADE`, two legs send no packet at all), Block Trades ships as the CVar and options row it is in 1.12 with its refusal on the chat line the catalog says, and the consent popup that was built came back out — 1.12 signals the trade dialog from nowhere, so asking first would be a divergence every player and addon feels, and the reference's answer to an unwanted trade is already in the ladder
- the client's message catalog ships as a generated table instead of being read by eye: every refusal, notice and confirmation goes through one dispatcher taking only a message id into a 465-record registry, benilla names 163 of the keys, and the surface does not follow the key names — the byte-exact sweep settles 170 chat / 28 yellow / 267 red, none on the console arm, with the skill-up trio speaking CHAT_MSG_SKILL rather than system; a message's surface now comes from its record, not its call site, which found `ERR_SPELL_FAILED_NOTUNSHEATHED` painted red where the client paints it yellow (seventeen of our eighteen cast-fail overrides are red; that one is not, and nothing downstream could have known which id the dispatch chose) — and a new test walks the workspace's own source asserting every `ERR_` literal is a real catalog row, because an unresolvable key shows NOTHING and nobody files a bug about a toast they never saw
- starting an attack stands you up — the stand sits after the reference's attackability cluster and before any range logic, so an attackable target out of reach stands you, a friendly NPC does not, and re-issuing an attack you are already making stands you too; the rest of the intended build is proven unnecessary rather than deferred: bare ground cannot stand you on any client (its two requirements are mutually exclusive in both CVar configurations), gossip is proven not to stand, and the loot leg is unproven and left alone
- a kick returns you to the login screen again instead of the client reconnecting indefinitely and taking the account back off whoever kicked it: "nobody is here" is DECLARED now, never inferred — the login environment variables answer "log in without waiting for someone to type", which cannot answer "is anybody here?", so a run acts unattended only on `$WOW_UNATTENDED` (implied by a capture or rig run), with the doubt deliberately falling toward a loud probe timeout over a silent account fight; and the player-tests gate finally resolves the install that sourcing FrameXML off the chain has required, instead of failing red for its own reasons on any shell without `$WOW_DATA` exported

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:20:07 +07:00
SamandClaude Fable 5 6af81087f6 trades answer the reference's way, and the message catalog is a table
- an incoming trade request is answered by the reference's eight-leg ladder, not by reflex — before, `CMSG_BEGIN_TRADE` went back the same frame it decoded, so any player in range could put a window on your screen mid-cinematic, mid-loading, while mind-controlled, at an auctioneer, or from your own ignore list; three legs answer with something other than "busy" (the ignore list sends `CMSG_IGNORE_TRADE`, two legs send no packet at all), Block Trades ships as the CVar and options row it is in 1.12 with its refusal on the chat line the catalog says, and the consent popup that was built came back out — 1.12 signals the trade dialog from nowhere, so asking first would be a divergence every player and addon feels, and the reference's answer to an unwanted trade is already in the ladder
- the client's message catalog ships as a generated table instead of being read by eye: every refusal, notice and confirmation goes through one dispatcher taking only a message id into a 465-record registry, benilla names 163 of the keys, and the surface does not follow the key names — the byte-exact sweep settles 170 chat / 28 yellow / 267 red, none on the console arm, with the skill-up trio speaking CHAT_MSG_SKILL rather than system; a message's surface now comes from its record, not its call site, which found `ERR_SPELL_FAILED_NOTUNSHEATHED` painted red where the client paints it yellow (seventeen of our eighteen cast-fail overrides are red; that one is not, and nothing downstream could have known which id the dispatch chose) — and a new test walks the workspace's own source asserting every `ERR_` literal is a real catalog row, because an unresolvable key shows NOTHING and nobody files a bug about a toast they never saw
- starting an attack stands you up — the stand sits after the reference's attackability cluster and before any range logic, so an attackable target out of reach stands you, a friendly NPC does not, and re-issuing an attack you are already making stands you too; the rest of the intended build is proven unnecessary rather than deferred: bare ground cannot stand you on any client (its two requirements are mutually exclusive in both CVar configurations), gossip is proven not to stand, and the loot leg is unproven and left alone
- a kick returns you to the login screen again instead of the client reconnecting indefinitely and taking the account back off whoever kicked it: "nobody is here" is DECLARED now, never inferred — the login environment variables answer "log in without waiting for someone to type", which cannot answer "is anybody here?", so a run acts unattended only on `$WOW_UNATTENDED` (implied by a capture or rig run), with the doubt deliberately falling toward a loud probe timeout over a silent account fight; and the player-tests gate finally resolves the install that sourcing FrameXML off the chain has required, instead of failing red for its own reasons on any shell without `$WOW_DATA` exported

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:20:07 +07:00
SamandClaude Fable 5 023c490974 the server's broadcasts land, and a ghost walks through closed doors
- the four world broadcasts decode and land — a shutdown countdown, the civilian-kill alarm, a defense-tower capture and the whisper-cap refusal all reached benilla as unhandled opcodes; `SMSG_SERVER_MESSAGE` carries a row id and a fill, never a finished line, so `ServerMessages.dbc` ships (the five sentences a shutdown has, plus the client's own `[%d]: %s` row-missing fallback, which is mechanism, not an error path); the two defense broadcasts are CHANNEL lines, not system lines — delivered once per joined channel whose row carries DEFENSE, the LocalDefense copy zone-matched against the PARENT area while the text names the packet's own — so a character in neither channel sees nothing, faithfully, and since nothing auto-joins WorldDefense in 1.12 an Eastern Plaguelands tower capture reaches only players standing there unless they typed /join; the wow-re cross-check then made the length prefix load-bearing — the client borrows a pointer into the receive buffer and composes NULL on an overrun, so an overrunning length is a refusal here too: the packet drops with a log line, nothing displayed
- `BankButtonIDToInvSlotID` is carved: `isBag` is a TYPE test, not truthiness — `lua_isnumber` refuses every non-number tag without ever loading the payload, so `true` takes the same arm as `nil` while `0` and `"1"` take the bag arm — the function is TOTAL (our out-of-range 0 was an invention; the only compares in all 118 bytes are on `lua_isnumber`'s return), and the +59/+39 arithmetic stands confirmed, byte-identical to `ContainerIDToInventoryID`'s second arm
- a deliberate right-drag turn no longer stands you while seated — the observation was right and our attribution was wrong: the body-facing commit is refused by two independent gates and the stand arm is skipped outright while the button is held; what stands you is the RELEASE, a press under 200 ms with under 2.25 degrees of yaw dispatched as a right-CLICK whose interact reaches `SetStandState(0)` — this lands the deletion half, with the click's own stand held for one more wow-re answer
- a ghost walks through closed doors — the whole mechanism is one instruction image-wide, reading bit 15 through the sign flag to answer "not a collision candidate" for a DOOR when the traced body is a player in ghost form; the bit is a property of whose trace it is, not of the door, so the exclusion rides the two mover-only casts and the set is empty on every living frame — the particle snap, the precipitation probe, the mouse pick and the creature conform keep the door's collider untouched, exactly as the binary leaves them

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:19:32 +07:00
SamandClaude Fable 5 b8758d0b21 the server's broadcasts land, and a ghost walks through closed doors
- the four world broadcasts decode and land — a shutdown countdown, the civilian-kill alarm, a defense-tower capture and the whisper-cap refusal all reached benilla as unhandled opcodes; `SMSG_SERVER_MESSAGE` carries a row id and a fill, never a finished line, so `ServerMessages.dbc` ships (the five sentences a shutdown has, plus the client's own `[%d]: %s` row-missing fallback, which is mechanism, not an error path); the two defense broadcasts are CHANNEL lines, not system lines — delivered once per joined channel whose row carries DEFENSE, the LocalDefense copy zone-matched against the PARENT area while the text names the packet's own — so a character in neither channel sees nothing, faithfully, and since nothing auto-joins WorldDefense in 1.12 an Eastern Plaguelands tower capture reaches only players standing there unless they typed /join; the wow-re cross-check then made the length prefix load-bearing — the client borrows a pointer into the receive buffer and composes NULL on an overrun, so an overrunning length is a refusal here too: the packet drops with a log line, nothing displayed
- `BankButtonIDToInvSlotID` is carved: `isBag` is a TYPE test, not truthiness — `lua_isnumber` refuses every non-number tag without ever loading the payload, so `true` takes the same arm as `nil` while `0` and `"1"` take the bag arm — the function is TOTAL (our out-of-range 0 was an invention; the only compares in all 118 bytes are on `lua_isnumber`'s return), and the +59/+39 arithmetic stands confirmed, byte-identical to `ContainerIDToInventoryID`'s second arm
- a deliberate right-drag turn no longer stands you while seated — the observation was right and our attribution was wrong: the body-facing commit is refused by two independent gates and the stand arm is skipped outright while the button is held; what stands you is the RELEASE, a press under 200 ms with under 2.25 degrees of yaw dispatched as a right-CLICK whose interact reaches `SetStandState(0)` — this lands the deletion half, with the click's own stand held for one more wow-re answer
- a ghost walks through closed doors — the whole mechanism is one instruction image-wide, reading bit 15 through the sign flag to answer "not a collision candidate" for a DOOR when the traced body is a player in ghost form; the bit is a property of whose trace it is, not of the door, so the exclusion rides the two mover-only casts and the set is empty on every living frame — the particle snap, the precipitation probe, the mouse pick and the creature conform keep the door's collider untouched, exactly as the binary leaves them

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:19:32 +07:00
SamandClaude Fable 5 3858aaaa6c the bank is the reference's own window, and far sight freezes your body
- the bank window is the reference's own `BankFrame.xml` run off the player's chain — our 890-line copy is deleted whole; putting bags into the bank's bag slots works (the two verbs its buttons are wired to now exist), the purse is a real `SmallMoneyFrameTemplate` splitting 987654321 copper into three coin buttons instead of one string, `BANKFRAME_OPENED` loses the argument the real client never fires, and the bank stops auto-opening your bags while the vendor and mailbox keep doing it; the empty-slot click still plays the bag sound, because the reference's own line plays it on every click the cursor did not consume — named as faithful rather than quietly fixed
- underneath it, the bank's inventory band is a VIEW over the container feed, not a second copy — inventory ids 40..69 had answered nil because the doll array stops at 24, so the stock file swapped in would have drawn an empty bank; the six bank bag slots become real inventory slots (64..=69) fed off the player descriptor, the three bag verbs are carved from the binary (`PutItemInBackpack` IS `PutItemInBag(0xFF)` through a ten-byte thunk, the occupied-by-an-ordinary-item leg auto-stores with no destination slot at all, and the return is the Lua number 1 or nil — the nil being the whole reason a plain left-click opens a bag), and the swap caught `BankButtonIDToInvSlotID`'s bag arm six slots wrong: it took a bag number 1..6 where the reference passes the button's own id, which is the container id
- far sight freezes your own body while the view is out — the fifth conjunct of the movement predicates' shared precondition, joining the health term so the walk, the turn and the follow teardown go down together; both halves are load-bearing: the `IsActivePlayer` half is what keeps a Mind Control victim walking (possession rides the same PLAYER_FARSIGHT field), and the latch half is the RESOLVED engage rather than the raw field — and the camera is untouched, you still look around freely, which is the whole point of the spell
- the reset offer no longer outlives the character that earned it — log out after walking out of the Deadmines and your next character was offered a reset the first one had earned — and the one lockout leg no probe can ever reach (vmangos writes a last-instance map only from raid binds, which the client's handler discards as not-a-party-dungeon) is now covered end to end through the real system, its module doc saying plainly: tested and never observed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:18:51 +07:00
SamandClaude Fable 5 14bd5ea1a4 the bank is the reference's own window, and far sight freezes your body
- the bank window is the reference's own `BankFrame.xml` run off the player's chain — our 890-line copy is deleted whole; putting bags into the bank's bag slots works (the two verbs its buttons are wired to now exist), the purse is a real `SmallMoneyFrameTemplate` splitting 987654321 copper into three coin buttons instead of one string, `BANKFRAME_OPENED` loses the argument the real client never fires, and the bank stops auto-opening your bags while the vendor and mailbox keep doing it; the empty-slot click still plays the bag sound, because the reference's own line plays it on every click the cursor did not consume — named as faithful rather than quietly fixed
- underneath it, the bank's inventory band is a VIEW over the container feed, not a second copy — inventory ids 40..69 had answered nil because the doll array stops at 24, so the stock file swapped in would have drawn an empty bank; the six bank bag slots become real inventory slots (64..=69) fed off the player descriptor, the three bag verbs are carved from the binary (`PutItemInBackpack` IS `PutItemInBag(0xFF)` through a ten-byte thunk, the occupied-by-an-ordinary-item leg auto-stores with no destination slot at all, and the return is the Lua number 1 or nil — the nil being the whole reason a plain left-click opens a bag), and the swap caught `BankButtonIDToInvSlotID`'s bag arm six slots wrong: it took a bag number 1..6 where the reference passes the button's own id, which is the container id
- far sight freezes your own body while the view is out — the fifth conjunct of the movement predicates' shared precondition, joining the health term so the walk, the turn and the follow teardown go down together; both halves are load-bearing: the `IsActivePlayer` half is what keeps a Mind Control victim walking (possession rides the same PLAYER_FARSIGHT field), and the latch half is the RESOLVED engage rather than the raw field — and the camera is untouched, you still look around freely, which is the whole point of the spell
- the reset offer no longer outlives the character that earned it — log out after walking out of the Deadmines and your next character was offered a reset the first one had earned — and the one lockout leg no probe can ever reach (vmangos writes a last-instance map only from raid binds, which the client's handler discards as not-a-party-dungeon) is now covered end to end through the real system, its module doc saying plainly: tested and never observed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:18:51 +07:00
SamandClaude Fable 5 f9e75a79fc the ghost world gets its own sky, and a corpse cannot steer
- a dead player's sky is DeathClouds now — `lightSkyboxID` was named in the schema and read by nobody and `LightSkybox.dbc` was never loaded at all, so a ghost kept the ordinary gradient dome; the shipped data has exactly 5 of 426 LightParams rows carrying a skybox id, every one of them DeathClouds.mdx, reached from the death profile (slot 4) of all 374 Light rows and gated on the ghost bit, with a hardcoded weight of 1.0 that skips the WMO skybox outright; it resolves off the same (map, camera position) seed the atmosphere uses, so the ghost sky and the death-profile fog it hangs over switch on the same frame — and the lane sheds its `wmo_sky` name for `skybox`, no longer being WMO-only
- a corpse cannot steer: the reference's two movement-input predicates share a precondition benilla never modelled — `UNIT_FIELD_HEALTH > 0` — so at zero health the keyboard turn and the right-drag close together, exactly as they do inside an Ice Block, while the ghost (health 1) keeps moving; the wow-re §5 round then corrected three byte trails (the mouse turn has its own eight-conjunct predicate, the supposed on-taxi test is the Knockdown animation lockout — set on exactly one of 208 AnimationData rows — and the "charm bit" is the far-sight latch) and added three verified health gates we lacked: the jump IS health-gated, sit and stand are refused while the body reads dead (a feigner too), and a follow tears down only when BOTH predicates drop — we had that wrong in both directions
- the walk-gait cross-check folds back confirming `min(walk, run)` and the speed-only animation channel (by a 2,304-function closure census), and finds the trap we lacked: every incoming spline re-authors the gait, and the spline's RUNMODE flag is an INVERSE — a spline marked as a run clears walk mode, one not so marked SETS it — so a charge, taxi, knockback or flee hand-off leaves the body in whatever gait its path was authored with, applied once on the spline's start edge; the post-worldport zero-walk fallback is retracted on a re-run (the ctor's zero is a transient thirty instructions wide, never observable), leaving the plain minimum the bytes compute — the better failure mode too, since a fallback makes a walker silently RUN if a walk speed ever goes missing, where the transcription makes them visibly stop

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:18:16 +07:00
SamandClaude Fable 5 25c865c00a the ghost world gets its own sky, and a corpse cannot steer
- a dead player's sky is DeathClouds now — `lightSkyboxID` was named in the schema and read by nobody and `LightSkybox.dbc` was never loaded at all, so a ghost kept the ordinary gradient dome; the shipped data has exactly 5 of 426 LightParams rows carrying a skybox id, every one of them DeathClouds.mdx, reached from the death profile (slot 4) of all 374 Light rows and gated on the ghost bit, with a hardcoded weight of 1.0 that skips the WMO skybox outright; it resolves off the same (map, camera position) seed the atmosphere uses, so the ghost sky and the death-profile fog it hangs over switch on the same frame — and the lane sheds its `wmo_sky` name for `skybox`, no longer being WMO-only
- a corpse cannot steer: the reference's two movement-input predicates share a precondition benilla never modelled — `UNIT_FIELD_HEALTH > 0` — so at zero health the keyboard turn and the right-drag close together, exactly as they do inside an Ice Block, while the ghost (health 1) keeps moving; the wow-re §5 round then corrected three byte trails (the mouse turn has its own eight-conjunct predicate, the supposed on-taxi test is the Knockdown animation lockout — set on exactly one of 208 AnimationData rows — and the "charm bit" is the far-sight latch) and added three verified health gates we lacked: the jump IS health-gated, sit and stand are refused while the body reads dead (a feigner too), and a follow tears down only when BOTH predicates drop — we had that wrong in both directions
- the walk-gait cross-check folds back confirming `min(walk, run)` and the speed-only animation channel (by a 2,304-function closure census), and finds the trap we lacked: every incoming spline re-authors the gait, and the spline's RUNMODE flag is an INVERSE — a spline marked as a run clears walk mode, one not so marked SETS it — so a charge, taxi, knockback or flee hand-off leaves the body in whatever gait its path was authored with, applied once on the spline's start edge; the post-worldport zero-walk fallback is retracted on a re-run (the ctor's zero is a transient thirty instructions wide, never observable), leaving the plain minimum the bytes compute — the better failure mode too, since a fallback makes a walker silently RUN if a walk speed ever goes missing, where the transcription makes them visibly stop

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:18:16 +07:00
SamandClaude Fable 5 03c70a7035 the bag windows are the real 1.12 files, and /walk actually walks
- the bag windows are the reference's own `ContainerFrame.xml` running off the player's patch chain — decision 1751 makes the stock 1.12 UI off the install the end state and `assets/ui` scaffolding that retires file-by-file (86 files against the 99-file stock corpus, 72% of the 1128-function global surface already built) — via one ordered manifest, `benilla.toc`, where a bare name is a file we ship and an `Interface\FrameXML\` path is the player's own file; the windows on screen are `ContainerFrame1..12`, the bank's six copy-pasted popouts fold into them at ids 5..10, net −750 lines, and the swap forced real engine verbs into being rather than stubs (`SetBagPortaitTexture` with its id-0 no-texture arm, `GameTooltip:AppendText`, `SetButtonPulse`, and a `<Model>` whose file is the cooldown indicator materializing as the native sweep, which three corpus addons inherit)
- the 1.12 walk toggle lands: `TOGGLERUN` latches one bit with `ToggleRun`'s own silent refusal chain (dead, rooted, on a live server spline), and the walk animation was never missing at all — the flag reaches it through exactly one channel, the speed (`0x5fd100` picks Walk over Run on `speed > 2 × walkSpeed`); pinning it caught our remote extrapolator with the two min rungs backwards — `min(walk, run)` runs BEFORE the backward min, so a walking backpedaller moves at 2.5 yd/s where we would have played them the Run clip at 4.5 — and one shared `current_speed` now keeps the local controller and the extrapolator from ever disagreeing about it
- the instance and raid lockout messages exist — six packets that were dropped as unknown opcodes now land, four of them as the CHAT_MSG_SYSTEM lines the client composes itself, which is why no Lua handler ever surfaced them — plus `IsInInstance`, `ResetInstances`, and the unit popup's "Reset all instances" row behind the reference's own predicate; where the binary contradicts vmangos: the ownership flag means "any instance bind" but vmangos only ever names raid binds, so the row was dead for every character without a raid lockout — term 1 is now also satisfied by our own eyes, walking out of a party dungeon the server itself bound us to
- opening the world map showed 3D world at the sheet's margins: `BlackoutWorld` was parented to the very `UIParent` a fullscreen panel hides, and three more frames had crossed the same boundary — the shared dropdown lists (so the map's continent and zone pickers answered a click with nothing at all), the master-looter menu, and the screenshot confirmation, which could never appear during a cinematic; `frame_flag_gate` now checks the parent seat against the reference both ways a frame acquires one (the attribute, template-inherited included, and `<Frames>` nesting), and its first harvest took 30 divergences down to three accepted ones

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:17:25 +07:00
SamandClaude Fable 5 67315143f9 the bag windows are the real 1.12 files, and /walk actually walks
- the bag windows are the reference's own `ContainerFrame.xml` running off the player's patch chain — decision 1751 makes the stock 1.12 UI off the install the end state and `assets/ui` scaffolding that retires file-by-file (86 files against the 99-file stock corpus, 72% of the 1128-function global surface already built) — via one ordered manifest, `benilla.toc`, where a bare name is a file we ship and an `Interface\FrameXML\` path is the player's own file; the windows on screen are `ContainerFrame1..12`, the bank's six copy-pasted popouts fold into them at ids 5..10, net −750 lines, and the swap forced real engine verbs into being rather than stubs (`SetBagPortaitTexture` with its id-0 no-texture arm, `GameTooltip:AppendText`, `SetButtonPulse`, and a `<Model>` whose file is the cooldown indicator materializing as the native sweep, which three corpus addons inherit)
- the 1.12 walk toggle lands: `TOGGLERUN` latches one bit with `ToggleRun`'s own silent refusal chain (dead, rooted, on a live server spline), and the walk animation was never missing at all — the flag reaches it through exactly one channel, the speed (`0x5fd100` picks Walk over Run on `speed > 2 × walkSpeed`); pinning it caught our remote extrapolator with the two min rungs backwards — `min(walk, run)` runs BEFORE the backward min, so a walking backpedaller moves at 2.5 yd/s where we would have played them the Run clip at 4.5 — and one shared `current_speed` now keeps the local controller and the extrapolator from ever disagreeing about it
- the instance and raid lockout messages exist — six packets that were dropped as unknown opcodes now land, four of them as the CHAT_MSG_SYSTEM lines the client composes itself, which is why no Lua handler ever surfaced them — plus `IsInInstance`, `ResetInstances`, and the unit popup's "Reset all instances" row behind the reference's own predicate; where the binary contradicts vmangos: the ownership flag means "any instance bind" but vmangos only ever names raid binds, so the row was dead for every character without a raid lockout — term 1 is now also satisfied by our own eyes, walking out of a party dungeon the server itself bound us to
- opening the world map showed 3D world at the sheet's margins: `BlackoutWorld` was parented to the very `UIParent` a fullscreen panel hides, and three more frames had crossed the same boundary — the shared dropdown lists (so the map's continent and zone pickers answered a click with nothing at all), the master-looter menu, and the screenshot confirmation, which could never appear during a cinematic; `frame_flag_gate` now checks the parent seat against the reference both ways a frame acquires one (the attribute, template-inherited included, and `<Frames>` nesting), and its first harvest took 30 divergences down to three accepted ones

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:17:25 +07:00
SamandClaude Fable 5 23b1a3e68b 71 more key commands, and the quest windows wear the reference's names
- the binding registry goes 130 → 201 of the client's 228 live commands: a coverage gate makes registered ∪ recorded-absent exactly the 1.12 surface, each absence naming the Lua globals whose arrival refutes it — which is how 37 rows whose mechanism had already landed came home (the keyring, the pet book, the raid tab, the six action-bar pages, both vertical multibars) — and then four mechanisms bring 71 more: the five camera views with the reference's default table and its sixteen camera CVars read out of the binary (NextView/PrevView do NOT wrap), self-cast riding UseAction's third argument with autoSelfCast a real CVar at last, the friendly target cone / assist / last-hostile as one cycler with a side (the two sides use different deadness tests on purpose, and assist neither requires an assistable target nor swings), and REPLY2 — reply to the last person you told; the friendly cone also stops borrowing the enemy side's in-combat score bonus, which would have shoved a healer targeting you to the head of every cycle
- the key names close their gaps: NUMPADEQUALS, F13-F24 and BUTTON6+ were keys the binding window listed and the file saved that the dispatcher could never press — named both ways now, with the namer and the codec structurally unable to disagree again; platform= becomes the OS gate it really is (off a Mac build the iTunes-remote category never even exists), and the Mac 1.12 slice settles that its F13 IS print-screen while F14/F15 map to UNKNOWN on purpose and stay unbindable; and the last-enemy stamp gains the four conjuncts it shipped without — you resolve, are not dead, not a ghost, not mounted, and the new target is alive-or-feigning — so TargetLastEnemy can no longer land somewhere the real client's never would
- the quest windows wear the reference's names: ~40 of the quest window's globals were ours (the reference's name usually sitting in the comment beside them), its two six-item reward pools merge into the reference's single ten-item run with the update function ported line-for-line, the four panels and the quest log take REAL scroll frames (the thumb drags now), the gossip window gets its greeting panel, the two mail panes' inert painted-at-rest scrollbars become real ones, the Skills detail pane nests as the reference has it, and nine private constants take their real names; the one that broke a feature detect: our QuestLogCount — a name 1.12 doesn't publish — made pfUI conclude it was running on TBC and die in that branch
- an EditBox is born with its constructor's five regions (the text, three selection quads and the caret), so GetRegions answers the reference's own count and authored regions start at index 6 — and a bare <NormalTexture/> now builds a live blank texture, since the element is what creates the region, not its file=; OpacityFrame lands, its first customer having arrived; and the equip / auto-equip / use bind confirms complete the soulbind arc — three different predicates with no quality leg (borrowing the loot arm's would have silenced the confirm on every white bind-on-equip item), Accept re-issuing the original action with the reference's suppress flag because 1.12 ships no confirm packet, and the soulstone use going through the same gate

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:16:49 +07:00
SamandClaude Fable 5 8ae42cc7ab 71 more key commands, and the quest windows wear the reference's names
- the binding registry goes 130 → 201 of the client's 228 live commands: a coverage gate makes registered ∪ recorded-absent exactly the 1.12 surface, each absence naming the Lua globals whose arrival refutes it — which is how 37 rows whose mechanism had already landed came home (the keyring, the pet book, the raid tab, the six action-bar pages, both vertical multibars) — and then four mechanisms bring 71 more: the five camera views with the reference's default table and its sixteen camera CVars read out of the binary (NextView/PrevView do NOT wrap), self-cast riding UseAction's third argument with autoSelfCast a real CVar at last, the friendly target cone / assist / last-hostile as one cycler with a side (the two sides use different deadness tests on purpose, and assist neither requires an assistable target nor swings), and REPLY2 — reply to the last person you told; the friendly cone also stops borrowing the enemy side's in-combat score bonus, which would have shoved a healer targeting you to the head of every cycle
- the key names close their gaps: NUMPADEQUALS, F13-F24 and BUTTON6+ were keys the binding window listed and the file saved that the dispatcher could never press — named both ways now, with the namer and the codec structurally unable to disagree again; platform= becomes the OS gate it really is (off a Mac build the iTunes-remote category never even exists), and the Mac 1.12 slice settles that its F13 IS print-screen while F14/F15 map to UNKNOWN on purpose and stay unbindable; and the last-enemy stamp gains the four conjuncts it shipped without — you resolve, are not dead, not a ghost, not mounted, and the new target is alive-or-feigning — so TargetLastEnemy can no longer land somewhere the real client's never would
- the quest windows wear the reference's names: ~40 of the quest window's globals were ours (the reference's name usually sitting in the comment beside them), its two six-item reward pools merge into the reference's single ten-item run with the update function ported line-for-line, the four panels and the quest log take REAL scroll frames (the thumb drags now), the gossip window gets its greeting panel, the two mail panes' inert painted-at-rest scrollbars become real ones, the Skills detail pane nests as the reference has it, and nine private constants take their real names; the one that broke a feature detect: our QuestLogCount — a name 1.12 doesn't publish — made pfUI conclude it was running on TBC and die in that branch
- an EditBox is born with its constructor's five regions (the text, three selection quads and the caret), so GetRegions answers the reference's own count and authored regions start at index 6 — and a bare <NormalTexture/> now builds a live blank texture, since the element is what creates the region, not its file=; OpacityFrame lands, its first customer having arrived; and the equip / auto-equip / use bind confirms complete the soulbind arc — three different predicates with no quality leg (borrowing the loot arm's would have silenced the confirm on every white bind-on-equip item), Accept re-issuing the original action with the reference's suppress flag because 1.12 ships no confirm packet, and the soulstone use going through the same gate

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:16:49 +07:00
SamandClaude Fable 5 ddc9e1a5a3 being summoned is a dialog, and a soulstone raises you where you fell
- being summoned becomes the dialog it should have been: the summon request was a name-table entry and nothing else, so a warlock's ritual, a meeting stone and a GM's summon all ended with the summoned player seeing nothing — now the three-field question raises CONFIRM_SUMMON and Accept sends the one guid back; the bytes settled three things a reasonable guess gets wrong: the refusal gate is dead OR ghost and lives in the handler (a ghost's wire health is 1, so the health leg alone lets one through — and refusing before the latch means an ill-timed summon can't silently overwrite a live question), the event carries no arguments because the popup re-reads all three getters every frame (the only way the summoner's name can appear after a cache miss), and there is no decline — Cancel, ESC and the clock running out are all silence plus the server's own two-minute timer; exercised end to end by a two-client live probe, the only thing that can, whose final assertion is that you land in the zone the dialog named
- self-resurrect is one private field and one bodyless opcode: PLAYER_SELF_RES_SPELL carries the stored spell and CMSG_SELF_RES spends it; HasSoulstone answers the Spell.dbc NAME of that spell, because that is where Blizzard put the button text — "Use Soulstone", "Reincarnation" and "Twisting Nether" are the spells' own names — and UseSoulstone sends on the death dialog's second button; a --self-res probe runs the whole arc on the live wire and measured the thing that matters: the field arrives BEFORE the health-zero flush, which is what makes the dialog's OnShow read safe
- the decode's fold-in corrected three claims we were resting on: HasSoulstone has a dead gate that runs FIRST, so a released ghost (wire health 1) loses the button at release rather than the dialog merely hiding; a zero field is not the empty answer — the client walks your carried inventory (gear, bags, backpack, keyring; no bank, no buyback) for an item whose on-use spell self-resurrects, labels the button with the ITEM's name and uses the item instead of sending, the two legs exclusive; and an unresolvable spell id labels the button "UNKNOWN" rather than hiding a self-res the server is holding for you

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:16:13 +07:00
SamandClaude Fable 5 542c2ac04b being summoned is a dialog, and a soulstone raises you where you fell
- being summoned becomes the dialog it should have been: the summon request was a name-table entry and nothing else, so a warlock's ritual, a meeting stone and a GM's summon all ended with the summoned player seeing nothing — now the three-field question raises CONFIRM_SUMMON and Accept sends the one guid back; the bytes settled three things a reasonable guess gets wrong: the refusal gate is dead OR ghost and lives in the handler (a ghost's wire health is 1, so the health leg alone lets one through — and refusing before the latch means an ill-timed summon can't silently overwrite a live question), the event carries no arguments because the popup re-reads all three getters every frame (the only way the summoner's name can appear after a cache miss), and there is no decline — Cancel, ESC and the clock running out are all silence plus the server's own two-minute timer; exercised end to end by a two-client live probe, the only thing that can, whose final assertion is that you land in the zone the dialog named
- self-resurrect is one private field and one bodyless opcode: PLAYER_SELF_RES_SPELL carries the stored spell and CMSG_SELF_RES spends it; HasSoulstone answers the Spell.dbc NAME of that spell, because that is where Blizzard put the button text — "Use Soulstone", "Reincarnation" and "Twisting Nether" are the spells' own names — and UseSoulstone sends on the death dialog's second button; a --self-res probe runs the whole arc on the live wire and measured the thing that matters: the field arrives BEFORE the health-zero flush, which is what makes the dialog's OnShow read safe
- the decode's fold-in corrected three claims we were resting on: HasSoulstone has a dead gate that runs FIRST, so a released ghost (wire health 1) loses the button at release rather than the dialog merely hiding; a zero field is not the empty answer — the client walks your carried inventory (gear, bags, backpack, keyring; no bank, no buyback) for an item whose on-use spell self-resurrects, labels the button with the ITEM's name and uses the item instead of sending, the two legs exclusive; and an unresolvable spell id labels the button "UNKNOWN" rather than hiding a self-res the server is holding for you

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:16:13 +07:00
SamandClaude Fable 5 a5c44f367e deleting a good item asks in the reference's words
- deleting a rare-or-better item forks to the reference's typed confirm — "Type \"DELETE\" into the field to confirm": Yes starts disabled, the box takes focus, the button enables only while the field spells DELETE, and a reflexive Enter over a half-typed word is inert; the missing engine piece was one hook, the edit box's on-text-changed; beside it, the loot bind-on-pickup confirm — bind-on-pickup AND uncommon-or-better, two conjuncts and no others, so a white BoP drop is taken with no dialog and a bind-on-equip blue never asks here — with the reference's one-pending-slot latch, and the take split into two verbs the way 1.12 actually has it: the row click stays C-side, and LootSlot is only the confirmation's continuation
- the message frames get their justify quartet, read out of the binary's method tables (with the Button table re-derived as the control that lacks it); Frame:EnableDrawLayer/DisableDrawLayer and Region:GetDrawLayer land — the disabled layers are a five-bit mask on the frame, so re-enabling a layer restores exactly what was showing underneath; and the Skills tab's children take the reference's names, with SKILLS_TO_DISPLAY published — pfUI loops over it and got "'for' limit must be a number"
- the README's roadmap line now names what is genuinely big — battlegrounds and meeting stones, both verified still missing — plus the long tail, because the old line named four features that have since landed and small features go stale on a roadmap within a week

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:15:38 +07:00
SamandClaude Fable 5 20f38e1f7e deleting a good item asks in the reference's words
- deleting a rare-or-better item forks to the reference's typed confirm — "Type \"DELETE\" into the field to confirm": Yes starts disabled, the box takes focus, the button enables only while the field spells DELETE, and a reflexive Enter over a half-typed word is inert; the missing engine piece was one hook, the edit box's on-text-changed; beside it, the loot bind-on-pickup confirm — bind-on-pickup AND uncommon-or-better, two conjuncts and no others, so a white BoP drop is taken with no dialog and a bind-on-equip blue never asks here — with the reference's one-pending-slot latch, and the take split into two verbs the way 1.12 actually has it: the row click stays C-side, and LootSlot is only the confirmation's continuation
- the message frames get their justify quartet, read out of the binary's method tables (with the Button table re-derived as the control that lacks it); Frame:EnableDrawLayer/DisableDrawLayer and Region:GetDrawLayer land — the disabled layers are a five-bit mask on the frame, so re-enabling a layer restores exactly what was showing underneath; and the Skills tab's children take the reference's names, with SKILLS_TO_DISPLAY published — pfUI loops over it and got "'for' limit must be a number"
- the README's roadmap line now names what is genuinely big — battlegrounds and meeting stones, both verified still missing — plus the long tail, because the old line named four features that have since landed and small features go stale on a roadmap within a week

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:15:38 +07:00
SamandClaude Fable 5 c4f432d0c3 the world map blips are the reference's, and particles go untinted
- the world map speaks the reference's names: WorldMapTooltip is declared (six corpus addons index it; pfUI dies on it at file scope), the blackout frame takes the reference's spelling BlackoutWorld, and the four hand-written party blips become the reference's own unit-blip templates — all 44 blips as instances, each with its published $parentIcon, the name pfUI recolours group blips by class through; the 40 raid ones are declared but not positioned yet, named in the OUT list rather than faked
- the templates addons build their own buttons from are published: ItemButtonTemplate, ContainerFrameItemButtonTemplate and the bag-slot chain — most of the handler scripts were live the whole time (this client executes the reference's own container and paper-doll code off the player's patch chain), so what was missing was mostly declarations, not behaviour — the bank arm stays out and is named as the omission it is; OptionsFrameSliders becomes real, with the three Graphics rows reading it and re-reading on show, so an addon's write to a slider's bounds actually moves the slider; and the template census that had ranked these at "zero demand" learns to see a local bound to a literal — its table went from 3 rows to 7
- the bindings answer in 1.12's own shapes: Button:IsEnabled() answers the NUMBER 1 or 0 (0 is truthy in Lua, so the reference's own == 0 tests got false forever, and three of our files had hand-worked around the divergence rather than fixed it); the constructors' string arguments are four shapes, not one rule — pfUI passes a Button where a template name goes and the reference constructs the FontString and ignores it; SetInventoryItem returns THREE values, the reference's own paper doll destructuring repairCost; UnitIsVisible is object presence and nothing else (57 bytes, one branch, no distance — an out-of-range party member is exists-yes, visible-nil); the tapped pair reads the two dynamic-flags bits, and its repaint wire fires UNIT_FACTION on the tap bit — the half a passing predicate test never sees; UnitIsPartyLeader is two ORed legs covering disjoint failures (a held stranger leading their own party, and an out-of-range member with no object to read a flag from); and the four nameplate verbs take no argument and return zero values
- the ghost's wisps compose onto its body instead of over it — the four emitters drew at full strength over a body at alpha 0.5, and now inherit the instance alpha down the attachment chain the way the reference recomposes it per frame; the residual we had parked ("the wisps should inherit the tint too") is REFUTED at the bytes: a particle batch commits hardcoded white RGB, and even that never reaches the fragment — particles are never tinted, only the alpha crosses, applied to the alpha byte alone — so the shipped shader split was already right and no code changes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:15:16 +07:00
SamandClaude Fable 5 a0d2fc383b the world map blips are the reference's, and particles go untinted
- the world map speaks the reference's names: WorldMapTooltip is declared (six corpus addons index it; pfUI dies on it at file scope), the blackout frame takes the reference's spelling BlackoutWorld, and the four hand-written party blips become the reference's own unit-blip templates — all 44 blips as instances, each with its published $parentIcon, the name pfUI recolours group blips by class through; the 40 raid ones are declared but not positioned yet, named in the OUT list rather than faked
- the templates addons build their own buttons from are published: ItemButtonTemplate, ContainerFrameItemButtonTemplate and the bag-slot chain — most of the handler scripts were live the whole time (this client executes the reference's own container and paper-doll code off the player's patch chain), so what was missing was mostly declarations, not behaviour — the bank arm stays out and is named as the omission it is; OptionsFrameSliders becomes real, with the three Graphics rows reading it and re-reading on show, so an addon's write to a slider's bounds actually moves the slider; and the template census that had ranked these at "zero demand" learns to see a local bound to a literal — its table went from 3 rows to 7
- the bindings answer in 1.12's own shapes: Button:IsEnabled() answers the NUMBER 1 or 0 (0 is truthy in Lua, so the reference's own == 0 tests got false forever, and three of our files had hand-worked around the divergence rather than fixed it); the constructors' string arguments are four shapes, not one rule — pfUI passes a Button where a template name goes and the reference constructs the FontString and ignores it; SetInventoryItem returns THREE values, the reference's own paper doll destructuring repairCost; UnitIsVisible is object presence and nothing else (57 bytes, one branch, no distance — an out-of-range party member is exists-yes, visible-nil); the tapped pair reads the two dynamic-flags bits, and its repaint wire fires UNIT_FACTION on the tap bit — the half a passing predicate test never sees; UnitIsPartyLeader is two ORed legs covering disjoint failures (a held stranger leading their own party, and an out-of-range member with no object to read a flag from); and the four nameplate verbs take no argument and return zero values
- the ghost's wisps compose onto its body instead of over it — the four emitters drew at full strength over a body at alpha 0.5, and now inherit the instance alpha down the attachment chain the way the reference recomposes it per frame; the residual we had parked ("the wisps should inherit the tint too") is REFUTED at the bytes: a particle batch commits hardcoded white RGB, and even that never reaches the fragment — particles are never tinted, only the alpha crosses, applied to the alpha byte alone — so the shipped shader split was already right and no code changes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:15:16 +07:00
SamandClaude Fable 5 e70654ac01 the select-screen ghost is four mechanisms, and a click raises windows
- the select screen's ghost is four mechanisms off one roster-row bit, and the rest of them land: the FFX pass swap — the reference builds TWO pass pairs, world and glue, and a ghost row installs the glue DEATH pass over the whole diorama while the character list and buttons composite clean above it; the ghost-gated sun — the scene's light gather is wiped and one hardcoded overhead directional injected, its colours turning out to be Light.dbc's death preset (diffuse (94,153,198), ambient (26,56,85)) rather than the live capture we had parked them as, with the sign settled at the bytes (an authored light already carries one negation, so reusing that convention lights the ghost from underneath); and three glow weights corrected — the glue screens pin 0.30 login / 0.40 living select / 0.15 ghost where we rendered a flat 0.5 everywhere, so login and create get slightly less glow too, a fidelity correction to a value we had never verified
- a press brings its window to the front: the mouse-DOWN handler raises the capture-or-hover frame unguarded, the toplevel gate living inside the worker — which is what makes a click on a child raise the window that owns it — and the release never raises; behind it, a frame-by-frame diff against the reference put back what the transcription dropped: toplevel on 47 frames (without it every raise trigger was a silent no-op across the whole interface), the mouse on 16 window bodies (a click on the bank's or merchant's own background was turning the camera or dropping your target), and id= on 81 frames — the slot index a 1.12 addon reads, where GetID() answered 0; a new gate reads our flags off the LOADED engine against the reference's, both directions with a reason each, so the next drift can't hide behind renamed templates
- the jump stops being frame-rate dependent: the fall step becomes the reference's closed form (the mean of the endpoint velocities is the exact displacement rate, and both our movers had the error in opposite directions), taking the apex from 1.668 yd at 144 fps and 1.844 at 20 to a flat 1.640 against the analytic 1.640 — a live run apexed at 1.6410; and the knockback's freeze is its PLANTED direction bit, not a velocity test — air control opens exactly while the direction nibble is clear, the launch plants forward whatever its speed, and the bit now rides the wire, which we had never sent at all
- the walk-away leash belongs to the OBJECT: the reference's watchdog overwrites the opener's stored range on the player typemask bit alone with (10+4)², so a player-shared quest panel is leashed at 14 yards — not at the 5.56-yard NPC reach, and not never, as two earlier readings had it — while a census of all 14 arming sites confirms every other NPC window already closed at the right distance; the walk-away also sends strictly LESS than the button: an NPC panel closes silently while a share still owes its decline

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:14:48 +07:00
SamandClaude Fable 5 961d7e46a0 the select-screen ghost is four mechanisms, and a click raises windows
- the select screen's ghost is four mechanisms off one roster-row bit, and the rest of them land: the FFX pass swap — the reference builds TWO pass pairs, world and glue, and a ghost row installs the glue DEATH pass over the whole diorama while the character list and buttons composite clean above it; the ghost-gated sun — the scene's light gather is wiped and one hardcoded overhead directional injected, its colours turning out to be Light.dbc's death preset (diffuse (94,153,198), ambient (26,56,85)) rather than the live capture we had parked them as, with the sign settled at the bytes (an authored light already carries one negation, so reusing that convention lights the ghost from underneath); and three glow weights corrected — the glue screens pin 0.30 login / 0.40 living select / 0.15 ghost where we rendered a flat 0.5 everywhere, so login and create get slightly less glow too, a fidelity correction to a value we had never verified
- a press brings its window to the front: the mouse-DOWN handler raises the capture-or-hover frame unguarded, the toplevel gate living inside the worker — which is what makes a click on a child raise the window that owns it — and the release never raises; behind it, a frame-by-frame diff against the reference put back what the transcription dropped: toplevel on 47 frames (without it every raise trigger was a silent no-op across the whole interface), the mouse on 16 window bodies (a click on the bank's or merchant's own background was turning the camera or dropping your target), and id= on 81 frames — the slot index a 1.12 addon reads, where GetID() answered 0; a new gate reads our flags off the LOADED engine against the reference's, both directions with a reason each, so the next drift can't hide behind renamed templates
- the jump stops being frame-rate dependent: the fall step becomes the reference's closed form (the mean of the endpoint velocities is the exact displacement rate, and both our movers had the error in opposite directions), taking the apex from 1.668 yd at 144 fps and 1.844 at 20 to a flat 1.640 against the analytic 1.640 — a live run apexed at 1.6410; and the knockback's freeze is its PLANTED direction bit, not a velocity test — air control opens exactly while the direction nibble is clear, the launch plants forward whatever its speed, and the bit now rides the wire, which we had never sent at all
- the walk-away leash belongs to the OBJECT: the reference's watchdog overwrites the opener's stored range on the player typemask bit alone with (10+4)², so a player-shared quest panel is leashed at 14 yards — not at the 5.56-yard NPC reach, and not never, as two earlier readings had it — while a census of all 14 arming sites confirms every other NPC window already closed at the right distance; the walk-away also sends strictly LESS than the button: an NPC panel closes silently while a share still owes its decline

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:14:48 +07:00
SamandClaude Fable 5 762a86deb0 quest sharing is in, and declining at an NPC reopens its list
- quest sharing lands whole: four opcodes, the Share button live with the reference's own predicate (registering the one party event it does for the one reason it does — joining a party changes the answer without changing the log), nine verdict lines for the sharer, and the escort confirm as its own dialog whose No sends nothing at all, because there is no decline packet for it; the push result's u64 means two things by direction — the sharer being answered going up, the member the verdict is ABOUT coming down — and the verdict stays a raw byte so an unmapped value shows the reference's own nothing rather than killing the packet
- the receiving half needs almost no code, and that is the design: the server sends an eligible member an ORDINARY quest-details packet whose giver guid is the sharing PLAYER, and Accept answers with the ordinary accept — a shared quest lands on the questgiver panel we already have, and the guid's own type bits drive the two real differences: declining a share owes the sharer a DECLINE verdict, and the panel is not held to the 5.56-yard NPC service range (the server's bound for a share is 14 yards, tested at accept)
- the fold-back corrected three things days-old code had just shipped: DeclineQuest and CloseQuest are ONE routine in the reference — what varies is the giver, not which button you pressed; declining an NPC's quest RE-OPENS its list — the session end sends the giver a fresh hello, refuting our five-month-old claim that "vanilla's client-side decline sends no packet" (and the details packet's trailing u32 we had parsed and ignored is exactly what suppresses that re-open) — so declining at an NPC now returns you to its menu instead of closing the window; and BUSY is the client's OWN verdict — a share arriving while any panel is open is refused client-side, covering the mid-turn-in case the server cannot see

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:14:06 +07:00
SamandClaude Fable 5 ea3ff5d050 quest sharing is in, and declining at an NPC reopens its list
- quest sharing lands whole: four opcodes, the Share button live with the reference's own predicate (registering the one party event it does for the one reason it does — joining a party changes the answer without changing the log), nine verdict lines for the sharer, and the escort confirm as its own dialog whose No sends nothing at all, because there is no decline packet for it; the push result's u64 means two things by direction — the sharer being answered going up, the member the verdict is ABOUT coming down — and the verdict stays a raw byte so an unmapped value shows the reference's own nothing rather than killing the packet
- the receiving half needs almost no code, and that is the design: the server sends an eligible member an ORDINARY quest-details packet whose giver guid is the sharing PLAYER, and Accept answers with the ordinary accept — a shared quest lands on the questgiver panel we already have, and the guid's own type bits drive the two real differences: declining a share owes the sharer a DECLINE verdict, and the panel is not held to the 5.56-yard NPC service range (the server's bound for a share is 14 yards, tested at accept)
- the fold-back corrected three things days-old code had just shipped: DeclineQuest and CloseQuest are ONE routine in the reference — what varies is the giver, not which button you pressed; declining an NPC's quest RE-OPENS its list — the session end sends the giver a fresh hello, refuting our five-month-old claim that "vanilla's client-side decline sends no packet" (and the details packet's trailing u32 we had parsed and ignored is exactly what suppresses that re-open) — so declining at an NPC now returns you to its menu instead of closing the window; and BUSY is the client's OWN verdict — a share arriving while any panel is open is refused client-side, covering the mid-turn-in case the server cannot see

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:14:06 +07:00
SamandClaude Fable 5 d245535e68 the HUD becomes UIParent's child, and cinematics fade through black
- the transcription had dropped parent= on 72 declarations the reference carries (37 to UIParent, 28 to the action bar's art frame, 7 more), so the HUD was a field of top-level frames and UIParent:Hide() reached almost nothing — which is why a cinematic had to hide the UI at the draw level and then special-case the pointer; both workarounds are deleted and the HUD hides through the reference's own route; restoring only the UIParent half would have been the dangerous part (draw order is one flat sort, so a partial restore lifts frames over the neighbours they carry), and manifest order turns out to be load-bearing — a parent name that doesn't exist yet is silently ignored, so UIParent.xml moves to the reference's own slot with a test that fails if anything declares a child first; the chat still drew over a fly-by after all that, because the reference parents the chat windows on their TEMPLATE, invisible to a name-for-name diff when both sides rename — two declarations fix it, and the sweep that replaces the method loads the whole shipped manifest, fires CINEMATIC_START and asserts the survivors are exactly three: 91 frames visible before, 3 after, and the count comes back on stop
- every cinematic boundary cuts through the reference's 0.25 s fade to black, and the next step hangs on the fade's COMPLETION, not a delay — the shot boundary is exactly where the reference does a blocking terrain load, so a delay-driven cut shows through a hitch and a completion-driven one cannot; and a fly-by now ends into black instead of a ~0.3 s loading cover: the reference raises no cover on that path, it holds black while the ground under the player loads back, so ours holds black while the stream catches up — budgeted, never an unbounded wait
- the death arc keeps its promises across a relog: the body's own state machine now dies with the session — re-entering the world as a ghost used to match no arm of the world-edge machine, so the corpse query never went out and "Resurrect Now" stayed down until the next death; and a corpse wears its owner's guild crest — the corpse descriptor carries the guild field and resolves through the same crest cache a living tabard does, where ours dressed the body crestless
- the char-select ghost gets its body kit: the screen resolves the state kit of the very "Ghost" aura a released ghost rides in the world — tint (140,185,253), alpha 0.5, and the Ghost_state wisp model at the body's attach point, pinned by a golden that decodes the row out of the shipped DBC; the airborne model is corrected before its fix lands — the address six comments called the gravity integrator is really the fall-velocity query, and a knockback is unsteerable because its launch PLANTS the forward bit, not because the arc is airborne (our guard was a velocity proxy, so a near-zero-speed knockback steered from frame 2); and the hover inspect card now decodes a picked part's mesh tag — alpha, rig slot, and both readings of the lighting payload side by side — turning a rebuild-and-two-probes hunt into a hover

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:13:31 +07:00
SamandClaude Fable 5 1052393487 the HUD becomes UIParent's child, and cinematics fade through black
- the transcription had dropped parent= on 72 declarations the reference carries (37 to UIParent, 28 to the action bar's art frame, 7 more), so the HUD was a field of top-level frames and UIParent:Hide() reached almost nothing — which is why a cinematic had to hide the UI at the draw level and then special-case the pointer; both workarounds are deleted and the HUD hides through the reference's own route; restoring only the UIParent half would have been the dangerous part (draw order is one flat sort, so a partial restore lifts frames over the neighbours they carry), and manifest order turns out to be load-bearing — a parent name that doesn't exist yet is silently ignored, so UIParent.xml moves to the reference's own slot with a test that fails if anything declares a child first; the chat still drew over a fly-by after all that, because the reference parents the chat windows on their TEMPLATE, invisible to a name-for-name diff when both sides rename — two declarations fix it, and the sweep that replaces the method loads the whole shipped manifest, fires CINEMATIC_START and asserts the survivors are exactly three: 91 frames visible before, 3 after, and the count comes back on stop
- every cinematic boundary cuts through the reference's 0.25 s fade to black, and the next step hangs on the fade's COMPLETION, not a delay — the shot boundary is exactly where the reference does a blocking terrain load, so a delay-driven cut shows through a hitch and a completion-driven one cannot; and a fly-by now ends into black instead of a ~0.3 s loading cover: the reference raises no cover on that path, it holds black while the ground under the player loads back, so ours holds black while the stream catches up — budgeted, never an unbounded wait
- the death arc keeps its promises across a relog: the body's own state machine now dies with the session — re-entering the world as a ghost used to match no arm of the world-edge machine, so the corpse query never went out and "Resurrect Now" stayed down until the next death; and a corpse wears its owner's guild crest — the corpse descriptor carries the guild field and resolves through the same crest cache a living tabard does, where ours dressed the body crestless
- the char-select ghost gets its body kit: the screen resolves the state kit of the very "Ghost" aura a released ghost rides in the world — tint (140,185,253), alpha 0.5, and the Ghost_state wisp model at the body's attach point, pinned by a golden that decodes the row out of the shipped DBC; the airborne model is corrected before its fix lands — the address six comments called the gravity integrator is really the fall-velocity query, and a knockback is unsteerable because its launch PLANTS the forward bit, not because the arc is airborne (our guard was a velocity proxy, so a near-zero-speed knockback steered from frame 2); and the hover inspect card now decodes a picked part's mesh tag — alpha, rig slot, and both readings of the lighting payload side by side — turning a rebuild-and-two-probes hunt into a hover

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:13:31 +07:00
SamandClaude Fable 5 a6e8929f18 the resurrect prompt is a 40-yard latch, and a corpse names itself
- the "Resurrect Now" prompt was never a click: release sends the corpse query, the position is cached, and a per-frame range check (d² ≤ 1600 — 40 yards) raises CORPSE_IN_RANGE, whose Lua raises the prompt, whose button sends CMSG_RECLAIM_CORPSE — a chain we already ran at exactly 40.0; what we were missing was the bones gate: every one of the reference's three latch writers requires the corpse's bones bit clear as well as ownership, and RetrieveCorpse itself carries no guard at all, so our reclaim guid could have named a pile of bones — the gate is in now, at create and on the in-place flip
- a corpse publishes a mouseover: the "Corpse of %s" name plate and the +64 brighten, applied before the publisher's type switch with no type test — any real body qualifies, owner irrelevant, only a bone pile with nothing to take is dropped — and it fires no event, so UnitName("mouseover") over a body stays nil, here as there; sitting refuses the corpse loot click with the client-local red not-standing error and no send; and the insignia doc is corrected — bit 5 unlocks spell 22027 "Remove Insignia", the corpse's actual PvP flag is bit 2, and there is no skinning of players in 1.12.1
- the size tripwire counts code, not lines: 145 flagged files → 15, because in this codebase the comments are the knowledge base and the tests are the gate — one flagged file was 4357 lines of which 5 are code, another 1566 with ZERO — so the budget (unchanged at 1000) now counts non-test, non-comment source, calibrated so the one split on record that actually mattered still fires

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:12:49 +07:00
SamandClaude Fable 5 dc11de7ef3 the resurrect prompt is a 40-yard latch, and a corpse names itself
- the "Resurrect Now" prompt was never a click: release sends the corpse query, the position is cached, and a per-frame range check (d² ≤ 1600 — 40 yards) raises CORPSE_IN_RANGE, whose Lua raises the prompt, whose button sends CMSG_RECLAIM_CORPSE — a chain we already ran at exactly 40.0; what we were missing was the bones gate: every one of the reference's three latch writers requires the corpse's bones bit clear as well as ownership, and RetrieveCorpse itself carries no guard at all, so our reclaim guid could have named a pile of bones — the gate is in now, at create and on the in-place flip
- a corpse publishes a mouseover: the "Corpse of %s" name plate and the +64 brighten, applied before the publisher's type switch with no type test — any real body qualifies, owner irrelevant, only a bone pile with nothing to take is dropped — and it fires no event, so UnitName("mouseover") over a body stays nil, here as there; sitting refuses the corpse loot click with the client-local red not-standing error and no send; and the insignia doc is corrected — bit 5 unlocks spell 22027 "Remove Insignia", the corpse's actual PvP flag is bit 2, and there is no skinning of players in 1.12.1
- the size tripwire counts code, not lines: 145 flagged files → 15, because in this codebase the comments are the knowledge base and the tests are the gate — one flagged file was 4357 lines of which 5 are code, another 1566 with ZERO — so the budget (unchanged at 1000) now counts non-test, non-comment source, calibrated so the one split on record that actually mattered still fires

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:12:49 +07:00
SamandClaude Fable 5 7e20feb770 nothing plays over a cinematic, and a lootable corpse sparkles
- the music slot refuses everyone while a cinematic runs: the suppression was checked at two of the four callers, and the other two — the zone intro fanfare and SMSG_PLAY_MUSIC — played over the narration to their end, the fanfare even stamping its once-per-cooldown play under a shot you couldn't hear it through; the guard moved into the slot itself, the reference's own shape (its music pump dies at its own first instruction), and the narration now holds its own line in the voice budget, where before the client believed it had one more free voice than it did for the length of every cinematic
- zone chat channels stop rejoining under a cinematic: the join walk reads the streamed area, which during a fly-by is wherever the camera is, so joins fired against zones the player was only looking at; the reference gates exactly this at two sites and rejoins once at the end — which our still-armed walk now does by simply falling through on the next frame
- a lootable corpse sparkles: the reference watches the corpse's dynamic-flags loot bit and spawns its hard-coded "Loot Art" effect row — Particles\LootFX.m2 at the body's attachment point, no scale, colour or lifetime set, the asset's own emitters being the whole look — the same asset at the same tag our lootable dead units already wore, so both share one arm; the corpse ground decal, meanwhile, is (almost certainly) dead code; and the probes see a corpse as its own entity kind again — the death probe had been identifying it by a heuristic a refactor outdated, so it failed runs whose wire was entirely correct
- the player controller splits along its concerns: one 1705-line function becomes six modules (the netted axes, posture, transport riding, the move-flag words, writing the frame onto the body, and the frame order itself), player.rs 2093 → 438 lines with zero behaviour change — checked by a source-line multiset and a live A/B of the same scripted key drive producing the identical opcode sequence and flags (walk 7.000 vs 6.978 yd/s, phase not physics)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:12:30 +07:00
SamandClaude Fable 5 5f822218b2 nothing plays over a cinematic, and a lootable corpse sparkles
- the music slot refuses everyone while a cinematic runs: the suppression was checked at two of the four callers, and the other two — the zone intro fanfare and SMSG_PLAY_MUSIC — played over the narration to their end, the fanfare even stamping its once-per-cooldown play under a shot you couldn't hear it through; the guard moved into the slot itself, the reference's own shape (its music pump dies at its own first instruction), and the narration now holds its own line in the voice budget, where before the client believed it had one more free voice than it did for the length of every cinematic
- zone chat channels stop rejoining under a cinematic: the join walk reads the streamed area, which during a fly-by is wherever the camera is, so joins fired against zones the player was only looking at; the reference gates exactly this at two sites and rejoins once at the end — which our still-armed walk now does by simply falling through on the next frame
- a lootable corpse sparkles: the reference watches the corpse's dynamic-flags loot bit and spawns its hard-coded "Loot Art" effect row — Particles\LootFX.m2 at the body's attachment point, no scale, colour or lifetime set, the asset's own emitters being the whole look — the same asset at the same tag our lootable dead units already wore, so both share one arm; the corpse ground decal, meanwhile, is (almost certainly) dead code; and the probes see a corpse as its own entity kind again — the death probe had been identifying it by a heuristic a refactor outdated, so it failed runs whose wire was entirely correct
- the player controller splits along its concerns: one 1705-line function becomes six modules (the netted axes, posture, transport riding, the move-flag words, writing the frame onto the body, and the frame order itself), player.rs 2093 → 438 lines with zero behaviour change — checked by a source-line multiset and a live A/B of the same scripted key drive producing the identical opcode sequence and flags (walk 7.000 vs 6.978 yd/s, phase not physics)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:12:30 +07:00
SamandClaude Fable 5 6e3a67e039 your corpse answers the mouse, and a cinematic's last holes close
- a corpse is a body you can hover, cursor and click: it enters the same ray pass as units with the winner switched on type at publish (the reference's own shape — one trace over every object), the cursor classifier is transcribed whole (lootable-by-me → the pickup cursor, grayed by range; else the insignia leg behind the learned-skill latch; your own body falls through to the clear), and right-click is "not mounted AND lootable" → CMSG_LOOT; CORPSE_FIELD_FLAGS bit 5 is the PvP insignia flag, not "reclaimable" as an old note had it; the widening also caught that a GameObject anywhere on screen could steal the click from a nearer corpse, and that a left-click on a body cleared your target as if you had clicked ground
- the cinematic sweep closes eleven holes: a shot plays its sequence band's WIDTH, not its end — two shipped fly-bys band away from zero and ran long with their opening frames frozen (the Night Elf intro measured 102333 → 102000 ms); CMSG_NEXT_CINEMATIC_CAMERA announces every shot, the first included — the old "between shots" reading meant it was sent NEVER, since every 1.12 sequence row carries exactly one camera; the ack invariant is stated once (one CMSG_COMPLETE_CINEMATIC per accepted trigger, over-acking tolerated, under-acking never — a dropped ack despawns the world around you until relog); the hardware cursor hides for the shot; and the music cut lands in the frame that starts it — it was one frame late, exactly long enough for a first login's zone fanfare to start under the narration and be stamped as played
- RunBinding exists, and a fly-by stops handing the world its mouse: CinematicFrame's OnKeyDown hands the SCREENSHOT chord back to its binding through RunBinding, a global we never had, so every press during a fly-by raised and took no picture; and the mouse was fully live under a shot — right-drag turned the character, clicks picked targets through the flying camera, both buttons walked the body — so the pointer now reads as over the UI while a shot plays
- AdvanceTime and ReplaceIconTexture are carved off the bytes: AdvanceTime really is a no-op in the reference (its one reachable call is mov eax,1; ret), ReplaceIconTexture is a material swap over a model's type-14 texture slots that stores nothing on the widget, and SetModel(nil) RAISES — the "documented clear" our binding claimed was invented, not read; and the addon roster now says WHOSE package is short when a .toc names files the folder doesn't hold — nine of the corpus's twenty-nine load failures are the addon's own manifest, which the real client also shrugs off ("Couldn't open %s", carry on), so they stop reading as our defects

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:55:36 +07:00
SamandClaude Fable 5 4f5e78f289 your corpse answers the mouse, and a cinematic's last holes close
- a corpse is a body you can hover, cursor and click: it enters the same ray pass as units with the winner switched on type at publish (the reference's own shape — one trace over every object), the cursor classifier is transcribed whole (lootable-by-me → the pickup cursor, grayed by range; else the insignia leg behind the learned-skill latch; your own body falls through to the clear), and right-click is "not mounted AND lootable" → CMSG_LOOT; CORPSE_FIELD_FLAGS bit 5 is the PvP insignia flag, not "reclaimable" as an old note had it; the widening also caught that a GameObject anywhere on screen could steal the click from a nearer corpse, and that a left-click on a body cleared your target as if you had clicked ground
- the cinematic sweep closes eleven holes: a shot plays its sequence band's WIDTH, not its end — two shipped fly-bys band away from zero and ran long with their opening frames frozen (the Night Elf intro measured 102333 → 102000 ms); CMSG_NEXT_CINEMATIC_CAMERA announces every shot, the first included — the old "between shots" reading meant it was sent NEVER, since every 1.12 sequence row carries exactly one camera; the ack invariant is stated once (one CMSG_COMPLETE_CINEMATIC per accepted trigger, over-acking tolerated, under-acking never — a dropped ack despawns the world around you until relog); the hardware cursor hides for the shot; and the music cut lands in the frame that starts it — it was one frame late, exactly long enough for a first login's zone fanfare to start under the narration and be stamped as played
- RunBinding exists, and a fly-by stops handing the world its mouse: CinematicFrame's OnKeyDown hands the SCREENSHOT chord back to its binding through RunBinding, a global we never had, so every press during a fly-by raised and took no picture; and the mouse was fully live under a shot — right-drag turned the character, clicks picked targets through the flying camera, both buttons walked the body — so the pointer now reads as over the UI while a shot plays
- AdvanceTime and ReplaceIconTexture are carved off the bytes: AdvanceTime really is a no-op in the reference (its one reachable call is mov eax,1; ret), ReplaceIconTexture is a material swap over a model's type-14 texture slots that stores nothing on the widget, and SetModel(nil) RAISES — the "documented clear" our binding claimed was invented, not read; and the addon roster now says WHOSE package is short when a .toc names files the folder doesn't hold — nine of the corpus's twenty-nine load failures are the addon's own manifest, which the real client also shrugs off ("Couldn't open %s", carry on), so they stop reading as our defects

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:55:36 +07:00
SamandClaude Fable 5 f6f2af3da9 the combat log's wire completes, and PlayerModel is its own class
- the combat log's last eight wire bodies decode — PARTYKILLLOG, SPELLINSTAKILLLOG, PROCRESIST, SPELLORDAMAGE_IMMUNE, SPELLDISPELLOG, DISPEL_FAILED, ENCHANTMENTLOG, SPELLLOGEXECUTE — each layout read off vmangos's own writer and pinned by a byte-exact golden (DISPEL_FAILED carries no count, the list runs to the end of the body; SPELLLOGEXECUTE's POWER_DRAIN row is (amount, power, multiplier), verified at the client's bytes as well as the server's); with the eleven chat types that were waiting on them, 44 chat types become 55 — 29 sentence families, the death pair and the aura rows riding descriptor diffs because 1.12 has no "unit died" opcode, plus the six trailers ((glancing), (30 blocked), (12 absorbed) and kin) that had been missing from every family; also corrected: the durability-death line was going to the red error frame with hard-coded English, and SMSG_SPELLNONMELEEDAMAGELOG lacked its split-damage branch and its blocked wording
- PlayerModel is its own frame kind: SetUnit, RefreshUnit and SetRotation were published on Model, but they belong to the <PlayerModel> method table (0x84f1fc), and the client's lookup chains derived → base only, so a plain <Model> can never reach them — they got there from a strings scan read as ownership, and a strings hit answers whether a name EXISTS, not who owns it (SetUnit's one pooled string is referenced by the PlayerModel and GameTooltip tables and by no Model entry); the new PlayerModel → Model → Frame → Region chain unblocks CreateFrame("PlayerModel"), which is how pfUI builds every unit-frame portrait — and through pfUI, pfQuest and ShaguDPS
- a nil where a number goes is four shapes, not one law: a census of all 408 widget-registrar entries found 62 numeric positions that validate-or-raise, 43 that gate then stage an alternate, 64 that silently coerce to 0.0 and 5 that gate then store nothing — so SetMaxLetters(nil) completes and stores 0, "no limit" (the argument COUNT is checked exactly, the type is not), and the backdrop colour setters are asymmetric on purpose: a nil r/g/b channel is 0.0 but a nil alpha is OPAQUE, and reading every nil as 0.0 would have turned those borders transparent; both setters now also quantize ×255 into the packed 0xAARRGGBB byte quad the client actually stores, so 0.09 reads back as 23/255
- two more addon-killers: LIGHTYELLOW_FONT_COLOR_CODE was the one missing line of Fonts.xml's seven-line colour block (aux-addon concatenates it at file scope and died on its own first line), and the guard now enumerates the whole block l.4-20 instead of the names an earlier round had noticed absent; and the unit frames publish the reference's names — 1.12 has no *PowerBar anywhere, every bar is ManaBar and the colour table is ManaBarColor (8 of the top-20 addons read it by name), so ManaBarColor is the published table our own bars index into, with 22 more regions renamed to the reference's own

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:54:58 +07:00
SamandClaude Fable 5 31706821cd the combat log's wire completes, and PlayerModel is its own class
- the combat log's last eight wire bodies decode — PARTYKILLLOG, SPELLINSTAKILLLOG, PROCRESIST, SPELLORDAMAGE_IMMUNE, SPELLDISPELLOG, DISPEL_FAILED, ENCHANTMENTLOG, SPELLLOGEXECUTE — each layout read off vmangos's own writer and pinned by a byte-exact golden (DISPEL_FAILED carries no count, the list runs to the end of the body; SPELLLOGEXECUTE's POWER_DRAIN row is (amount, power, multiplier), verified at the client's bytes as well as the server's); with the eleven chat types that were waiting on them, 44 chat types become 55 — 29 sentence families, the death pair and the aura rows riding descriptor diffs because 1.12 has no "unit died" opcode, plus the six trailers ((glancing), (30 blocked), (12 absorbed) and kin) that had been missing from every family; also corrected: the durability-death line was going to the red error frame with hard-coded English, and SMSG_SPELLNONMELEEDAMAGELOG lacked its split-damage branch and its blocked wording
- PlayerModel is its own frame kind: SetUnit, RefreshUnit and SetRotation were published on Model, but they belong to the <PlayerModel> method table (0x84f1fc), and the client's lookup chains derived → base only, so a plain <Model> can never reach them — they got there from a strings scan read as ownership, and a strings hit answers whether a name EXISTS, not who owns it (SetUnit's one pooled string is referenced by the PlayerModel and GameTooltip tables and by no Model entry); the new PlayerModel → Model → Frame → Region chain unblocks CreateFrame("PlayerModel"), which is how pfUI builds every unit-frame portrait — and through pfUI, pfQuest and ShaguDPS
- a nil where a number goes is four shapes, not one law: a census of all 408 widget-registrar entries found 62 numeric positions that validate-or-raise, 43 that gate then stage an alternate, 64 that silently coerce to 0.0 and 5 that gate then store nothing — so SetMaxLetters(nil) completes and stores 0, "no limit" (the argument COUNT is checked exactly, the type is not), and the backdrop colour setters are asymmetric on purpose: a nil r/g/b channel is 0.0 but a nil alpha is OPAQUE, and reading every nil as 0.0 would have turned those borders transparent; both setters now also quantize ×255 into the packed 0xAARRGGBB byte quad the client actually stores, so 0.09 reads back as 23/255
- two more addon-killers: LIGHTYELLOW_FONT_COLOR_CODE was the one missing line of Fonts.xml's seven-line colour block (aux-addon concatenates it at file scope and died on its own first line), and the guard now enumerates the whole block l.4-20 instead of the names an earlier round had noticed absent; and the unit frames publish the reference's names — 1.12 has no *PowerBar anywhere, every bar is ManaBar and the colour table is ManaBarColor (8 of the top-20 addons read it by name), so ManaBarColor is the published table our own bars index into, with 22 more regions renamed to the reference's own

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:54:58 +07:00
SamandClaude Fable 5 b46cb7e338 the chat dock exists, and alt-arrow keys are declined not swallowed
- the chat dock is a list, not a hardcoded pair: DOCKED_CHAT_FRAMES, SELECTED_DOCK_FRAME and the FCF verbs — DockFrame, UnDockFrame, DockUpdate, SelectDockFrame, SaveDock, SetTabPosition, Close and the button-side trio — are transcribed from FloatingChatFrame.lua, and every "for i = 1, 2" moved to what it meant, the dock or every window; the dock seeds at load from the window record's own docked byte handed to FCF_DockFrame, whose first line IS the reference's early return — transcribing the function that answers the way the reference answers, where an empty stub "because we have no dock" would have been the bandaid; its unconditional re-lock cannot clobber an unlock you saved, because the stored look is applied last; pfUI's RefreshChat now runs end to end
- ignoreArrows is alt-arrow mode, and the arrow is DECLINED, not swallowed: the reference's edit-box handler returns unconsumed on an arrow key, so the walk carries it past the box and it runs TURNLEFT — the whole feature is steering while the chat box holds focus, where the old reading left the arrows dead; holding Alt resumes the caret; SetAltArrowKeyMode and its getter land with the reference's own quirk that a missing argument enables while nil disables (the getter answers 1 or nil, never a boolean), SetIgnoreArrows turns out not to exist in 1.12 and is unpublished, and the binding pass gets a named four-key exemption rather than a hole, because the handler consumes every other path
- pfUI's square minimap is actually square: Minimap:SetMaskTexture makes the disc's mask state instead of a load-time constant — write-only from Lua, as shipped, no invented getter — where an empty path restores the round default and a failed load keeps the current mask and warns, because either mistake would otherwise hand a mistyped path a silently square map that looks like a feature; and MiniMapBattlefieldFrame grows the Border and Shine regions beside its icon, on the reference's own layers — icon on BACKGROUND, ring on ARTWORK above it, the only order a ring around an icon can draw in — which pfUI hides unguarded to sit the icon flush

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:51:11 +07:00
SamandClaude Fable 5 850f7df3b0 the chat dock exists, and alt-arrow keys are declined not swallowed
- the chat dock is a list, not a hardcoded pair: DOCKED_CHAT_FRAMES, SELECTED_DOCK_FRAME and the FCF verbs — DockFrame, UnDockFrame, DockUpdate, SelectDockFrame, SaveDock, SetTabPosition, Close and the button-side trio — are transcribed from FloatingChatFrame.lua, and every "for i = 1, 2" moved to what it meant, the dock or every window; the dock seeds at load from the window record's own docked byte handed to FCF_DockFrame, whose first line IS the reference's early return — transcribing the function that answers the way the reference answers, where an empty stub "because we have no dock" would have been the bandaid; its unconditional re-lock cannot clobber an unlock you saved, because the stored look is applied last; pfUI's RefreshChat now runs end to end
- ignoreArrows is alt-arrow mode, and the arrow is DECLINED, not swallowed: the reference's edit-box handler returns unconsumed on an arrow key, so the walk carries it past the box and it runs TURNLEFT — the whole feature is steering while the chat box holds focus, where the old reading left the arrows dead; holding Alt resumes the caret; SetAltArrowKeyMode and its getter land with the reference's own quirk that a missing argument enables while nil disables (the getter answers 1 or nil, never a boolean), SetIgnoreArrows turns out not to exist in 1.12 and is unpublished, and the binding pass gets a named four-key exemption rather than a hole, because the handler consumes every other path
- pfUI's square minimap is actually square: Minimap:SetMaskTexture makes the disc's mask state instead of a load-time constant — write-only from Lua, as shipped, no invented getter — where an empty path restores the round default and a failed load keeps the current mask and warns, because either mistake would otherwise hand a mistyped path a silently square map that looks like a feature; and MiniMapBattlefieldFrame grows the Border and Shine regions beside its icon, on the reference's own layers — icon on BACKGROUND, ring on ARTWORK above it, the only order a ring around an icon can draw in — which pfUI hides unguarded to sit the icon flush

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:51:11 +07:00
SamandClaude Fable 5 ac74c98c15 knockback is a jump the server aims, and tabards wear their guild crest
- knockback is a jump whose seed came off the wire, not a server spline: SMSG_MOVE_KNOCK_BACK's four floats are exactly the MovementInfo jump tail in a different order (speedZ down-positive — a rising launch carries a negative value, the sign vmangos's own source marvels at), flown at the same take-off site as the keyboard jump and acked with the post-launch pose, which the server matches float-for-float within 0.01 before relaying to anyone; it is the one take-off that seeds BOTH axes, absolute in world XY, so it skips the arm that would hand the momentum back to a held key and reduce the whole knockback to a hop; root refuses it silently, a seated player stands up first, no MSG_MOVE_JUMP goes out — that packet is the keyboard's, and vmangos logs a jump at knockback speed as an overspeed cheat — and MSG_MOVE_KNOCK_BACK turns out not to be dead: an observed knockback replays as the arc it is
- your guild crest reaches your tabard: the three GuildEmblems layers — background, border, emblem, on both torso tiles — belong to no equipment slot, so the body composite never wrote those cells and every guild wore the Guild Tabard's own default art; the install is gated on a display flag only seven of 29,604 rows set, of which exactly one is worn by any item — which is why every battleground and Argent Dawn tabard keeps its own art; the five wire indices are signed, because an undesigned guild carries −1 in all five and the client refuses to paint on the first −1 (unguarded, the commonest guild state on a young realm would have worn a blank tabard); the emblem keys the skin cache, so two guilds in the same tabard display cannot lend each other crests; and a guild tabard's background hides the bra where a plain tabard does not — it is a cutout, 79.5% opaque off the shipped art, and what shows at the sides is bare skin
- a fly-by is framed by the world camera, not the M2 camera record's fov: a census of every reader shows the record's fov and clip planes reach only the portrait and Model-frame paths — nothing on the cinematic path reads any of the three — so fifteen of the sixteen shipped shots were rendering at HALF their intended vertical FOV, visibly over-zoomed on every race intro but the Undead; the fix is a deletion, and it dissolves a standing puzzle: the authored 0.22 and 27.8-yard "clips", which would render the dwarf intro's mountains as empty sky, are not clips at all on this path — two floats nobody consumes look exactly like that
- 104 of Onyxia's lava traps stood on an interior floor classified as outdoors, taking the day/night sun inside a windowless lair: a GameObject's light node anchors at its model's AUTHORED bounding-box centre, and the trap's box reaches 98.42 yd past its bind pose — the anchor sits ~40 yd under the chamber floor, where a downward ray finds nothing above it to claim; the reference recovers with a 1000-yd upward retry at the claim itself, which benilla had only at the colour sample, so the verdict was decided before any colour was read; all 104 now classify interior, beside the 26 rocks and 119 eggs that always did — and the drowned-corpse pose came back from its cross-check confirmed at the bytes (a strict liquid-depth test at exactly two-thirds of a yard, Dead or Drowned), with the two reference-side notes it contradicted corrected over there

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:50:50 +07:00
SamandClaude Fable 5 1d65531ef8 knockback is a jump the server aims, and tabards wear their guild crest
- knockback is a jump whose seed came off the wire, not a server spline: SMSG_MOVE_KNOCK_BACK's four floats are exactly the MovementInfo jump tail in a different order (speedZ down-positive — a rising launch carries a negative value, the sign vmangos's own source marvels at), flown at the same take-off site as the keyboard jump and acked with the post-launch pose, which the server matches float-for-float within 0.01 before relaying to anyone; it is the one take-off that seeds BOTH axes, absolute in world XY, so it skips the arm that would hand the momentum back to a held key and reduce the whole knockback to a hop; root refuses it silently, a seated player stands up first, no MSG_MOVE_JUMP goes out — that packet is the keyboard's, and vmangos logs a jump at knockback speed as an overspeed cheat — and MSG_MOVE_KNOCK_BACK turns out not to be dead: an observed knockback replays as the arc it is
- your guild crest reaches your tabard: the three GuildEmblems layers — background, border, emblem, on both torso tiles — belong to no equipment slot, so the body composite never wrote those cells and every guild wore the Guild Tabard's own default art; the install is gated on a display flag only seven of 29,604 rows set, of which exactly one is worn by any item — which is why every battleground and Argent Dawn tabard keeps its own art; the five wire indices are signed, because an undesigned guild carries −1 in all five and the client refuses to paint on the first −1 (unguarded, the commonest guild state on a young realm would have worn a blank tabard); the emblem keys the skin cache, so two guilds in the same tabard display cannot lend each other crests; and a guild tabard's background hides the bra where a plain tabard does not — it is a cutout, 79.5% opaque off the shipped art, and what shows at the sides is bare skin
- a fly-by is framed by the world camera, not the M2 camera record's fov: a census of every reader shows the record's fov and clip planes reach only the portrait and Model-frame paths — nothing on the cinematic path reads any of the three — so fifteen of the sixteen shipped shots were rendering at HALF their intended vertical FOV, visibly over-zoomed on every race intro but the Undead; the fix is a deletion, and it dissolves a standing puzzle: the authored 0.22 and 27.8-yard "clips", which would render the dwarf intro's mountains as empty sky, are not clips at all on this path — two floats nobody consumes look exactly like that
- 104 of Onyxia's lava traps stood on an interior floor classified as outdoors, taking the day/night sun inside a windowless lair: a GameObject's light node anchors at its model's AUTHORED bounding-box centre, and the trap's box reaches 98.42 yd past its bind pose — the anchor sits ~40 yd under the chamber floor, where a downward ray finds nothing above it to claim; the reference recovers with a 1000-yd upward retry at the claim itself, which benilla had only at the colour sample, so the verdict was decided before any colour was read; all 104 now classify interior, beside the 26 rocks and 119 eggs that always did — and the drowned-corpse pose came back from its cross-check confirmed at the bytes (a strict liquid-depth test at exactly two-thirds of a yard, Dead or Drowned), with the two reference-side notes it contradicted corrected over there

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:50:50 +07:00
SamandClaude Fable 5 f3ecb5c968 a cinematic holds your body, and all seven chat windows exist
- a cinematic holds the body while the camera flies: the streaming focus follows the eye — a Tauren's intro opens 1741 yards from the body — so the body's own ground unloaded under it and it fell (z=59.4 to −62 in four seconds, the server yanking it back); it now takes the same hold a teleport takes, released when the focus comes home to a resident tile, and the loading screen stops covering the fly-by, because "the player's ground is missing" is not a load while the eye is deliberately elsewhere; the letterbox measurement prints as an ordinary info line, and scripts/cine.sh plays any one cinematic and prints what the client did to the world while the camera was away
- a cinematic cuts the zone music: the reference asserts exactly what /console EnableMusic 0 asserts, through the same setter — a cut, not a fade, the music pump dead for the duration, resume at +3.000 s — and benilla asserts the runtime flag, never the CVar, or quitting during a 102-second intro would save your music off for good; ambience is deliberately untouched (it has the analogous flag and the cinematic never writes it); the narration has no category at all — its flags route it around every volume slider but not around the Enable Sound Effects gate, so unchecking that silences it — and the listener follows the cinematic camera for the duration
- addons can walk a UI they did not build: GetChildren/GetNumChildren/GetRegions/GetNumRegions land — the basis of the whole reskin genre, and the wall four of the twenty most-installed 1.12 addons died on: pfUI drew nothing before and now paints 495 quads, ShaguDPS 71 → 566, pfQuest 3 → 498 — with the reference's own quirks: the title region is NOT in the list (its creation path never reaches the region linker), hidden regions are returned and counted, oldest first; the Model widget gets its Lua surface (SetModel, SetSequence and siblings, read out of the shipped image, where SetRotation and SetFacing are two names for one slot); and the Minimap carries the nine engine-created Model children no XML declares, built before the frame descent, the ninth being the player arrow Questie indexes as ({Minimap:GetChildren()})[9]
- all seven chat windows come from one template, tabs and scroll buttons included — whether a window is shown has nothing to do with whether its globals resolve, and pfUI reads every piece of that furniture for all seven, unguarded; isDocked only on windows 1 and 2 (the chat-cache's own DOCKED byte), SELECTED_CHAT_FRAME seated beside DEFAULT_CHAT_FRAME (the window you last picked vs the one FrameXML prints to), and the four per-window fields ChatFrame_OnLoad seats; two more one-name walls fell — a Button's label answers to its second spelling NormalText, which KLHThreatMeter's column headers die without, and ABANDON_QUEST_WITH_ITEMS is declared because a dialog table is read at load whatever the client later shows, leaving Questie with zero errors of ours — and the addon harness gains --probe/--eval, one addon's VM answering a question in ~5 s instead of a 90 s corpus run

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:50:08 +07:00
SamandClaude Fable 5 d4690cc67c a cinematic holds your body, and all seven chat windows exist
- a cinematic holds the body while the camera flies: the streaming focus follows the eye — a Tauren's intro opens 1741 yards from the body — so the body's own ground unloaded under it and it fell (z=59.4 to −62 in four seconds, the server yanking it back); it now takes the same hold a teleport takes, released when the focus comes home to a resident tile, and the loading screen stops covering the fly-by, because "the player's ground is missing" is not a load while the eye is deliberately elsewhere; the letterbox measurement prints as an ordinary info line, and scripts/cine.sh plays any one cinematic and prints what the client did to the world while the camera was away
- a cinematic cuts the zone music: the reference asserts exactly what /console EnableMusic 0 asserts, through the same setter — a cut, not a fade, the music pump dead for the duration, resume at +3.000 s — and benilla asserts the runtime flag, never the CVar, or quitting during a 102-second intro would save your music off for good; ambience is deliberately untouched (it has the analogous flag and the cinematic never writes it); the narration has no category at all — its flags route it around every volume slider but not around the Enable Sound Effects gate, so unchecking that silences it — and the listener follows the cinematic camera for the duration
- addons can walk a UI they did not build: GetChildren/GetNumChildren/GetRegions/GetNumRegions land — the basis of the whole reskin genre, and the wall four of the twenty most-installed 1.12 addons died on: pfUI drew nothing before and now paints 495 quads, ShaguDPS 71 → 566, pfQuest 3 → 498 — with the reference's own quirks: the title region is NOT in the list (its creation path never reaches the region linker), hidden regions are returned and counted, oldest first; the Model widget gets its Lua surface (SetModel, SetSequence and siblings, read out of the shipped image, where SetRotation and SetFacing are two names for one slot); and the Minimap carries the nine engine-created Model children no XML declares, built before the frame descent, the ninth being the player arrow Questie indexes as ({Minimap:GetChildren()})[9]
- all seven chat windows come from one template, tabs and scroll buttons included — whether a window is shown has nothing to do with whether its globals resolve, and pfUI reads every piece of that furniture for all seven, unguarded; isDocked only on windows 1 and 2 (the chat-cache's own DOCKED byte), SELECTED_CHAT_FRAME seated beside DEFAULT_CHAT_FRAME (the window you last picked vs the one FrameXML prints to), and the four per-window fields ChatFrame_OnLoad seats; two more one-name walls fell — a Button's label answers to its second spelling NormalText, which KLHThreatMeter's column headers die without, and ABANDON_QUEST_WITH_ITEMS is declared because a dialog table is read at load whatever the client later shows, leaving Questie with zero errors of ours — and the addon harness gains --probe/--eval, one addon's VM answering a question in ~5 s instead of a 90 s corpus run

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:50:08 +07:00
SamandClaude Fable 5 4ad8718b2a your corpse is your own dead body, and particles take the room's light
- corpses render: a corpse create spawned a real entity, latched its guid for the reclaim send, and drew nothing — one missing EntityKind variant folded it into the catch-all and four downstream lanes then failed by default, not even the debug cube fired; the reference forks once, on the bones flag: a fresh body builds a character composite from the corpse's OWN appearance bytes and 19 item slots through the same compositor a living player is dressed by (its own hide-helm/hide-cloak bits honoured, the ranged slot never dresses, and weapons never dress because the client looks a packed display word up as a guid — we reproduce the outcome, not the dead lookup), while a bone pile takes its race and sex's DeathSkeleton, sixteen of which ship; the pose is one settled arm — Dead, or Drowned when the body lies more than 0.66666669 yd under a liquid surface — and a corpse is not a unit, so clicking one no longer sends a selection for a guid no unit owns
- a lit particle takes its MODEL's committed light node, not the sky's: indoors the reference lights an object's particles from the object's own node — the footprint's baked floor colours on the fixed interior axis plus the room's placed lights — never the day/night sun; a particle quad carries exactly one normal, world up, one constant per draw, so the whole node collapses to three floats, published off the same fold the model's mesh renders from so a mesh and its own particles can never disagree about the room they stand in; in Onyxia's Lair the 78 lit falling-rock emitters on the 26 anchors that classify interior now take the room's light
- and that lit lane is FIXED FUNCTION, not the mesh's soft SH curve: the shipped client null-binds both shader slots for a particle batch and re-zeroes the shader mask at the head of every batch, so a model and its own particles share the same committed light words and legitimately differ in the curve applied to them — the mesh's wrapping lobe against the hardware's max(N·L, 0) is 0.900 vs 0.869 on the key axis, up to the three nearest room lights arrive diffuse-only under GL's own 1/(0.7d + 0.03d²) falloff (a light at or below the emitter cannot reach it at all), and the clamp sits on the product, never the multiplier, because it is GL that clamps the lit vertex colour
- the cinematic letterbox has no aspect test in it: CinematicFrame.lua's if guards a recompute, not whether there are bars — below 4:3 the reference's bar textures simply stand at their declared 1024x128 on the client's native 1024x768 sheet, which is height/6, the same formula evaluated at exactly 4:3 — so one expression covers every window shape, min((h − min(w/2, h))/2, h/6): a 2:1 picture when wide, bars at 4:3 too; and a shown keyboard frame swallows EVERY key, not the ten names the host used to feed it — you could walk around underneath your own intro; the proof is the reference's own handler, which has to RunBinding("SCREENSHOT") by hand to get one key back; ESC still skips, and a W already held when the shot starts keeps running, which is faithful too

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:49:15 +07:00
SamandClaude Fable 5 e9e38bf6ec your corpse is your own dead body, and particles take the room's light
- corpses render: a corpse create spawned a real entity, latched its guid for the reclaim send, and drew nothing — one missing EntityKind variant folded it into the catch-all and four downstream lanes then failed by default, not even the debug cube fired; the reference forks once, on the bones flag: a fresh body builds a character composite from the corpse's OWN appearance bytes and 19 item slots through the same compositor a living player is dressed by (its own hide-helm/hide-cloak bits honoured, the ranged slot never dresses, and weapons never dress because the client looks a packed display word up as a guid — we reproduce the outcome, not the dead lookup), while a bone pile takes its race and sex's DeathSkeleton, sixteen of which ship; the pose is one settled arm — Dead, or Drowned when the body lies more than 0.66666669 yd under a liquid surface — and a corpse is not a unit, so clicking one no longer sends a selection for a guid no unit owns
- a lit particle takes its MODEL's committed light node, not the sky's: indoors the reference lights an object's particles from the object's own node — the footprint's baked floor colours on the fixed interior axis plus the room's placed lights — never the day/night sun; a particle quad carries exactly one normal, world up, one constant per draw, so the whole node collapses to three floats, published off the same fold the model's mesh renders from so a mesh and its own particles can never disagree about the room they stand in; in Onyxia's Lair the 78 lit falling-rock emitters on the 26 anchors that classify interior now take the room's light
- and that lit lane is FIXED FUNCTION, not the mesh's soft SH curve: the shipped client null-binds both shader slots for a particle batch and re-zeroes the shader mask at the head of every batch, so a model and its own particles share the same committed light words and legitimately differ in the curve applied to them — the mesh's wrapping lobe against the hardware's max(N·L, 0) is 0.900 vs 0.869 on the key axis, up to the three nearest room lights arrive diffuse-only under GL's own 1/(0.7d + 0.03d²) falloff (a light at or below the emitter cannot reach it at all), and the clamp sits on the product, never the multiplier, because it is GL that clamps the lit vertex colour
- the cinematic letterbox has no aspect test in it: CinematicFrame.lua's if guards a recompute, not whether there are bars — below 4:3 the reference's bar textures simply stand at their declared 1024x128 on the client's native 1024x768 sheet, which is height/6, the same formula evaluated at exactly 4:3 — so one expression covers every window shape, min((h − min(w/2, h))/2, h/6): a 2:1 picture when wide, bars at 4:3 too; and a shown keyboard frame swallows EVERY key, not the ten names the host used to feed it — you could walk around underneath your own intro; the proof is the reference's own handler, which has to RunBinding("SCREENSHOT") by hand to get one key back; ESC still skips, and a W already held when the shot starts keeps running, which is faithful too

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:49:15 +07:00
SamandClaude Fable 5 c827813351 cinematics play, and a lit particle stops brightening as you turn
- cinematics play: `SMSG_TRIGGER_CINEMATIC` resolves through `CinematicSequences.dbc` → `CinematicCamera.dbc` → a `Cameras\*.m2` whose camera record is an eye path, a look-at path and a roll — cubic tracks with the full step / linear / Bézier / Hermite dispatch the bone loops collapse to step-or-linear (every shipped fly-by is Bézier), planted at the row's origin and facing (`Rz(+facing)`, checked against vmangos's own waypoint table: 8.5 yd mean on the human path, 154 with the other sign); FOV is per shot (the Undead intro authors 90°, the rest 45°), roll is authored around whole turns, the flight ends at the sequence band's end, a trigger arriving before the loading cover lifts is latched and started when it does, ESC skips through `CinematicFrame`'s own `OnKeyDown` (`StopCinematic` has zero native callers), and every path acks — natural end, ESC, or an unresolvable trigger immediately, as before; `.debug play cinematic <id>` replays any of them
- a cinematic hides the HUD — `UIParent:Hide()` reaches nothing here because the HUD frames are not its children, so the ALT-Z draw-level hide is used and the letterbox moves out of FrameXML onto Bevy UI nodes where that hide cannot reach it, recomputed per frame (bars only wider than 4:3, the picture cropped to 2:1) — and the restore only un-hides a UI this module hid
- a lit particle's normal is world up, one constant per draw, not camera-facing: Onyxia's always-on floor haze read about four times brighter from a level camera than from above because the shader dotted a to-eye normal against a fixed light; the reference uploads row 2 of the view matrix — world +Z carried into eye space — on every vertex and carries the light into the same frame, so the rotation cancels; 400 lit emitters (the waterfalls, chimney and campfire smoke, Westfall dust, Dun Morogh snow) stop brightening and dimming as you turn
- the `play` profile drops ThinLTO — rustc #86049 was intermittently poisoning the incremental link with `_anon.<hash>.llvm.<id>` symbols promoted by an earlier compile (48 unresolved across 7 codegen units; 1065 such symbols → 0 after) — and `benilla-formats` stops taking `image` with default features, which had been compiling a complete AV1 encoder into a crate that writes two PNGs: 499 → 466 workspace crates

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 17:24:50 +07:00
SamandClaude Fable 5 d7fb26713c cinematics play, and a lit particle stops brightening as you turn
- cinematics play: `SMSG_TRIGGER_CINEMATIC` resolves through `CinematicSequences.dbc` → `CinematicCamera.dbc` → a `Cameras\*.m2` whose camera record is an eye path, a look-at path and a roll — cubic tracks with the full step / linear / Bézier / Hermite dispatch the bone loops collapse to step-or-linear (every shipped fly-by is Bézier), planted at the row's origin and facing (`Rz(+facing)`, checked against vmangos's own waypoint table: 8.5 yd mean on the human path, 154 with the other sign); FOV is per shot (the Undead intro authors 90°, the rest 45°), roll is authored around whole turns, the flight ends at the sequence band's end, a trigger arriving before the loading cover lifts is latched and started when it does, ESC skips through `CinematicFrame`'s own `OnKeyDown` (`StopCinematic` has zero native callers), and every path acks — natural end, ESC, or an unresolvable trigger immediately, as before; `.debug play cinematic <id>` replays any of them
- a cinematic hides the HUD — `UIParent:Hide()` reaches nothing here because the HUD frames are not its children, so the ALT-Z draw-level hide is used and the letterbox moves out of FrameXML onto Bevy UI nodes where that hide cannot reach it, recomputed per frame (bars only wider than 4:3, the picture cropped to 2:1) — and the restore only un-hides a UI this module hid
- a lit particle's normal is world up, one constant per draw, not camera-facing: Onyxia's always-on floor haze read about four times brighter from a level camera than from above because the shader dotted a to-eye normal against a fixed light; the reference uploads row 2 of the view matrix — world +Z carried into eye space — on every vertex and carries the light into the same frame, so the rotation cancels; 400 lit emitters (the waterfalls, chimney and campfire smoke, Westfall dust, Dun Morogh snow) stop brightening and dimming as you turn
- the `play` profile drops ThinLTO — rustc #86049 was intermittently poisoning the incremental link with `_anon.<hash>.llvm.<id>` symbols promoted by an earlier compile (48 unresolved across 7 codegen units; 1065 such symbols → 0 after) — and `benilla-formats` stops taking `image` with default features, which had been compiling a complete AV1 encoder into a crate that writes two PNGs: 499 → 466 workspace crates

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 17:24:50 +07:00
SamandClaude Fable 5 6710b31a82 scrolling chat brings lines back, and Onyxia's lava traps spurt again
- every chat scroll re-arms the displayed lines' fade — up, down, top, bottom, page up/down, and a no-op scroll at the bottom too, which is what clicking the arrows on an already-at-bottom chat does; a line at alpha 0 was unreachable for the rest of the session (play two minutes and the chat was empty and stayed empty), because "scrolling off the bottom freezes all fade countdowns; nothing un-fades" had been read as a law about the class when it was a finding about one function's gate; the displayed set only, never the whole ring, with the frame's current defaults, as the reference's re-arm writes them; there is no hover reveal — the scroll re-arm is the whole mechanism; and the fade ramp reads the un-rounded countdown, not the stored f32
- the GameObject animation arm rolls a variation: Onyxia's 104 lava traps and 26 falling rocks author Stand twice — a 29491 : 3276 chain whose second take is the `LAVALUMP2` ember burst — and the arm is issued with `variationIdx = −1`, a weighted random walk of the alias chain, where we resolved the chain head every time (215 emitters, every one at live = 0); and a resting substate re-arms itself through the model's completion callback, so the roll is re-taken every window for ever (~867 ms for the traps) — 45 emitters firing and 1299 live particles at +25 s, a standing field rather than a one-time flurry; 72 of 1582 GameObject models author such a chain (the fish schools, the AQ fireflies, the plague cauldrons, lanterns, mugs) and every one was playing take 1 of N; `benilla-extract goanimscan` censuses it

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 17:23:44 +07:00
SamandClaude Fable 5 27bd2670aa scrolling chat brings lines back, and Onyxia's lava traps spurt again
- every chat scroll re-arms the displayed lines' fade — up, down, top, bottom, page up/down, and a no-op scroll at the bottom too, which is what clicking the arrows on an already-at-bottom chat does; a line at alpha 0 was unreachable for the rest of the session (play two minutes and the chat was empty and stayed empty), because "scrolling off the bottom freezes all fade countdowns; nothing un-fades" had been read as a law about the class when it was a finding about one function's gate; the displayed set only, never the whole ring, with the frame's current defaults, as the reference's re-arm writes them; there is no hover reveal — the scroll re-arm is the whole mechanism; and the fade ramp reads the un-rounded countdown, not the stored f32
- the GameObject animation arm rolls a variation: Onyxia's 104 lava traps and 26 falling rocks author Stand twice — a 29491 : 3276 chain whose second take is the `LAVALUMP2` ember burst — and the arm is issued with `variationIdx = −1`, a weighted random walk of the alias chain, where we resolved the chain head every time (215 emitters, every one at live = 0); and a resting substate re-arms itself through the model's completion callback, so the roll is re-taken every window for ever (~867 ms for the traps) — 45 emitters firing and 1299 live particles at +25 s, a standing field rather than a one-time flurry; 72 of 1582 GameObject models author such a chain (the fish schools, the AQ fireflies, the plague cauldrons, lanterns, mugs) and every one was playing take 1 of N; `benilla-extract goanimscan` censuses it

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 17:23:44 +07:00
SamandClaude Fable 5 55b40b5257 names persist, the GM notice is a toast, and the target reads Dead
- the name cache persists to `benilla-config/cache/<realm>.tsv`, because the reference's `namecache.wdb` / `petnamecache.wdb` do and that is why it never sees the races we lost every time — unstabling a pet raised `Usage: SetText("text")` off an unresolved `UnitName("pet")`; the header carries magic, format, build, locale and realm and any mismatch discards the file (no checksum, no TTL: eviction is explicit only, via `SMSG_INVALIDATE_PLAYER`, decoded now and never sent by vmangos), negatives persist too, written on a 10 s debounce and at exit; a stable list also seeds the pet-name cache from the names it already carries
- `SMSG_NOTIFICATION` is the red UIErrorsFrame toast, not a chat line — `.gm on` printed twice because vmangos sends both a system message and a notification and both were rendered as chat; the stand-in dated from before the errors frame existed; `SMSG_AREA_TRIGGER_MESSAGE` moves with it on the yellow arm (its handler ends `xor edx,edx` — flag 0 — where the notification's is flag 1)
- the target frame's dead text is the GlobalString `DEAD` = "Dead" resolved through `text=` like the target-of-target frame, not the key typed into `SetText` in caps, and it takes `UnitIsConnected` so a disconnected player does not read as dead; a shipped-script sweep now refuses any key-shaped literal handed to a text sink; and the Help window is one click deep — Home's button opens the ticket text box, Auto-Unstuck sits beside it, and a ticket files as category 0, which a GM's queue names "Unknown" (a guard added days earlier refused 0 on reasoning from the enum rather than from the function that reads it)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 17:23:05 +07:00
SamandClaude Fable 5 164b0a7a44 names persist, the GM notice is a toast, and the target reads Dead
- the name cache persists to `benilla-config/cache/<realm>.tsv`, because the reference's `namecache.wdb` / `petnamecache.wdb` do and that is why it never sees the races we lost every time — unstabling a pet raised `Usage: SetText("text")` off an unresolved `UnitName("pet")`; the header carries magic, format, build, locale and realm and any mismatch discards the file (no checksum, no TTL: eviction is explicit only, via `SMSG_INVALIDATE_PLAYER`, decoded now and never sent by vmangos), negatives persist too, written on a 10 s debounce and at exit; a stable list also seeds the pet-name cache from the names it already carries
- `SMSG_NOTIFICATION` is the red UIErrorsFrame toast, not a chat line — `.gm on` printed twice because vmangos sends both a system message and a notification and both were rendered as chat; the stand-in dated from before the errors frame existed; `SMSG_AREA_TRIGGER_MESSAGE` moves with it on the yellow arm (its handler ends `xor edx,edx` — flag 0 — where the notification's is flag 1)
- the target frame's dead text is the GlobalString `DEAD` = "Dead" resolved through `text=` like the target-of-target frame, not the key typed into `SetText` in caps, and it takes `UnitIsConnected` so a disconnected player does not read as dead; a shipped-script sweep now refuses any key-shaped literal handed to a text sink; and the Help window is one click deep — Home's button opens the ticket text box, Auto-Unstuck sits beside it, and a ticket files as category 0, which a GM's queue names "Unknown" (a guard added days earlier refused 0 on reasoning from the enum rather than from the function that reads it)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 17:23:05 +07:00
SamandClaude Fable 5 0cd0a28942 edit boxes take focus the way 1.12 does, and the stable shows its pets
- the caret was a wedge in the line: it sat in flow, four units wide, so focusing a login box shoved its text right by one caret and a caret parked mid-word opened a gap; it is a zero-width seam now with the bar drawn as an overlay (a 0×26 seam and a 5×26 bar at the same x, off a node probe), the way the reference anchors its caret quad over the FontString; a blurred box collapses its selection so only the box holding the keyboard shows one; and a dialog answers only keys pressed while it was already on screen — ENTER on an empty form used to open the error dialog and close it in the same frame
- `autoFocus` defaults ON in the reference (the ctor leaves flags = 1, and every one of the ten `autoFocus` attributes in the shipped chain is an opt-out), showing an autoFocus box focuses it after its `OnShow`, and hiding the focused box releases the keyboard — the "never focuses on show" note we had transcribed came from a `call`-only census that missed a tail `jmp`; the loader's flag loop could only ever turn a flag on, so `autoFocus="false"` was a no-op, fixed before the default flipped or it would have grabbed exactly the ten boxes the reference exempts; and the EditBox does fire generic `OnChar` with the spliced string, on every typed or pasted character and never on `SetText`
- the stable master's portrait was a black disc (its session was missing from the `"npc"` token chain — the third window to ship that way, so the test is now exhaustive over every `impl NpcSession` and names the one you forgot) and its model pane draws the selected pet: a stabled pet is a database row with no world entity, so a `PortraitStandIn(display_id)` — an entity that exists only to be mirrored — feeds the existing booth code unchanged; plus the registrar's two service rows were hidden by an inherited attribute, a charter's tooltip carries the guild name and master on line 3, and the Help window drops three strings pointing at a company that stopped answering

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 17:22:27 +07:00