Files
healer-man/docs/ROM_NATIVE_COMBAT_RESOLUTION_SOURCE_2026-10-03.md
2026-10-04 13:13:35 -04:00

9.2 KiB
Raw Permalink Blame History

RoM native combat resolution and source boundary — 2026-10-03

This note documents the narrow native combat resolver in src/game/romNativeCombatResolution.ts. It supplies Runewaker hit, avoidance, critical, typed resistance, and ATK/DEF or MATK/MDEF stages to the shared CombatResult shape. The resolver accepts only ruleset: "rom"; WoW and CoA requests return an explicit unsupported result and never reinterpret WoW ratings as Runewaker points.

Source inputs and identity

scripts/runewaker-pipeline/export-rom-native-combat-data.mjs reads local npcobject.db, magiccollectobject.db, magicobject.db, vocation/race/sex growth, FixTable, and SkillValueBase/JobSkillValue records. It exports a compact entityIdToNpcId map from generated population entity IDs to raw Runewaker NPC IDs. romNativeNpcIdForEntity only resolves that map. The raw ID is not an AzerothCore serverEntry and must not be passed to server-entry registration.

Player hit profiles are bound to exact imported (sourceSkillId, componentId) pairs. NPC hit profiles are bound to the NPC's exact source spell row and component. The profile keeps MagicCollect.EffectType, HitRateFunc, and its two raw arguments separate from MagicObject.Attack.CalType, AttackType, and MagicType: the collect effect selects the source hit gate, calculation type selects physical/magical damage and critical handling, and MagicType selects one of the six elemental resistance channels (Earth, Water, Fire, Wind, Light, Darkness). An elemental school is therefore not inferred from the physical/magical calculation channel.

NPC derived attributes use authored NPC attributes plus the source vocation, race, and sex growth tables at the explicit runtime level. NPC runtime levels accept the source's 1–200 range; the level-80 player cap is not applied to scaled NPCs. Combat snapshots additionally require an available skill-table row (currently through level 100). No source hit/avoidance table is silently extrapolated past its exported rows.

romNativeNpcEquipment.generated.json preserves all five main/off-hand candidate IDs (empty entries and duplicates at 20% each), fixed BowEQ/OrnamentEQ IDs, item WearEQ values, refinement and explicitly separate drop metadata. romNativeNpcEquipment.ts resolves deterministic source candidate selection, durability/power-quality wear power and verified refine sequences. Supported channels feed native attributes, attack/defense, healing, resistance and main-hand speed/range/damage. Live callers use the NPC-id seed; actor-specific seeds and explicit candidate overrides are available. DropItemAbility metadata is not worn affix data and is not applied. The default selection supports 398/440 imported NPCs, including Barren 101459–101461 and Anglo. Remaining off-hand weapon damage, bow/ornament channels, unknown effects and vocation mismatches fail closed. See the equipment pass. NPC periodic target/caster data may be present for inspection on an unsupported profile, but callers must gate native combat use on combatStatus === "supported" / nativeCombatant.

Exact enemy manual fixed/MaxHP packets now use romNativeEnemyDamage.ts; their source stages differ from ordinary physical/magical attack formulas and skip those generic power stages. Source absorption/AoE inputs require verified neutral canonical training gear and aura state. Exact source amounts retain authored encounter multipliers and bypass the native shared-damage calibration. Unbound Throw and unknown fixed inputs remain explicit gaps. The encounter follow-up records the updated runtime boundaries and pressure evidence.

Combat stages

The positive damage amount arrives with fixed-value and supported ability-power inputs already resolved by the native amount helper. Unless sourcePowerStagesAlreadyApplied is explicitly set, the resolver applies the source level-difference multiplier, the attacker's exact typed native magic-power rate, and the defender's exact typed resistance to magical channels. The source resistance routine works on signed negative damage: it adds raw integer resistance when the damage magnitude is large enough, halves damage when resistance exceeds half of the damage, then multiplies by the typed resistance rate. Missing school inputs return unsupported rather than using a guessed school or WoW Nature mapping.

For NPC attackers, source Global::DLvRate uses the verified level-difference table, whose result bottoms at 1 and caps at 2. Player attackers use 1. Native mitigation then uses max(0.2, 1 + ((ATK or MATK)+100 - ((DEF or MDEF)+200)) / max((ATK or MATK)+100, (DEF or MDEF)+200)). Auto attacks also retain the source 0.3 minimum. The critical or parry multiplier is applied before this defense ratio, and the final amount is truncated once, at the end of those multipliers.

Auto-attack physical avoidance follows the source dodge-then-miss order. Physical miss compares native weapon and Define skill points; a player weapon skill receives the source +10, while an NPC uses level * 2.3 directly. Define skill receives +10. Physical hit/dodge uses the source FIX hit base/range and raw point ratio, source body hit/dodge channels, and the non-player-versus-player level adjustment. Spell hit checks use the exact imported MagicCollect function: base, linear level, signed-square level, per-target ordinal, or Shoot. For a physical-effect spell with a non-base function, source physical skill miss is rolled first; integer physical hit rate then modifies the collect hit check, whose failure is reported as dodge. Spells do not use a separate auto-attack dodge roll. Unavailable Shoot skill or invalid source arguments fail closed.

The source physical avoidance table checks block, then parry, then critical. Block and parry rates come from native point-vs-resist point formulas with FIX base and level terms; they are not percentages. Parry also consumes the source ParryRate body-rate adjustment, halves the landed damage, and prevents a critical. A block prevents damage. Imported HP attack types gate block/parry, and callers can disable individual outcomes through CombatResultRules for source components or mechanics that disallow them.

Critical profiles use the actual physical or magical CalType, not the collect effect or elemental school. Critical points are compared against the target's native ResistCriticalHit points and source FIX base/level terms. Direct magic critical uses the source integer threshold and ±10 level adjustment; auto physical critical uses the source floating threshold and ±20 adjustment. The resulting CombatResult retains the shared outcome/table fields while source metadata records the selected channel, applied source rates, and defense ratio.

At DoT tick time, targetMitigationSnapshotted (also accepted from CombatResultRules) bypasses hit, critical, resistance, and DEF/MDEF because application-time native DoT snapshots already contain those stages. Immunity is checked before that bypass. This keeps snapshot damage from being mitigated twice.

Snapshot factories and explicit simulator policies

romNativeNpcCombatant(npcId, runtimeLevel?) is the NPC snapshot factory; romNativeNpcIdForEntity(entityId) supplies its raw source ID for imported populations. NPC snapshots are cached by source ID and exact runtime level.

romNativeTrainingCombatant(stats, trainingGearProfileMatches, weaponSlot) creates a player/party snapshot only after the caller confirms the complete canonical training gear profile. It reads the compact generated training profile and verified neutral training-item body defaults. Weapon/Define skills use the source SkillValueBase/JobSkillValue maximum at the current player level; this is an explicit simulator auto-training policy, not a claim about an individual character's training history. Custom or unverified gear returns unsupported. Typed magic rates and resistances retain six source channels.

nativeMagicInputsForType(snapshot, magicType) exposes the exact typed power rate and resistance for periodic snapshot callers. romNativePlayerHitProfile and romNativeNpcHitProfile expose only the exact exported binding pairs. resolveRomNativeCombatResult returns a full shared CombatResult or an explicit unsupported reason; a caller must preserve its damage school and ruleset rather than relabeling a fallback result as native.

Verification

src/game/romNativeCombatResolution.test.ts covers native ATK/DEF and MATK/MDEF ratios, hit ordering, the NPC weapon-skill exception, block/parry, bound hit/calculation channel separation, typed MPow and resistance, NPC level 81, the supported Anglo neutral off-hand, fail-closed non-neutral equipment, neutral verified training supplements, immunity, and snapshotted mitigation. Source-level claims were checked against the local read-only Runewaker/SourceCodeVS2019/Libraries/Union/RoleData/RoleAttr.cpp, RD_ValueCal.cxx, RD_ValueCal.inl, RD_Normal.cxx, RD_Setting.cxx, ObjectStruct.h, and the referenced binary DB layouts. This is a source-derived subset; unresolved equipment, beyond-table skills, unsupported calculation types, missing supplements, and cross-ruleset inputs remain explicit unsupported cases rather than certified native combat.