From ef7140c294decff8e7fe1326c24d870170a48b94 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 4 Sep 2026 20:51:17 +0700 Subject: [PATCH] an item on the cursor is a sale, and an animation's sound has a place MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - right-clicking a vendor with an item on the cursor sells it: `0x5df5d0` asks `GetCursorItem` before anything else and a non-zero answer takes the arm to `CMSG_SELL_ITEM` instead of `CMSG_LIST_INVENTORY`, where benilla sent the list unconditionally — the whole stack, to the clicked vendor, with no merchant-window gate anywhere in the arm; the clear is `SetCursorItem(0, …)` rather than `ClearCursor`, which fires `CURSOR_UPDATE` and leaves the source slot greyed until the server's inventory update (1914); and the NPC you are talking to is not clickable: `0x5f0130` refuses a right-click on the NPC whose window is already open, against a global with exactly two writers — the interaction setter, whose fourteen callers are every one a window opener, and its clear — so the first click always gets through and no double-click is eaten, where benilla re-sent the arm's opcode on every click (1866) - a Hated or Hostile questgiver's `!` now disappears, every `!` blinks off and back on when you resurrect, and the minimap no longer dots a corpse, your own pet or an untrackable unit: the sweep tears down on HOSTILITY, not on the flag — `0x5eb134` is a convergent block reached from two branches, so three incompatible readings had each seen one path — and an escort giver's `!` clearing with its flag is the field watch instead, which B257 now rests on; the dots gain `0x4eaa90`'s three preconditions (1906); the quest log's rebuild is all-or-nothing — the reference makes every rebuild a no-op until its template misses are answered, so a cold log shows empty and then lands in one step, never our `"..."` row per in-flight quest — sorted level then title, case-insensitively, with the `id == 0` group first under the reference's own "Missing header! (quest designers)"; and `GetQuestLogTitle`'s `isHeader`/`isCollapsed` answer 1/nil, not booleans - an animation event has a place, not just a time: the M2 event kernel (`0x719370`) places each fired key through its bone, and benilla carried the when and the what and never the where, so every animation-event sound played at the model root — 149 of 244 `$DSL` records sit off their model's origin, out to 67.6 yd on `Maraudon_Waterfall01.m2` — and the point belongs to the record, not the tag, since 75 models author one tag twice; doodad, GameObject and shake sounds now sound where the key is (1904); and the unit dispatcher's arms do not agree about the point they are handed — footsteps and emotes at the event point (the footstep's material foley stays at the origin), `$TRD` at the origin, `$CSD` at attachment 17 rather than its head-mounted records, `$CSS` at the event point on a hit and attachment 1 on a miss — with `0x623b90`'s attachment-else-bias resolver transcribed table and all (1915) - `GetSkillLineInfo` out of range answers the reference's thirteen-value tuple, numbers in the rank slots, not a single nil — the stock skill window adds two of those slots unguarded straight off `SetSelectedSkill(0)`, so one nil raises the moment it drives us; one shape serves every out-of-range case because the bounds test at `0x4d3675` is a single unsigned compare, and our source had asserted the opposite (1919); the outbound wire is traced by name (`out`, mirroring `in`), a line pushed only after a successful write — a 5-second visit to the Goldshire poster reads as 87 lines (1901); the binding shape gate seats a player and checks the `Unit*` verbs it had been skipping, coverage 220 → 249, all agreeing with the reference (1911); and a UI capture scenario opts the UI in itself instead of photographing a UI-less world and exiting 0, the opt-in answering `false` in a player build that has no harness Co-Authored-By: Claude Fable 5.1 --- crates/benilla-app/src/camera_shake.rs | 44 +- crates/benilla-app/src/capture/fixtures.rs | 23 +- crates/benilla-app/src/capture/mod.rs | 10 +- .../benilla-app/src/capture/probe_goquest.rs | 15 +- .../benilla-app/src/capture/probe_service.rs | 385 +++++++++++++++++- crates/benilla-app/src/capture/scenarios.rs | 177 ++++++-- crates/benilla-app/src/creature_anim.rs | 5 +- .../benilla-app/src/creature_anim/events.rs | 147 ++++++- .../benilla-app/src/creature_anim/impact.rs | 9 + crates/benilla-app/src/creature_anim/lod.rs | 5 +- crates/benilla-app/src/doodad_events.rs | 24 +- crates/benilla-app/src/entities.rs | 75 ++++ crates/benilla-app/src/entities/missile.rs | 4 + .../benilla-app/src/entities/spell_fx/mod.rs | 22 +- crates/benilla-app/src/footprints.rs | 26 +- crates/benilla-app/src/go_anim.rs | 30 +- crates/benilla-app/src/lib.rs | 6 +- crates/benilla-app/src/minimap/blips/dots.rs | 148 ++++++- crates/benilla-app/src/minimap/blips/mod.rs | 2 +- crates/benilla-app/src/minimap/mod.rs | 15 +- crates/benilla-app/src/net/apply/combat.rs | 3 + crates/benilla-app/src/net/io.rs | 31 +- crates/benilla-app/src/quest_markers/query.rs | 361 ++++++++++++++-- crates/benilla-app/src/run_mode.rs | 18 + crates/benilla-app/src/sound/anim_events.rs | 110 +++-- crates/benilla-app/src/sound/combat.rs | 29 +- crates/benilla-app/src/sound/footsteps.rs | 10 +- crates/benilla-app/src/sound/gameobject.rs | 8 +- crates/benilla-app/src/target/click.rs | 291 ++++++++++++- crates/benilla-app/src/target/ring.rs | 8 + crates/benilla-app/src/ui_quest.rs | 25 +- crates/benilla-app/src/ui_quest_log.rs | 243 +++++++++-- crates/benilla-app/src/ui_script/lifecycle.rs | 8 +- crates/benilla-app/src/ui_script/mod.rs | 6 +- .../benilla-app/src/ui_script/shape_gate.rs | 47 ++- crates/benilla-assets/src/lib.rs | 8 +- crates/benilla-assets/src/m2.rs | 22 +- crates/benilla-assets/src/model.rs | 2 +- crates/benilla-assets/src/model/anims.rs | 39 +- crates/benilla-assets/src/trace.rs | 8 +- .../src/bin/benilla-extract/main.rs | 13 + .../src/bin/benilla-extract/scan/mod.rs | 2 +- .../src/bin/benilla-extract/scan/skeleton.rs | 131 ++++++ crates/benilla-formats/src/models/anim.rs | 44 +- .../benilla-formats/tests/m2_anim_events.rs | 71 ++++ .../src/messages/update_object/fields/unit.rs | 13 + crates/benilla-protocol/src/world/session.rs | 1 + .../benilla-protocol/src/world/writer/mod.rs | 39 +- crates/benilla-ui/src/script/cursor.rs | 101 +++++ crates/benilla-ui/src/script/merchant.rs | 12 +- crates/benilla-ui/src/script/quest_log.rs | 59 ++- crates/benilla-ui/src/script/skills.rs | 112 ++++- 52 files changed, 2747 insertions(+), 300 deletions(-) diff --git a/crates/benilla-app/src/camera_shake.rs b/crates/benilla-app/src/camera_shake.rs index 284a28ba..26cefff5 100644 --- a/crates/benilla-app/src/camera_shake.rs +++ b/crates/benilla-app/src/camera_shake.rs @@ -19,7 +19,7 @@ //! - **`SpellVisualKit` field 14** (`kit+0x38`, the `ShakeID` column) → once per kit play, via //! [`SpellKitShake`] and [`fire_kit_shakes`], which carries the mechanism and the one deviation. //! - **the `$SHK` animation event** → its payload *is* a group id, fired ungated at the event's own -//! bone-transformed point ([`event_point`]). +//! bone-transformed point — the fired key's own, carried on the event (decision 1904). //! //! **`$SHK` is decoded by exactly two handlers**, hanging off the GameObject (typemask `0x20`, //! `0x5f3e20`) and DynamicObject (`0x40`, `0x5d58c0`) trampolines — the dword `0x4b485324` occurs @@ -106,7 +106,7 @@ use benilla_formats::{CameraShake, CameraShakeCatalog, SpellShakeGroup}; use crate::creature_anim::{ footfall_culls, footfall_side, move_flags, AnimSoundEvent, MovementState, SpellKitShake, }; -use crate::entities::{BoneAttach, Creatures}; +use crate::entities::Creatures; use crate::net::{Embodied, NetEntity, ObjectStore, Spline}; use crate::player::ViewSubject; use benilla_assets::{AssetSet, LockRecover, WorldAssets}; @@ -288,19 +288,13 @@ struct Shakes(CameraShakeCatalog); fn fire_shakes( mut events: MessageReader, time: Res