Files
healer-man/docs/ROM_NATIVE_STATS_SOURCE_2026-10-03.md
T

8.3 KiB
Raw Blame History

RoM native stats source contract (2026-10-03)

This adapter is a source-derived RoM stat model for a selectable simulator ruleset. It keeps the existing shared CharacterStats contract available to combat code, but its romNative sidecar retains Runewaker semantics and raw point channels. It does not convert RoM hit, dodge, critical, or parry points to WoW ratings or percentages.

Source records and identity

The stat exporter reads racetable.db, sextable.db, voctable.db, and fixtable.db from F:\Projects\Runewaker\Resource\data. Records begin at byte 140 and are 408 bytes. Vocation records contain two 408-byte records per class. The exporter validates the expected record IDs before interpreting floats and fails on non-finite values.

The player vocation argument rows are IDs 301003, 301005, 301007, 301009, 301011, 301013, 301015, 301017, 301019, and 301021 for Warrior through Champion. Playable race rows are 300000–300002 (Human, Elf, Dwarf); gender rows are 302000–302001 (male, female); the player formula/fix row is 303001. The generated output includes all ten classes, all three races, and both genders. The race and gender growth contributions are zero in these records, but their identities remain explicit inputs.

Formula definitions were checked against:

  • F:\Projects\Runewaker\SourceCodeVS2019\Libraries\Union\RoleData\RoleAttr.cpp
  • F:\Projects\Runewaker\SourceCodeVS2019\Libraries\Union\RoleData\ObjectStruct.h
  • F:\Projects\Runewaker\SourceCodeVS2019\Server\NetGameServer\MainProc\MagicProc\MagicProcess.cpp

romNativeStatRules.generated.json records the source file hashes, row IDs, per-class/race/gender growth and coefficients, player FIX values, and formula notes. The generator also checks the Human/male base curves against romBaseStatGrowth.generated.json.

Verified native formulas

At each level above 1, each base stat follows the table-defined progression value = value * (1 + combinedPercentGrowth) + combinedFlatGrowth. For a secondary vocation, Runewaker adds the secondary base at Level_Sub divided by ten. The helper accepts that secondary level; its primary-level fallback when the value is absent is simulator policy. The level-10 secondary-vocation unlock gate is also simulator policy, applied by the runtime caller rather than the source equation.

The source's MND attribute is exposed as Wisdom in the application. The player-derived channels implemented here are:

  • Max HP: (base HP + ExAddHP + STR*HpStrArg + STA*HpStaArg + flat MaxHP) * MaxHP_Rate, minimum 1. The table adapter includes the base, attributes, flat gear, and rate; ExAddHP from external abilities/passives is outside this base helper.
  • Max MP: (base MP + INT*MPIntArg + MND*MpMndArg + flat MaxMP) * MaxMP_Rate, minimum 0, when the primary or secondary class has the source MP resource type. The player JobSpMpType mapping gives MP to source classes 4–10 (Mage through Champion); classes 1–3 use SP. This does not replace current simulator Focus/Rage/Psi pools or change ability costs.
  • ATK: (STR*AtkStrArg + INT*AtkIntArg + AGI*AtkAgiArg + flat ATK) * ATK_Per, minimum 1.
  • DEF: (STA*DefArg + flat DEF + armor-category base DEF contributions) * DEF_Per, minimum 1. The helper calculates the attribute and flat-gear terms; its reference equipment curves do not contain source armor-category BaseEQ.Def values.
  • MDEF: (MND*MDefArg + flat MDEF) * MDEF_Rate, minimum 1.
  • MATK: (INT*MAtkArg + flat MATK) * MATK_Rate, minimum 1.
  • DMG: (ATK/DmgAtkArg when nonzero + flat DMG) * class/race/gender DmgRate * DMG_Per, minimum 0. Player FIX row 303001 has DmgAtkArg = 0, so player gear DMG is flat.
  • MDMG: (MATK/MDmgMAtkArg when nonzero + flat MDMG) * MDMG_Per, minimum 0. Player FIX row 303001 has MDmgMAtkArg = 0, so player gear MDMG is flat.
  • MPow: the all-magic BodyRate, neutral 1. It is not MATK or MDMG.

The source's raw hit and avoidance channels are preserved as point values: physical hit adds PhyHit_AgiArg * AGI; physical dodge adds PhyAvoid_AgiArg * AGI; magical hit adds MagHit_MndArg * MND; magical dodge adds MagAvoid_MndArg * MND + MagAvoid_AgiArg * AGI. Physical/magic critical and parry are also source point channels. The sidecar does not label those values as percentages or ratings.

The verified Holy Heal inputs are source-backed, but the per-spell equation remains incomplete in this adapter. MagicProcess.cpp uses skill OrgPower, the capped MND * 0.3 * SpellTime contribution (cap branch gives OrgPower * 6), optional MDMG * Dmg_HealArg, then multiplies by MPow * HealPowerRate and adds HealPoint * SpellTime. Player FIX row 303001 supplies Dmg_HealArg = 0.5. The target-side modifier is Power * (1 + HealAbsorbRate/100) + HealAbsorb unless the effect is ignored. romNative.healing carries these coefficients and raw caster/target inputs; skill rank, OrgPower, timing, flags, random variance, undead behavior, and the full spell resolver remain outside this stat adapter.

Equipment inputs and compatibility bridge

Native gear is an optional ItemStats.romNative payload. Its additive fields are explicitly source-semantic points; *RateFactor fields are multiplicative factors, with 1 neutral. equipmentStats combines flat values by addition and rate factors by multiplication. A source-native payload on an item takes precedence over the foreign-item bridge for that item, so mixed sets do not count its common direct values twice.

The native heirloom reader imports only romNativeGearCurves.generated.json, a compact five-class/slot/level table, and does not load the larger audit catalog at runtime. Those curves are source-selected Good-quality item references for Knight, Priest, Scout, Rogue, and Mage. The reference builder selects class/slot medians and selected package pieces, then applies its documented monotone level interpolation. This is simulator gear-profile policy, not a Runewaker per-level item formula. The remaining five classes and missing slots have no source-native curve.

The canonical converter keeps legacy reference aliases distinct: legacy magicalDamage is source type 148 MdmgPoint; legacy magicalWeaponDamage is type 191 MDMG; weaponDamage is type 25 DMG; healingPower is type 150 HealPoint; and dodge is type 17 physical dodge. The compact table therefore does not use a generic name normalizer that could map 148 into 191.

An audit of the 999 unique item IDs selected by the current five-class curves against the matching raw armorobject.db, weaponobject.db, and fixed addpowerobject.db records found no selected-item type 44 MPow, type 149 HealPowerRate, type 144 HealAbsorbRate, or type 147 HealAbsorb effects. Type 150 HealPoint occurs on two selected raw items and remains exported. The profile's neutral MPow/HealPowerRate factors therefore reflect absent effects on these selected references, not an assumed RoM conversion. The helper still accepts native payloads for those exact channels if a later source-specific item loader supplies them.

For items without a native payload, romNativeGearFromSharedDirectStats is an explicit compatibility simulator policy: it bridges only flat Strength, Stamina, Intellect, Agility, HP, and MP. WotLK Spirit is not Wisdom. WotLK ratings, Attack Power, Spell Power, armor, weapon DPS, and resistances are not interpreted as native RoM stats. Existing common item fields and their WotLK projections are preserved for compatibility.

At the shared combat boundary, runtime may use numeric native ATK as common attack power, MDMG as common spell power, and DEF as common armor under an explicit combatStatModel simulator label. That 1:1 adapter is for shared combat consumers; it does not assert equivalence to WoW stat definitions. MDEF and native point channels remain in the sidecar. A complete armor-defense calculation and verified native-point combat resolution are still open.

Focused verification

romNativeStats.test.ts checks every class/race/gender primary curve, player-derived level-1 channels, MP gating, source healing coefficients, the 148/191 conversion boundary, a real level-70 Mage reference curve, rate-preserving aura recomputation, and gear aggregation. equipment.test.ts checks per-item payload precedence, foreign direct-stat bridging, mixed-loadout behavior, and native equip/unequip impact. itemStats.test.ts checks that native payload persistence does not change the existing WotLK numeric fields.