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

64 lines
9.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RuneWaker Native Disarm Main-Hand Audit (2026-10-05)
## Finding and scope
The local RuneWaker RoleAttr source initializes a missing main hand as `EM_Weapon_Unarmed` with speed 30, and its unarmed Main_DMG path supplies `Level * 2` when the main-hand base damage is zero. This is enough to replace the training weapon inputs for native Main_DMG-dependent formulas. It does not justify a WoW punch constant or a blanket multiplier. The implementation is opt-in through the native amount adapters; the separate WoW compatibility formula is not changed here.
The verified profile applies to the five active native player-build identities and their generated party-NPC derived-stat snapshots: Knight/Scout, Priest/Knight, Rogue/Warrior, Scout/Rogue, and Mage/Warlock. It retains the existing training-profile `residualFlatDamage` bridge and tracked generic physical-damage multiplier. The native stat model does not represent all weapon-category damage/speed rates, so the profile records a neutral-rate boundary rather than claiming exactness for those channels.
## Primary local source evidence
Paths below are relative to `F:\Projects\Runewaker`. Hashes are SHA-256 of the audited local files.
| Source | SHA-256 | Relevant evidence |
| --- | --- | --- |
| `SourceCodeVS2019/Libraries/Union/RoleData/RoleAttr.cpp` | `8143A90CE826BF3152551A4508D212F186012CBC054E505E426551CB62369C1F` | `Init_BaseEQValue` lines 178–203 initializes unarmed main hand, speed 30, and main DMG/speed factors 1. `_Main_ATKSpeed` lines 533–580 applies generic `AtkSpeed`, main-hand speed factor, all-weapon speed rate, unarmed speed rate, and the weapon's base speed, clamped to 5. `_MainDMG` lines 713–758 applies main-hand DMG factor, all-weapon DMG rate, `DMG_Per`, and unarmed DMG rate; if `BaseEQ.Main_DMG == 0`, it assigns `Level * 2`, then returns `max(1, (Fin.DMG + BaseEQ.Main_DMG) * DmgRate)`. `AddAbility` line 1323 routes type 25 into `BaseEQ.Main_DMG`; lines 1327–1374 route typed main damage and speed rates by weapon category. `MainAtkDamage` line 963 applies source weapon variance after `Fin.Main_DMG`. |
| `SourceCodeVS2019/Libraries/Union/RoleData/ObjectStruct.h` | `708F2D087F2654B97D149AB7124D9CDFFC915CF8BF18108FEFF3F1A45F124C20` | Defines `EM_Weapon_Unarmed` (line 1047) and the typed body-rate fields consumed by RoleAttr. |
| `SourceCodeVS2019/Libraries/Union/RoleData/RoleBaseDef.h` | `BAE1014F3628B4AD05C2E0F08CDCAE14D5F13A0981DAD4D592F2F0A10BFAAB18` | `_BASE_ATTACK_SPEED_` is 40; that default is not the unarmed main-hand value, which RoleAttr sets explicitly to 30. |
| `SourceCodeVS2019/Server/NetGameServer/MainProc/Global_SimpleSchedular.cpp` | `5DCDBDD69EEFE1086E2087AB167270D580884929A083E4E69032C773F7F941AC` | Main-hand auto scheduling adds `int(Fin.Main_ATKSpeed)` to the server clock (line 445), so this source speed unit is milliseconds. The source auto-hit then calls `MainAtkDamage` (line 633). |
| `SourceCodeVS2019/Server/NetGameServer/MainProc/MagicProc/MagicProcess.cpp` | `B2EEE402FDF9AC488ABE62DC8D2EC9C52B312542EF06D95A6B6FF594711CA7C3` | Physics damage uses `Main_DMG * 10 * SpellTime / Main_ATKSpeed` (line 2327). Physics_Point subtracts `Main_DMG * Dmg_MagArg` once for fixed effects or times SpellTime otherwise (lines 2384–2386). These are native ability branches that must see the unarmed main-hand inputs after Disarm. |
| `SourceCodeVS2019/Libraries/Union/RoleData/RD_Normal.cxx` | `5C0C726F469A7BE63D76114015BF169193A88F545209D4A4DEB5E090443C2692` | `CalAddDotMagicLv` lines 2423 onward applies rank correction; its negative physical DotMagic branch reads `Fin.Main_DMG` and `Fin.Main_ATKSpeed` (line 2507). |
| `SourceCodeVS2019/Libraries/Union/RoleData/RD_ValueCal.cxx` | `D7F00AD9E760EC2263F81F231113F165D0D9F29EB2C329593C66F4C6288A69A9` | Confirms the native `Main_DMG` accessor and unarmed category logic in the value-calculation path (lines 2529, 3642–3646). |
The active ability bindings are `src/game/generated/romNativeHolyHealBindings.generated.json` (SHA-256 `CE2F39D602296686382CDC424F9FEA4EFA8DE384093443311E9C8FB6C97AAA04`) and `src/game/generated/romNativePeriodicDamageBindings.generated.json` (SHA-256 `C01E17C4DF2ADB3B3A196D9EE1F84EA752BE94DBA81FB47E23A889599CB63D33`). Their source metadata pins `magiccollectobject.db` (`4aae19865cb07b30f99945aa7319a0096db244ff5e5b4b5d902fc6889474f501`) and `magicobject.db` (`52af13a2d8cfa9a66d4caa2d3c38832edc232f058995a64ef8135a6b82e43691`) for player attack/periodic rows. The native stat-rule artifact pins `racetable.db`, `sextable.db`, `voctable.db`, and `fixtable.db`; `romNativeStatRules.generated.json` SHA-256 is `6DA531D6D56642013155438B93BB55C120A6421C7C12858CAD8C1B934E1E4E99`.
The training item curve used for the bridge is `src/game/generated/romNativeDamageTrainingProfiles.generated.json` (SHA-256 `A3E064F536F4E1EFAFA076D13755DE5DC62C23B20A496AE572F783126DBE3BCB`). It records `romHeirloomReference.json` SHA-256 `4E5194A28C2764838C5862C1F3DB505F5BE3CE99143A4440BAECF5E38C3099BF` and the source item-database hashes: `armorobject.db` `A2F0017C4772165D562EBB08CA0AC51625CDC4453E3670A1501DF7D741D2BAD3`, `weaponobject.db` `2BC28EEBBE413BD6118F027E3FEC1A3953CEF371476937D0B65622C542971A67`, `addpowerobject.db` `D651861FC5918BAC937D726D754D17A92DA34DB5059F9C70AC9F6B2C15711432`, and `string_enus.db` `4FCE021BA9ED110A213E08D3228B2DE57CEC66DDBBC8DCF5753204874D9AB75`. These are the pins recorded by the generated reference, not a claim that the separate active `Runewaker/Resource/data` directory has those same hashes.
## Implemented formula and adapter contract
For a verified active-five native training profile, the source-shaped unarmed main-hand amount is:
```text
Main_DMG_unarmed = max(1, (residualFlatDamage + Level * 2) * physicalDamageRateFactor)
Main_ATKSpeed_unarmed = 30
Main_attack_interval = 30 * 100 ms = 3000 ms
```
`residualFlatDamage` is the existing native training-weapon bridge remainder after the generated profile's weapon contribution is removed. The `physicalDamageRateFactor` remains applied. Main-hand-only rate channels that this stats bridge cannot supply are held neutral and explicitly surfaced as `unmodeledRateChannels`: `Main_DMG_Per`, `DMG_AllWeapon`, `DMG_Unarmed`, `AtkSpeed`, `Main_ATKSpeed_Per`, `AttackSpeed_AllWeapon`, and `AttackSpeed_Unarmed`.
`nativeDisarmedMainHandAutoAttackProfile(caster)` only returns a profile when the current native derived stats match the canonical active-five training-gear profile. It identifies the main source as `runewaker-roleattr-unarmed-main-hand`, has no training weapon item IDs, and leaves `physicalAttack` intact. This profile is the prepared optional override for:
- `nativeDamageCasterAmount(..., targetContext?, mainHandOverride?)`: when the bound native component is calculation type 0 or 9, or has a nonzero fixed contribution of type 2, the adapter validates the override and feeds the lower native resolver a copied training profile whose main hand is `Level * 2`, speed 30, and `sourceItemIds: []`. The residual, generic rates, off-hand, and ranged entries are retained. Other components do not consume the substituted main-hand fields; a ranged branch therefore keeps its existing ranged inputs.
- `nativePeriodicDamageCasterSnapshot(..., magicResistance?, effectiveMagicPower?, mainHandOverride?)`: only the physical negative HP DotMagic rank-correction branch consumes the override. Other periodic branches keep their current source behavior.
In both APIs an omitted override preserves the pre-existing native calculation. A stale or invalid supplied override fails closed for a branch that consumes Main_DMG/Main_ATKSpeed (`null` for direct damage, `undefined` for a periodic snapshot). Custom/unverified gear returns no disarmed profile; callers must keep their existing explicit fallback behavior. This does not grant spell usability or certify native weapon/category prerequisites for every ability.
## Known limits
- This is the source main-hand input before `MainAtkDamage` variance, hit, target defense, crit, and other downstream combat resolution. It is not a complete auto-hit result.
- The native rules sidecar models generic physical damage rate, not all seven unarmed/main/all-weapon rate channels listed above. Their neutral values are a boundary of this implementation, not a source claim that the rates are always 1.
- The profile is verified against canonical generated training gear's tracked aggregate stats. A custom set whose tracked totals differ fails the gate, but a custom set with identical aggregates cannot be distinguished because the native gear payload has no item provenance. Equipment-specific `BaseEQ.Main_DMG` effects and unidentified weapon/category rate modifiers remain unmodeled.
- Existing ability cast/equipment prerequisites remain unchanged and unverified by this amount adapter. Direct abilities continue through their existing shared status/availability checks.
- The active-five generated party-NPC profile test establishes the stats preparation and formula input shape; it does not certify all classes, every NPC type, every ability, or native combat readiness.
## Verification
Focused source tests passed:
```text
npx vitest run src/game/romNativeDisarm.test.ts src/game/romNativeCombatAmount.integration.test.ts src/game/romNativePeriodicDamage.test.ts src/game/romNativeDamage.test.ts
4 files passed; 25 tests passed.
```
The Disarm tests cover all five active identities as player and configured party-NPC derived stats, preserve the residual and tracked multiplier, reject custom gear and stale override values, confirm native main-hand-consuming basic-attack branches change while the ranged Scout Shot remains unchanged, and verify a physical DotMagic rank correction responds to the unarmed inputs. This was a scoped source/runtime test run, not a full balance/readiness pass.