Files
healer-man/docs/ENEMY_AUTOMATIC_OFFHAND_2026-10-05.md
2026-10-06 22:25:34 -04:00

133 lines
7.1 KiB
Markdown

# Automatic enemy offhands — 2026-10-05
Current source-table recovery supersedes the missing equipment-map limitation
recorded below. See [the 2026-10-06 equipment mapping audit](ENEMY_EQUIPMENT_MAPPING_2026-10-06.md)
for the pinned SQL, Item.dbc validation, importer output, and runtime helper.
## Result
Added narrow helpers for the two historical AzerothCore automatic offhand
gates: the creature-template `USE_OFFHAND_ATTACK` flag and the default `AUTO`
mode's resolved offhand weapon check. The flag helper binds only the five
matching creatures present in HealerMan's imported template-reference subset.
The item helper requires a nonzero item ID and an already resolved Item.dbc
class of `WEAPON` (2). It does not map an `equipment_id` to an item because
that table is absent from the pinned world import.
The helpers are in [enemyAutomaticOffhand.ts](../src/game/enemyAutomaticOffhand.ts).
They do not schedule attacks or mutate combat state. Runtime should treat
`enemyFlagDrivenOffhandContract(entry)` as an intrinsic creature capability and
feed it into the existing ordinary WoW offhand path. Keep this source flag
separate from the simulator's temporary spell-granted capability: core sets the
flag mode in creature initialization and entry updates, so evade, death, and
encounter reset must not clear it while the creature entry remains the same.
The simulator's existing reset policy can continue to clear spell-granted
Dual Wield state.
`enemyAutoEquippedOffhandContract({ entry, name?, offhandItemId,
offhandItemClass })` resolves the default `AUTO` item gate only after a caller
has loaded the actual offhand virtual item and looked up its class. An absent,
zero, unresolved, or non-weapon item returns no contract. Do not use a model,
creature appearance, or `equipment_id` alone as a substitute for the missing
slot-to-item mapping. Keep both helpers on the source WoW creature path; the
existing RuneWaker, summon, and character-defender exclusions remain in force.
## World source and coverage
The imported AzerothCore world source is commit
`4d9d1d4b5723e819c5c390f6ca1177283dbfb8e3`. Its lock pins `creature.sql` at
SHA-256 `f54ddfe7ee00080b4b0bc970a809d6f947f6fb2627dbcd67e92b402fb34cb701`
and `creature_template.sql` at SHA-256
`0066e4fd35b809986dd325487515087f5a95f4ffe50ea03846c8d8bf5196870a`.
`creature_template.sql` has 29,947 rows, 43 of which set
`flags_extra & 0x00000800`. The imported runtime reference contains 1,671
creature templates; five carry that bit:
| Entry | Creature | `flags_extra` |
| ---: | --- | ---: |
| 4540 | Scarlet Monk | 2048 |
| 10478 | Splintered Skeleton | 2048 |
| 10488 | Risen Construct | 2048 |
| 16593 | Shattered Hand Brawler | 134219776 |
| 16808 | Warchief Kargath Bladefist | 2048 |
The source lock includes `creature.sql` rows with an `equipment_id` field, but
does not include `creature_equip_template.sql`. The pinned spawn table has
149,879 rows; 51,248 have a nonzero `equipment_id` value (the distinct values
are `-1`, `1`, and `2`). Those values select equipment-set IDs; they do not
identify an offhand item by themselves.
I found `creature_equip_template.sql` in two local AzerothCore backup trees:
`../wow-pvt-server-backups-2/azerothcore-wotlk/data/sql/base/db_world/creature_equip_template.sql`
and
`../WoW-Pvt-Server-Backups/azerothcore-wotlk/data/sql/base/db_world/creature_equip_template.sql`.
The copies are byte-identical at SHA-256
`D95423028E4D9CD7068E317BDE66ED847DC8238F4A6AA952D9EA40EF36109A1A`, and their
rows include Stockade mappings such as `1696/1 -> 5305, 5305`,
`1711/1 -> 2184, 2184`, and `1716/1 -> 2179, 1897`. These are useful leads,
but they are not bound to this import: the adjacent archived
`creature_template.sql` and `creature.sql` hashes are respectively
`e20e921613a02f8a86e997fd0d1d959dbe868bd6886151c8ca5f6c187a549356` and
`135c78413da1bbbce0c65ee4ee833c0f19343933b3a0e30f82ff023f37aa6164`, which
do not match the pinned inputs. The equipment-table path is also absent from
the pinned Git tree. The importer therefore does not bind those mappings.
The local WotLK client
`../wow335a/Data/patch-A.MPQ/DBFilesClient/Item.dbc` and AzerothCore server
`../wow-pvt-server-backups-2/azerothcore-wotlk/data/Item.dbc` have the same
SHA-256:
`22061a811523790ddd2ee95b36ade2ac6e0c79a8b3121324d32a639acf50567f`.
`ItemEntry::ClassID` is field 1 in the core DBC structure and the client DBC
uses the matching `Class` field. Item class 2 is `ITEM_CLASS_WEAPON`. The core
`Creature::HasWeapon(OFF_ATTACK)` checks only that the offhand virtual item
resolves and its class is weapon; it does not check subclass or inventory type.
This pins the gate logic and the local DBC bytes, but does not supply the
missing equipment-set-to-item mapping for the locked world rows.
## Core behavior reference
The behavior audit uses the local read-only AzerothCore checkout at HEAD
`e822d718322289dd036fb2daf8867ba41283bf9a`. This is a core-behavior reference,
not the world-data commit above.
- `CreatureData.h:57` defines `CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK = 0x800`.
- `Creature::InitEntry` and `Creature::UpdateEntry` set
`DualWieldMode::ENABLED` when the flag is present (`Creature.cpp:565-566,
614-615`).
- `Creature::HasWeapon(OFF_ATTACK)` returns true in `ENABLED` mode. In `AUTO`
mode it resolves virtual item slot 1 and accepts only a valid item whose
`ClassID == ITEM_CLASS_WEAPON` (`Creature.cpp:3299-3317`). The core enum is
in `Unit.h:587-590`; `ITEM_CLASS_WEAPON` is 2 at `ItemTemplate.h:293`.
- `Creature::SelectLevel` initializes both base and offhand from the same
generated creature range (`Creature.cpp:1540-1549`).
`Creature::CalculateMinMaxDamage` uses the same range, template variance,
attack-power contribution, and damage modifier for either hand
(`StatSystem.cpp:1119-1167`). `UNIT_MOD_DAMAGE_OFFHAND` applies the default
0.5 total damage factor (`Unit.cpp:401`).
- Both swing timers use `CreatureTemplate.BaseAttackTime`
(`Creature.cpp:617-618`). Ordinary white swings add 19 percentage points to
miss chance when an offhand is available (`Unit.cpp:15288-15290`). The
existing Dual Wield source contract supplies the half-swing initial offset
and 200 ms simultaneous-swing presentation stagger.
Relevant paths in the reference checkout are `src/server/game/Entities/Creature/CreatureData.h`,
`src/server/game/Entities/Creature/Creature.cpp`,
`src/server/game/Entities/Unit/StatSystem.cpp`,
`src/server/game/Entities/Unit/Unit.cpp`,
`src/server/game/Entities/Unit/Unit.h`,
`src/server/game/Entities/Item/ItemTemplate.h`, and
`src/server/shared/DataStores/DBCStructure.h`.
## Validation and limits
`npm test -- --run src/game/enemyAutomaticOffhand.test.ts` passes four focused
tests. npm's `pretest` hook also ran the existing loot-catalog generator; it
reported the existing 9-boss/23-item catalog, with no loot-file change.
The flag path is implemented from the locked creature-template data. The AUTO
path is implemented as a strict, reusable item-class gate, but no locked world
equipment mapping currently supplies it with item IDs. Exact automatic
offhand availability for the imported `equipment_id` and template-default
cases remains unresolved until a matching equipment-template source is pinned
or imported.