c573ecc5d0fd52c7dcefa426678658bb9624ebd9
27
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7a1d799cf7 |
Comments and strings: the passes' leftovers, and a region's template matched as one name (#391)
The comment and string passes' last leftovers, and one lookup made to match the reference. **Comments that stated a wrong reference fact**, now stating the right one: - A cast failure clears the GCD, and a failed cooldown-on-event spell also drops its parked record (`0x6e73cc`). - `SetUnit` is `0x505d70`; `0x476cb0` is the component clone it reaches. - Interior sound follows the character, where the listener sits, not the camera. - Template inheritance re-runs `LoadXML` on the one object against each template (`0x76985c`), and reads `name` and `id` from the instance alone (`0x769770`). - On the ghost-to-alive edge the reference clears its corpse cache and sends nothing (`0x5ee990`, `0x491f50`). - The name cache has no high-bit miss. An empty-name answer evicts the record in the reference (`0x55f6f0`). - The combat-log deferred queue (`0xc4e208`) waits only on an item name. A unit name never waits. - A flying spline's segments are paced by curve length (`0x454320`, `0x453760`), not by chord length. - A stop within 3 yd of its stop point keeps the arrow nocked (`0x6019e4`). - A missile's sound id is at `CMissile+0x40`. - A nil or unresolved `AssistUnit` token is a gap against `0xb8` ERR_GENERIC_NO_TARGET, not a deviation. **Strings:** - Test, log and tool messages lose leftover history wording and placeholder labels. - The extractor's scan summaries describe the current tint and sequence-clock paths. - The smoke script's failure text carries no private numbers. **Line continuations:** twelve log, panic and assert strings get back the `\` continuations they had lost. Each printed a run of spaces mid-message. Fixes #106. **One lookup:** a region's `inherits=` is looked up as one verbatim name, matched case-insensitively (`0x6ee6f0`, `_strnicmp` at `0x6ee747`), as `framexml::expand` already did for frames. The gate before it split on commas and matched case-sensitively, so a texture or font string whose `inherits=` spelled its template in another case never received the template. `a_region_takes_a_mis_cased_template` fails before this change and passes after. **Tests:** the quest tests stop loading three FrameXML files that no quest file uses. **In game:** nothing changes for the stock UI. Log lines read as one sentence, and an addon texture that names its template in another case now gets it. Verified with `scripts/check.sh` on the changed crates, plus the loader and quest tests with the install present. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
4dc33b4afd |
Comments in benilla-protocol: the second read, citations on the pinned revision and restored facts (#375)
The second read of `benilla-protocol`'s comments, the last step #41 asked for: that crate's comments were rewritten to the rule before second readers existed. Four readers compared every file's comments against their state before the rewrite and against the code as it is now. Fixes #41. - **Citations.** vmangos line citations are re-pointed to the pinned revision: off-by-a-few lines (`Pet.cpp`, `Guild.cpp`, `Petition.cpp`, `LootHandler.cpp`, `ChatHandler.cpp`), and paths the rewrite dropped where a bare file name is ambiguous (`Server/Packets/Spell.cpp` against `Spells/Spell.cpp`, the two `Item.cpp`, `Group.cpp` and `Chat.cpp`). The gossip-hello citation is `NPCHandler.cpp:347`. - **Facts restored.** The move-time-skipped chain (`0x603b40` → `0x601560` → `0x61ab90`), the addon-message TAB surviving vmangos's chat sanitizer, the zone-under-attack sender, `SMSG_ADDON_INFO` answering the auth session's addon block, the officer-note permission gate, and `SMSG_ITEM_COOLDOWN` on equipping an item with an on-use spell (the old text said a proc). - **Deviation labels.** A label now carries its reason, or goes when the behaviour is only unbuilt (the survey window, the group XP bonus, the NPC-text language garble). - **Two examples.** `park_probe` and `login_refusal_probe` took a hard-coded local account; they now read `WOW_USER` and `WOW_PASS`. - **Verification.** Every file strips to identical code apart from the two examples; `cargo fmt --check` is clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
0fe1ea87b9 |
Tests, examples and run lines use placeholder credentials (#332)
Test fixtures pass "player"/"secret"; the run lines in `.cargo/config.toml`, the protocol examples and the auth and world binaries show placeholders; the SRP probe example takes --user and --pass instead of defaulting them. Built on macOS. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
715d6d91c9 |
String literals in benilla-assets, -blp, -protocol and -srp carry no record numbers (#291)
Part of #92, for the crates whose comment pass has landed and whose literals are few: `benilla-assets`, `benilla-blp`, `benilla-protocol` and `benilla-srp`. - Assert messages, printed labels and a probe's error text drop their private record and bug numbers and the workspace's name for the maintainer. Each keeps its fact in its own words. One assert names a client function by its address, `0x68a830`, instead of a disassembler's label for it. - Two terrain tests named after a bug number are renamed for what they check: `the_pin_stands_one_chunk_west_of_an_impassable_band` in `benilla-formats`, and `a_body_walking_east_from_the_pin_is_stopped_at_the_wall` in `benilla-world`. Those two crates' other literals follow in their own pass. - Every change is to string-literal text, proven with comments stripped and every literal masked, except the two renamed tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
f48f32e741 |
vmangos citations read in one revision, vmangos core 6b34a36 (#289)
Fixes #32. Every vmangos `File.cpp:N` citation in the crates whose comments have been rewritten now reads in one revision, vmangos core `6b34a36`, the one the comments are written against, a few commits from the server benilla is run against. Some citations had been taken from a newer vmangos checkout, where the lines have moved. #32's own targets came from that newer checkout too, so where this PR and #32 differ, this PR follows `6b34a36`. - 53 citations in 31 files are re-pointed: most in benilla-protocol, a few in benilla-ui and benilla-app. Each new line was read and is the construct its comment names. - #32's cases: - The root-ack kick is `MovementHandler.cpp:715-722`; three comments cited the falling deferral after it. - Movement is discarded before `CMSG_SET_ACTIVE_MOVER` lands at `:291-293`, through `Player::GetConfirmedMover`. - The released mover's parting stop is `:955-964`. - The mount-special echo is sent with `self` false at `:972`. Whether the sender gets its own echo depends on the server's broadcaster (`Object.cpp:2273-2280`); that is stated once, where the packet is parsed, and the opcode comment points there. - Ten more citations were a line or two off in every revision. One comment also stated a wrong fact: vmangos's ignoring-you party refusal names the invitee, and the empty name is the raid-convert confirmation's. - Comment text only. Every file strips to identical code, and `cargo fmt --check` is clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
3b3b185bab |
tests: 24 protocol tests check what their names claim
Thirteen now assert what they claimed: - every bare trade status, and the 444-byte extended snapshot for empty, full and mixed slots; - the status-1 and status-3 battlefield tails; - every quest-share verdict byte through encode, parse and decode; - the greeting pick itself, and the whole compressed-move batch. Eleven are renamed to what they check, where the claim had nothing to check against: no capture bytes, no production opcode list, no server encoder. No strengthened assertion failed. Probe and assert messages drop their record numbers. The aura probe's refusal message no longer claims the level-6 probe accounts need a grant for a level-4 command. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
8d9f007415 |
comments: protocol, the auth code, the live probes and their examples
1,071 comment lines become 573; the code is unchanged. The world probe's field docs are its --help text, so the help is shorter now and matches what each flag runs: --quest takes quest 783 from Willem to McBride, --quest-item needs the starter item to survive, --giverstatus queries once, --spells makes three casts. Also corrected: - the aura-duration packet has a SessionEvent; - the SRP xor hash is SHA1(N) XOR SHA1(g), not a hash of it; - the cooldown probe picks Tri, else the first character. benilla sending a stored integrity hash instead of hashing the install is now marked as a deviation. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
3fccac9398 |
comments: protocol, the quest, spell, vendor and world-state probes
404 comment lines become 168; the code is unchanged. Two claims were wrong and are corrected: the world-state probe needs no temporary GM grant, since the probe accounts carry level 6 and the command needs 5. The root-ack citation now points at the current vmangos lines. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
b05d5d18d5 |
a fresh clone gets the whole project, and the maintainer's tooling moves out
What a clone lacked: the gates went green with a thousand skipped tests and said nothing, every login script assumed a worktree-pool slot and its probe account, the client defaulted to the maintainer's account, a docker path and a home directory were in the code, the temp files broke on GNU coreutils, and two generators could only run against a sibling repo. Now the test gates print how many data-gated tests skipped and why; a scripted run logs in as the account its checkout declares in `.probe-identity` or as the three variables name, never a default, and the client refuses a scripted login on any other account; the event and message tables regenerate from vendored inputs, byte for byte; the docs state the real prerequisites. Out of the tree, into the maintainer's own tooling: the worktree pool, its hooks, the perf leg runner, the cross-platform compile and the Vulkan container leg — a way of working, and setups not every machine has. One hook stays: no writes into the install. Two tests that could never run are gone with their subject: the quest-timer geometry diff compared a copy of the window that no longer exists (and joined a chain path onto a folder, so it always skipped), and the FrameXML flag gate now reads the stock files off the player's own patch chain instead of an extracted folder only one machine had. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
3e12fa1d7f |
benilla is one repo: the working tree moves in, restructured
Until now this repo was an export of a private tree, pushed as squashed snapshots. From here on it is the working tree itself, and the private repo is archived. The root holds AGENTS.md and the Rust and GitHub files only. Everything an agent needs to orient is in docs/: METHOD.md (the rules and how the project is built, 123 lines, project method only), MAP.md (generated from the code at every land) and CONTRIBUTING.md (what gets in and how a change is judged). One skill, `probe`, holds the procedure for running the client unattended against the local server. Brought over because the project needs it: reference/ (the 1.12 name catalogues 34 source files read; the export failed its own tests without them), the gate chain and the pool (scripts/), the hooks that enforce the install rule and the worktree rule, the full .gitignore, the `cargo play` alias, and the original wording of eleven source files the export had reworded. Left out: the decision records (archived with the private repo), the export tooling, the operator's Discord and triage loop, the lab-laptop scripts and the instruments nothing references, which live outside the repo now. What read the records is gone from the tooling: no corpus lint, no records index, no `reserve` verb, no number-collision check. Three hooks are gone: fmt-at-commit and the file-size nudge were redundant with the gates, and the assets/ui guard is now a test (`assets_ui_does_not_grow`) that fails for everyone, not only for a Claude Code session. The remaining pool hooks stand down on a machine without a pool. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
eacf343a55 |
the player picks the realm, and a harmful spell flinches its victim
- the player picks the realm: benilla connected to whichever realm the auth server listed first — not a policy but the absence of one — with the reference's Change Realm button drawn permanently disabled and the character screen's banner reading "Connecting…", a string 1.12 does not have, invented because the realm only ever arrived as a passenger on the character list (`CharacterSelect.lua` l.66 ends that ladder with `CharSelectRealmName:Hide()` — no name, no banner); `read_realm_list` had read the flags byte, the category byte and the realm id into `_` locals and stringified the population on arrival, and all four are load-bearing; the IO thread gains a third park — logon → which realm? → world handshake → which character — publishing `SessionEvent::RealmList` and blocking on `RealmRequest::{Enter, Refresh, Abandon}` exactly as at the other two, `Enter` carrying the realm's name and not its index since the list is re-requested every 5 s (`RealmList.lua`'s `REALM_LIST_REFRESH_TIME`) off a realmd socket `Logon` now keeps; Change Realm hands the logon forward (`HeldLogon`) so a different realm costs a world dial, not a re-authentication, and a deliberate ask arms `Realms::forced` for exactly one list, because the realm remembered from last time is precisely the realm you are standing on and asking to leave — without it the button re-entered the same realm and appeared to do nothing; the auto-answer ladder is a pure `auto_answer` — `WOW_REALM`, then the remembered `realmName` CVar (which `set_realm_name` had poked into its slot in place, never reaching `cvar_changes`, so the persisted CVar whose whole job is "the last realm connected to" was absent from disk forever; it writes through `set_from_engine` now), then an unattended run takes the first realm that is up, every arm requiring the realm to be up — and the screen is `RealmList.xml` rebuilt on the same 640×512 HelpFrame plate the AddOns list uses, eighteen rows at the authored 20 px pitch, four sortable columns; the Population word is a BAND, not the float: `realm_load_stats` (0x46e510) takes the mean and `sqrt(Σ(pop − mean)² / (n − 1)) · 0.6`, `realm_load_classify` (0x46ec60) reads the sentinels first (0x20 → Recommended, 0x40 → New, 0x80 → Full) then strict `pop < mean − stddev` → Low, `pop > mean + stddev` → High, else Medium — so the same realm reads High on a quiet list and Medium on a busy one, and a lone realm is banded against the `n ≤ 1` short circuit's literal 1.0, so a local vmangos realm at 0.06 reads Low, not Medium; then wow-re's cross-check of `RealmList.cpp [0x46e3bf, 0x46fa05)` confirmed 0x01 = invalid / 0x02 = offline and that the real client does auto-connect by `realmName` without raising the list (`0x5ab800` resolves the stored name against the fresh list, its success leg calling no `SetStatus`), and corrected four things the first cut shipped: the statistics are GLOBAL — `0x46e510` walks the flat all-categories array unconditionally (`0x46e5ee fadd [esi+0x12c]`), the per-category bucketing in the same loop a side effect and never a bound, so scoping them to the tab made the same realm read a different word per tab; Recommended/New/Full never travel as flag bits at all — the parser recognises magic populations by bit pattern and rewrites them, 600.0 → 0.0 + 0x20, 200.0 → 0.001 + 0x40, 400.0 → 8.0 + 0x80, and it has to happen in `read_realm_list` before anything averages, since one realm advertised Recommended arriving as 600.0 drags the global mean past every other row; pvp/rp is a `Cfg_Configs.dbc` join on the type dword at `[realm+0x04]`, and the shipped rows make types 3 and 5 PvP and 7 RP where the 1/6/8 match had all three Normal; and the default sort is `[characters, load, name, mode]`, all ascending, written once per process by `0x46e300` — a four-key fallthrough where "ascending" characters means higher count first, a column click being move-to-front (`0x46e9b0`) that keeps the column's direction unless it was already in front — with the reference's non-antisymmetric characters comparator (its tie-breaking `strcmp` result clobbered one instruction later at `0x46e846`) deliberately not reproduced; `RealmWizard`, the `REALM_IS_FULL` confirm and the category tab strip are deliberately not built, each for a stated reason (2056, 2062)
- a re-played effect REPLACES the same record at the same attach point: the director's report that the reference draws far fewer impact effects when many mobs beat on one warrior — and named the star burst on both hands on every crit — is not a limiter but a replace, recorded in wow-re for weeks: `CEffect::AddEffect` (0x61fdd0), the path every kit slot and the melee blood spurt take, opens with `0x6208e0(owner, rec, tag)`, which walks the owner's `+0xb4` list and destroys every node carrying the same `SpellVisualEffectName` record at the same attach tag (bounded `[0x6208e0, 0x62092c]`), skipping `tag == -1`; benilla replaced only persistent instances, on the narrower `(spell_id, class)` key of the spell-id reap `0x614150`, and pushed every one-shot unconditionally — a gap seen and scoped to one caller four times (0850, 0927, 1149, 1518) without being generalised — so five attackers landing swings on one body held five blood spurts at `(UnitBlood, 0xf)` where the reference holds one, linear in the attacker count on additively-blended emitters where the reference's is flat, and near-identical in a 1v1, which is why it survived; the sharpest case is Flurry (12966–12970), applied on every critical strike, whose visual 2759 hangs record 416 — `Spells\Strike_Impact_Chest.mdx`, the gold chest-impact flare reused at the hand tags 0x15/0x16 — on both hands via cast kit 3010 over a persistent `BloodLust_State_Hand` glow from state kit 3009, and every crit stacked another pair; `spell_fx::replace_same_slot` now runs per slot at every Begin for persistent and self-terminating instances alike, keyed on the RECORD id threaded through the fx lane (`FxSlot { tag, effect, path }`, the hardcoded map becoming name → (id, path)) and not the path, because two rows may name one .mdx and the reference lets those coexist, with both exclusions kept — the world plant and an already-decaying node — and four tests pinning the key's two halves and both; the other half of the report, that the burst is too large and too long, is a separate mechanism and is not touched — and the instrument that would have measured it was lying: `m2part` read a burst emitter's `peak_rate()` as its particle count, while the reference fires on the rising edge of `enabled != 0 && rate > 0` sampled on one clock, and `Strike_Impact_Chest.m2`'s gold flare steps its gate 1 → 0 on the very keyframe its rate steps 0 → 50, so the two are never true together and that emitter emits NOTHING; the tool printed "burst of ~50 particles, life 0.50s" and a diagnosis started 2.7× over on count and 1.7× over on duration off that one line; `EmitTiming::first_burst` walks the slot's keyed span at 60 Hz and returns the first instant the predicate holds or `None`, the dump prints the real count and time or `NEVER FIRES`, and a real-asset test pins that model's emitter #0 as never and #1 as exactly 30 (2057)
- a harmful spell's impact flinches its victim: benilla had one spell-side wound path, `play_kit`'s `[8,10]` branch — the kit player's own `0x60f3ad` test — carrying the KIT's anim id, so a spell whose impact kit names no wound anim, which is nearly every one (Fireball, Frostbolt, Shadow Bolt, Sunder Armor, a mob's Strike), twitched nothing, and a kit naming CombatCritical played 10 where the client plays 9 or 8; `0x60ea70` has six callers, not the three an earlier sweep found (`grep "call 0x60ea70"` over the full objdump was the one command it never ran): the missile hand-off `0x61dc50` lays the flinch before the impact kit for every living target (`0x61dc74`, HEALTH > 0; the already-dead arm `0x60ea30` plays Death/SwimDeath instead), the instant-hit ImpactKit loop `0x6e8bf0` lays it after the kit when `0x6ea280(SpellRec) == 2` (`0x6e8c89`), the reflect kit `0x6e8cb0` does the same on the caster, and every spell-side call pushes severity 0, so the id is always `0x60ea70`'s own `(severity, engaged)` pick — CombatWound(9) engaged, StandWound(8) otherwise — never the kit column; `0x6ea280` is the spell's hostility class and `== 2` ⇔ the spell targets enemies: Targets bit `0x100` → never, `0x80` → yes, else `EffectImplicitTargetA[i]` then `B[i]` for effects 0..3 indexed into the byte tables `0x6ea338`/`0x6ea378`, whose case-0 ids `{2, 6, 15, 16, 24, 28, 53, 54}` are exactly the modern enum's `*_ENEMY` set; so `WoundAnim { entity }` loses its id and the driver resolves every spell edge as `wound_anim(0, engaged)`, `play_impact` takes an `ImpactPlay` with `flinch` — the instant hits pass `SpellDisplay::is_harmful()`, a missile arrival passes true — `SpellDisplay` gains `effect_implicit_target_a/b` (columns 82–84 / 85–87) and `is_harmful()`, and the flinch is laid as the same secondary-blend overlay as a melee wound, over a cast, a swing or a run, never replacing them; pinned against the real 5875 `Spell.dbc` — Fireball, Charge and Sunder Armor harmful through A, Frost Nova through B alone, Renew, Healing Touch, Arcane Intellect and Battle Shout not — and the driver never picks CombatCritical for a spell edge; `WOW_MOVE_TRACE` gains `fct wound trigger/evict/expire` lines, since the whole flinch lifecycle was invisible in a trace; and the report this was chased under — the warrior's swings reading as cut short by incoming impacts in a many-mob brawl — stays open, with one reference gate found on the way that benilla does not model: `0x60ea70` aborts while any attached CEffect on the victim carries `+0x2c & 0x4` (`0x60eab6`), and which kit-spawned nodes carry that bit is the question dispatched to wow-re (2058)
- other players' roots, levitation and blinks now relay to onlookers: a report that a nearby player getting rooted, water-walking or Blinking slid or stood in the wrong place until their next movement packet was exactly right — a movement mode has three legs and benilla carried two, the mover's own acked handshake (`SMSG_FORCE_MOVE_ROOT → CMSG_FORCE_MOVE_ROOT_ACK`, 0866) and the server-driven `SMSG_SPLINE_MOVE_*` twelve (1780), while the third, what observers are told once the mover acks (vmangos `MovementPacketSender.h:30-60`, broadcast by `SendMovementFlagChangeToObservers` / `SendTeleportToObservers`), fell through `parse_server` to `ServerPacket::Other` and was dropped — so a rooted player kept sliding for the ~500 ms to their next heartbeat, a Levitate on someone else was never rendered, and a Blink left them standing at the old spot; six opcodes — `MSG_MOVE_ROOT`/`UNROOT` (236/237), `MSG_MOVE_HOVER` (247), `MSG_MOVE_FEATHER_FALL` (688), `MSG_MOVE_WATER_WALK` (689), `MSG_MOVE_TELEPORT` (197) — all the ordinary `[packed guid][MovementInfo]` shape and all sharing the client's one relay handler `0x603bb0` with the 22 already carried (wow-re's opcode-handler table has thirty rows on it; the remaining two are cheat opcodes vmangos never constructs), so admitting them to `is_movement_relay` is the whole fix for five: apply/unapply rides the flags word rather than the opcode, since vmangos runs `SetHoverReal` &co. before the broadcast and the word on the wire already carries the bit's new state, and `apply_move` already commits it wholesale into the union the ground resolve reads for hover's offset and the water-walk plane — the pose the packet carries beside the mode is half the value, a rooted player's stopping place being news too, and the root arrives already post-wipe with `MOVEFLAG_ROOT` forced back on, so no wipe of ours is invented; the teleport is a relay in shape and a discontinuity in meaning, so `RelayMove::reconciles()` excludes it from the pre-fire reconcile beside the heartbeat, for the opposite reason — the heartbeat because the dead-reckon has already converged, the teleport because there is nothing to converge to, and lerping toward a Blink's destination would drag the mover across the twenty yards the Blink exists to skip, sweeping the shared capsule through every wall between — while it still rides the unit's relay chain rather than snapping at arrival, because replay is paced against the sender's own stamps and applying it early would put the player at the destination while their walk to the launch point was still replaying; a `tp` tag joins `hb`/`tr` on the `rly` trace line; and parse and decode of all six are pinned against golden bodies asserted four bytes shorter than the counter-carrying `MSG_MOVE_TELEPORT_ACK` form, so a counter is provably not being read (2061)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
41dc45a9a0 |
swing refusals latch and repeat, and the trade arc stops being silent
- a refused auto-attack is no longer silent, and the display is a latch, not a toast: six opcodes, four arms — `0x145 SMSG_ATTACKSWING_NOTINRANGE` and `0x146 SMSG_ATTACKSWING_BADFACING` latch a code through `0x5ecdb0`, which writes `[0xc4d758]` and `[0xc4c270]` together; `0x148 DEADTARGET` and `0x149 CANT_ATTACK` share arm `0x625ab8` verbatim (StopAttack, no message), so the client cannot tell them apart and `AttackSwingError` collapses them into one `DeadOrUnattackable`; `0x147 NOTSTANDING` is never registered by the reference and `SendAttackSwingNotStanding` has zero callers in vmangos, so a test pins that it stays an unknown opcode; and the fourth arm hides: `0x14e SMSG_CANCEL_COMBAT` is registered at `0x5e3303` by the SPELL TU, not the combat one, so the eight-opcode census every note quotes did not contain it, and its handler `0x5e7dd0` is arm 4's body instruction for instruction; vmangos sends each refusal on the edge alone, so the repeat the player sees is the client's own: the tick `0x5ec950` — `CGPlayer_C` vtable `0x80af78` slot 14, dispatched once per object per rendered frame on the `CWorldFrame::OnPaint` chain, so a Bevy `Update` system is the exact cadence — prints `ERR_BADATTACKPOS`/`ERR_BADATTACKFACING` (catalog rows `0xd8`/`0xd7`, both red) at once and again every 4000 ms while an attack target is set, and a landed swing clears it: `SMSG_ATTACKERSTATEUPDATE`'s arm calls `0x5ea800`, whose first act is `0x5ecdb0(0)`, gated on the attacker being the active player AND the victim resolving (both gates kept — wow-re's round had glossed the second away, and sending that back corrected the note); the latch is one `Option<(Latched, Duration)>` because `[0xc4c270]` has no meaning while `[0xc4d758]` is 0, a second refusal packet defeats the cooldown and the re-arm precedes the print (both tested), the clear also runs once per world entry (`0x5e2510`), and both edges ride ONE `SwingRefusalEdge` stream so a refusal and a landing in the same frame stay in packet order; `SMSG_FEIGN_DEATH_RESISTED` (`0x2b4`) arrives beside them and is built differently because its handler `0x6e9800` is two instructions — a bare `DisplayError(421)` = `ERR_FEIGN_DEATH_RESISTED`, the single word "Resisted" — so it takes the plain `UiErrorKeys` route with no latch; the `--attack` world probe now reports the refusals it tallies; and two remainders are named, not built: `0x5ea800`'s post-swing leash — `R = 4.0 * max(5.0, reach(victim) + reach(self) + 1.3333333730697632)`, floor 20.0, sending a bare `CMSG_ATTACKSTOP` past it — a new packet-sending behaviour left for the director's call, and `StopAttack 0x5ecac0`'s two one-shot tutorials (10 low-health, 11 low-mana), which benilla's copy has never fired (2037)
- the trade arc stops being silent: eighteen of the twenty-three `SMSG_TRADE_STATUS` arms print where benilla printed none, all through `UiErrorKeys` so the text is the player's own `GlobalStrings.lua` and the surface is the catalog record's `+0x04`; the four-row start was the trade block's own emit-site census, which answered "which arms raise a TRADE-keyed message" — eleven of the ids come from elsewhere in the 465-row table (`ERR_PLAYER_BUSY_S` `0x3e`, `ERR_PLAYER_DEAD` `0x7e`, `ERR_GENERIC_NO_TARGET` `0xb8`, `ERR_PLAYER_WRONG_FACTION` `0xff`, `ERR_IGNORING_YOU_S` `0x13d`, the stunned pair `0x191`/`0x192`, the logout pair `0x19d`/`0x19e`, `ERR_RESTRICTED_ACCOUNT` `0x1be`, and `ERR_TRADE_WRONG_REALM` `0x1ca`, whose text is the ONLY_CONJURED sentence), and the surfaces do not follow the English — a successful trade's two lines are the yellow ones, `ERR_PLAYER_BUSY_S`/`ERR_IGNORING_YOU_S` are chat lines, the refusals red; `TRADE_REQUEST_CANCEL` fires from the top of the CANCELED arm (`0x4bf832`, the dispatcher's one `SignalEvent`) where nothing had fired it; `ERR_INITIATE_TRADE_S` prints — it is client-emitted from inside the `CMSG_INITIATE_TRADE` sender, at once on a name-cache hit (`0x5d4042`) and from the query callback on a miss (`0x5d40ca`), so clicking Trade had produced no text in either direction; the three arms that name a player (0/5/14) read the network handler's guid cell `[0xc4bed0]`/`[0xc4bed4]` behind the outbound-initiate latch `[0xc4bec8]` — `InitiateTrade 0x5d3fb0` writes the target's guid and then the latch, the `BEGIN_TRADE` arm writes the initiator's and no latch — which is what keeps a client that just ignored a trade from printing "X is ignoring you." to itself, and behind `0x468460(guid, TYPEMASK_PLAYER)`, a live-object test the initiate line does not share, so `NamedLine::needs_live_object` drops a status line the moment its player stops being streamed while an initiate line waits; a network-cell clear is not a window close: `0x4bf4e0` has four call sites in the dispatcher — one opens, `{3, 8, 12}` close — and the other fourteen run the handler's tail `0x5d490a`, which drops the latch and the pending guid and leaves the window standing, so `close()` splits into `close_window()` and `clear_pending()`; `REJECTED` drops only the partner's accept, `BACK_TO_TRADE` reaches `0x4bf230(0)` and sends `CMSG_UNACCEPT_TRADE` on a real change, `ONLY_CONJURED` bounces the offending slot out of our own offer; `CLOSE_WINDOW`'s message is deliberately not built (`0x4bfd70` maps `result` over a 61-id range with a flagged self/other polarity inconsistency, and vmangos never sends status 12); and `ERR_TRADE_BLOCKED_S`'s transcribed English literal is deleted along with `ChatLog::pending_texts` (2042)
- both halves of B373's lighting closed: `MeshTag` bits 6..=13 mean two things — the exterior payload's ground-shade byte and the low eight bits of the interior payload's 13-bit probe slot — and the overlap cannot be removed (the four mode-independent fields cost 19 bits, leaving 13 for a payload region the two modes want 21 of), so the payload question becomes a witness the accessors demand: `mesh_tag::ExteriorPayload` is a zero-sized proof only `exterior_payload(on_bake_law, own_probe)` mints, when both probe-slot populations answer no, and `with_shade`/`shade_of` take it, so no expression in the crate reaches those bits without having asked; `describe` alone reads the raw masks and prints both readings (2041); and a transport's deck cargo is lit by its own doodad def, not by the boat (B373): a transport's WMO is linked into the same global `TSExplicitList<CMapObjDef>` at `0xca7d98` the ordinary scene walk iterates, so its MODDs are ordinary doodad defs; `[def+0xa4]` has exactly four writers image-wide — the ctor `0x6a7d73` (0.0), the doodad-set commit `0x695bb3` (1.0), the ADT MDDF commit `0x6b01bb` (1.0) and the MCSH refresh `0x698cb4` (0.5) — and none is the light node, whose 2.5-producer and ramp are reachable only through `[obj+0xe0]`, a path a doodad def never takes; the MCSH sample is one-shot (`0x698c50` unlinks each entry) and its null-tile legs return lit; so we had the wrong source, the wrong cadence and the wrong mechanism: `entity_shade::DoodadDefLit` marks every WMO-gameobject prop batch from inside the placed-model assembler — the engine asserts it, not the caller; the world-API wall failed the first cut, and a following card is reachable only there — both shade write sites skip it on ownership, separately from 2041's payload question, and exterior props take one `doodad_ground_shade` at their own footprint at spawn, frozen, which answers lit for a non-resident tile so a boat at sea needs no special case and the unverified "a boat is never MCSH-shadowed" goes; 749 prop parts under a shade root carry the marker on a live run — the same 749 B373 measured reading a foreign probe — and the ADT doodad path is deliberately untouched, since the D3D capture's measured 2.5 gain outranks the closure (2047)
- 2043's unit-predicate law is confirmed byte-exact by the round it dispatched — all 29 `Unit*` predicates push the constant double 1.0 or nil, exactly one value at every live `ret`, and a BFS from all 83 bindings in `0x850438` reaches 460 functions without hitting `lua_pushboolean` — and three of its supporting citations are corrected in the comments, no behaviour changing: `lua_pushboolean 0x6f39f0` EXISTS with seven call sites, one of them the registered FrameScript binding `IsPetAttackActive 0x4be0e0`, which answers a real true/false and never nil (ours already did, and its doc already named itself the exception), so 2043's "no boolean on its binding surface at all" is withdrawn for the scoped claim 1830 actually made; `IsEnabled` is `0x7800b0`, not `0x780270` (that is `SetButtonState`), and its number-1-or-0-never-nil fact stands; and `UnitReaction 0x5167e0` pushes `0x6061e0(u1, u2)` PLUS ONE, so 0 is unreachable and its nil means only that a token did not resolve — our `reaction == 0` → nil stays, named now as a feed gap rather than a mechanism the binary does not have (2048)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
c0b7a613a9 |
the stable master is in, and master loot hands drops to the raid
- the stable master: `MSG_LIST_STABLED_PETS` both ways, the four mutations answered by one result byte, two slots priced from `StableSlotPrices.dbc` (500 and 50000 copper — the cap falls out of the data, corroborated by vmangos's `MAX_PET_STABLES` and the reference's `NUM_PET_STABLE_SLOTS`), rows seated by slot never by position (a petless hunter's list starts at a stabled row), slot 0 read from the wire because a dismissed pet has a row and no live unit, a re-list after every success because no result carries one, and `GetPetIcon` reading `CreatureFamily.dbc`'s icon column — which ships `Ability_Druid_CatForm` for both warlock families, asserted as shipped - master loot: `CMSG_LOOT_MASTER_GIVE` (the slot is a byte here where the roll family's is a u32) and `SMSG_LOOT_MASTER_LIST`; the click gate is the wire's slot-type byte and nothing else — the reference never asks who the master looter is — so a MASTER row fires `OPEN_MASTER_LOOT_LIST` instead of a take and auto-loot skips every non-ALLOW row; the candidate array is bucketed by raid subgroup into blocks of five (a dense list would have filed every candidate under the wrong "Group N"), an unresolved name is nil so `UPDATE_MASTER_LOOT_LIST` has something to repaint, and a give re-checks the row is still MASTER before sending; found on the way: a master looter in another raid subgroup lit the crown on your own portrait - `SetLootMethod` takes its third (threshold) argument; the reference's silent reset to Uncommon when it is absent is flagged and not adopted; the stable's model pane draws nothing yet — a stabled pet is a database row with no world entity, and a booth that renders from a bare display id is the next arc Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
35a7c870ba |
a 6.3 MB-a-frame copy dies, and the ghost portrait comes back alive
- the rig-palette copy-on-write copies the live prefix, never the capacity: ~6.3 MB of memcpy per frame — paid everywhere, however few rigs were awake — becomes the ~250 KB actually live, ~25× smaller and observationally identical - a frozen model-particle pool stays hidden (spell shards were drawing through walls and the cull forever), and a parked rig's animation event tracks fall silent like the reference's — except MORE_AUDIBLE creatures, so off-screen combat stays audible while the rest of the world goes properly quiet - an automatic dismount no longer keeps the mount's run speed on foot (the reported `.tele` repro): a mover's speeds stage for the whole drain instead of being written live mid-teleport - a dead character's portraits show the living face again: the ghost death-screen pass was leaking into every portrait bake — all eight portraits, paper doll, inspect, dress-up — because the death gate was the one combine lane with no per-view scale; post-fix the bake reads MAE 0.000 against the living control while the world still goes steel-blue on release Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b427860b35 |
dialogs come to the front, and the red line names what it needs
- dialogs carry the reference's toplevel bit, so the stack-split window opens above Bagnon instead of behind it - the failure line fills its blank — "Requires Blacksmith Hammer", not "Requires" — and Opening casts stop printing a label the spell itself marks "no text" - the pet bar's Attack/Follow/Stay tooltips name their keybinding, and typed text reaches the focused frame through the client's own delivery walk - AtlasLoot parses again: our Lua keeps 5.0's compatibility semicolon Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7b4bd8af4a |
quest timers, honest interiors, and snow rebuilt from the bytes
- timed quests get their countdown — a Quest Timers box under the minimap and a Time Remaining line in the quest log, kept against the server's own clock - interiors go honest: footsteps stop crunching the terrain's snow through a building's floor, and tavern voices are authored dry, so the fake cave echo is gone - snow is the reference's again — point-sprite flakes at the era's pixel size, and a spawn slab that leans into your run so a runner no longer outruns the field - Stormwind stops drawing Elwynn through its walls, a looted quest crate's lid settles, cold zones puff visible breath, and the collect-quest toast fires only on a real advance Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
94de741cb1 |
/target, /assist and /follow, and right click opens a clam
- /target and /assist find units by name like the original, and /follow walks you behind your target - right click opens clams and lockboxes, and the tooltip promises it - the freeze thaws clean, poses release instead of sticking, and you drop to neutral standing before it grips - unit frame art layers over the bars like the original Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
143a734b91 |
slow fall, levitate and water walking work, and Z sheathes your weapon
- the granted movement modes are one system now: slow fall slows, levitate hovers, water walking holds the surface, a root pins you even mid-air - casting while moving refuses with the real error, and a jump cast plays its animation in the air - Z cycles melee, ranged, stowed, every landed swing draws the weapon, and Alt-Z hides the UI - login faces where you logged out, and the dev shortcuts moved to Ctrl+Shift off the Windows key Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
56c62c7591 |
point-and-click casting: Blizzard lands where you aim
- ground-targeted spells work end to end: press the button, get the targeting cursor, click the ground, and the cast ships on the wire the way the real client sends it - a reticle sized to the spell's real radius rides the cursor, and on the click the storm model, falling shards and area sound all land at the point - character screens: a newly created character comes back selected on the list, and the authored layout scales to small windows instead of clipping the bottom controls off - blob shadows ride their receiver's clip transform and the raster bias that kept them faint is measured and fixed Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c887170b37 |
the social window, the escape menu, and items that work from the bar
- The social window: friends, ignore and `/who`, in the reference's own two-level arrangement with its inner tabs and per-row right-click menus. The lists ride the real wire — guid-keyed with no names, resolved through the name cache, and capped at 50 friends / 25 ignores because those are the client's own caps — and the ignore list does its actual job: an ignored speaker's chat is dropped silently, a whisper additionally tells the sender, and a duel challenge is auto-declined. Guild ships as a disabled tab, which is what a guildless character sees. - The escape menu: ESC's last rung finally opens something. The game menu lands at the reference's own size and ladder, and leaving works — logout and quit with the server owning the clock (instant at an inn, refused in combat, or the 20-second countdown), narrated by the real camp/quit dialogs. While the menu holds the center nothing else opens, and the bag bar greys out under it. - Items on the action bar act like items. An equipped trinket was a dead button; the reference's use law is two stages — not equippable means use, equippable and already worn means use in place — over its exact search order (equipment first, then the bags, then the backpack, then the keyring; never the bank). What can go on the bar is now the reference's own two filters, byte-read: an item with an on-use spell or one you can equip, and no passive spells — and dropping a passive there speaks the real client's own line, "You can't put a passive ability in the action bar." - A quest-starter item opens its quest instead of "The item was not found." The question goes to the item's own guid, never through item-use — which the server refuses for every one of the 215 quest-starter items — and refusing the quest says what the client says, then closes the window. - For the toolbox: a pixel pick that reports everything under the cursor all the way back, a lens that says which render phase a draw landed in, and switches that separate "discarded" from "lost depth" from "never drawn" — built while cornering a flickering tent, whose fix ships in the next snapshot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c1ba444119 |
the micro menu, the ping meter, and the PvP flag
- The main menu bar's two empty recesses fill. The micro menu — the eight arched buttons: character, spellbook, talents, quest log, socials, world map, main menu, help — sits in the dark panel between the page arrows and the bags, transcribed from the reference's own layout: the character button wears your portrait, a button holds itself down while its panel is up, and the talent button appears at level 10. The latency meter fills the one slot the bar art leaves transparent for exactly it, tinted green/yellow/red on the reference's 300/600 ms thresholds and drawn UNDER the bar art, seen through its window, the way 1.12 actually does it. The socials and main-menu buttons find their panels one snapshot later; help still waits. - The UI explains itself on hover: 1.12 ships its detailed two-line tooltips ON — a FrameXML default, not the opt-in extra it is often remembered as — so the micro buttons, your own portrait, the XP bar, the five bag buttons and the ping meter all answer a hover with the reference's own explanations. - The PvP flag, end to end: crossed swords on the player, target and party frames, driven by the two descriptor bits the wire actually carries, and `/pvp` speaks — the yellow toast plus the system chat line, the OFF sentence being the one that warns the flag lingers for five minutes. It really does linger: turning it off waits out the server's timer, which looks like a bug and is the reference. - Quest markers stopped freezing. The grey `!` over a questgiver above your level goes gold the moment you ding, an NPC re-entering view is asked afresh instead of wearing a session-old answer, and the marker drops when the questgiver bit goes off — the re-ask triggers mirror the reference's own set, plus one named addition (your own level changing). - Water and heights: swimming downhill no longer bounces between swim and fall ten times a second — the surface line is a two-sided constraint, so the swimmer settles with the river instead of being left behind by it. And collision height is per-unit now, read from the unit's own model: a gnome used to swim with a third of a yard of water over her head because five depth lines were quoting a human's number. - A round of pixel honesty in text and chat: the newest chat line no longer wears a wider drop shadow than every line above it (shadows move in whole pixels now), keeps its descenders, and hover boxes stop painting over the chat body. The chat input's history browse survives programmatic edits, a failed script chunk warns instead of vanishing, and recall deepens to 64 lines. Idle animation got honest too: a model's idle is Stand resolved through its own lookup table — never file order, which had the duel flag bobbing on its spawn clip — and an armed idle is culled on the box its animation authors, not the bind pose, so the flag stops vanishing at most camera angles. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
959fb8ba08 |
duels, inspecting other players, and water that ends at the bank
- Duels. Challenge another player from their right-click menu, or with `/duel`, and `/forfeit` to give it up. Both popups are there — the challenge you accept or decline, and the warning when you leave the field — with the flag countdown between them, and the outcome printed in chat when someone is knocked out or flees. Read off the reference client's own duel packets and the three globals it keeps client-side, which is why the challenger reads "You have requested a duel." even though the server never echoes that back. - Inspect. Right-click a player and open their paper doll: their nineteen publicly visible equipment slots with icon, name, quality and a linkable item, and their body standing in the pane, which follows along if you retarget while the window is open. No stack counts and no durability — those are not fields another player publishes, which is exactly why the reference client's inspect doll has never shown them either. - Water knows where it stops. Swimming used to trigger in the dry tunnel thirty yards under Stormwind's canal, and in mid-air over Blackrock's lava. A liquid is now the grid the map file actually stores — one wet flag per cell, and the surface height a bilinear over the four corners of the cell you are standing in, the same sample the reference client takes — so the water starts at the bank and its surface sits where your eye puts it. Lava is a liquid you swim in, an indoor liquid asks the building you are inside, and `/liquid` prints every surface claiming your feet and where each one came from. - Creature parts authored to appear only on death — the voidwalker, the banshee, the ooze, the stone elemental, the infernal — were drawing the whole time: material alpha now bakes per animation sequence and the unit lane actually evaluates it. The character and inspect model panes are lit by the reference widget's own single light instead of blowing out white, the rotate arrows spin while you hold them, and the transparent sort ladder had its sign backwards, so overhead names draw over water again instead of being painted out by a canal behind them. Underneath, the protocol writer's 95-verb drawer and its thousand-line spell module split into per-family files — no behaviour change, just a tree you can find things in. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c48d8c6b7e |
other players walk the world, and NPCs speak your name
- Other players walk the world. A watched player's motion between server updates now runs through the same grounded movement resolve as your own character — same capsule, same colliders, same step-up and ground election — so they follow terrain every frame instead of every packet, and stop at a wall instead of walking into it and being snapped back. Their jumps are swept against the world too, and a standstill jump steered mid-air reports that steer, the one mid-air press the reference client broadcasts rather than defers. - The move relay rebuilt: relayed moves ride a per-unit chain on real time, a due move never jumps ahead of its unit's queue, and SMSG_COMPRESSED_MOVES — the batched carrier the server sends under load — is decoded rather than skipped. Our own send path now matches the reference client's: facing streams while moving, Nagle is off so each packet is one write, and the strafe axis nets so left and right can never ride the same packet. - NPC and quest text speaks the whole $-macro grammar, carved from the reference bytes — your name, class and race, the gendered forms, and the server's live world states — and the same substitution feeds gossip, quest text, the quest log and unit frames. Greetings are a weighted draw, rolled per NPC per open, and a quest giver with no quest to give is no longer clickable, where it used to open a dialog holding nothing but a placeholder greeting. - Spell tooltips finish the reference's line law: cast time and cooldown behind the real passive gate, the required tool or form, the reagents block, the chance-to-X line, a multi-bit equipped-item requirement named properly instead of by raw bits, and a buff's dispel class. - The UI layout engine got a dense, reusable anchor solver, and a change gate that skips the resolve entirely on frames where no layout input moved — in practice, almost every idle frame. Pets are named by their pet number rather than a creature-template entry, so the client asks the query the server can actually answer. And the asset extractor grew a stray-geometry census for tracking down geometry that renders where it should not. This snapshot replaces the six dated `sync` commits that stood here before it, whose subjects said nothing but the date. If you cloned before now: `git fetch && git reset --hard origin/main`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2ad0043523 |
benilla: initial public release
A from-scratch World of Warcraft 1.12.1 client in Rust + Bevy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |