Elemental resistance in HP damage
- Source
docs/executable/elemental-resistance.md- SHA-256
6dfc436e…51b2e03b
Which equipment value is read · When that value affects damage · Decoded-data checks · Evidence and limits
How equipment's elemental defense enters the HP damage formula: its opposing-element lookup, target-affinity conditions and limits. This covers the elemental part of R42-I10 (../issues/R42-I10-elemental-resistance.md); no in-game behavior has been verified.
| Based on | The unpacked executable, SHA-256 7b0230a11498f40396c9048345e7cfe0ef442cb7ca8faffc6f197920d2669599; battle/battle_data_release.dat, entries #11, #16 and #19; localize/LocalizeText_en-us.dat, entries #37 and #54. |
| Checked | 2026-10-15 |
| Method | Ghidra 12.1.4 through REA: decompile 0x140388bb0, 0x14038bff0, 0x14038c220 and 0x140451230; disassemble 0x14038bff0; read the lookup tables at 0x140689BA8 and 0x140689B40. Cross-check against saved Ghidra decompilation and objdump -d; DuckDB queries below. Code interpretations are likely, not runtime observations. |
Which equipment value is read
Likely: 0x140388bb0 passes the attacker, defender, action and the paired HP formula rows to 0x14038bff0. For each elemental component, that function calculates an offensive contribution and calls 0x14038c220 with the defender and defender formula row. Its fifth argument is passed on the stack at 0x14038c1ca; the decompiler omits it from the displayed call, but the assembly preserves it.
Likely: the helper maps the incoming element to the element that beats it, sums that column of arms_data.element_resist over the defender's equipment slots 1–6, and multiplies by the corresponding defender hp_damage_data.element_def weight. Equipment slot 0 is excluded. These are signed equipment bytes and signed 16-bit formula weights.
| Incoming element | Equipment defense column read | Zero-based array index |
|---|---|---|
| Air | Ice | 5 |
| Earth | Air | 0 |
| Lightning | Earth | 1 |
| Water | Lightning | 2 |
| Fire | Water | 3 |
| Ice | Fire | 4 |
| Light | Dark | 7 |
| Dark | Light | 6 |
Confirmed: the executable bytes at 0x140689BA8 (file offset 0x6881A8) decode as ten little-endian 32-bit values: 0, 6, 1, 2, 3, 4, 5, 8, 7, 0. The eight named element IDs are 1–8. The lookup therefore agrees with the game's own help: SELECTHELP_LC_SHTXT_ELE02_DEF_TEXT through ELE09_DEF_TEXT describe strength in one element providing resistance to the next weaker element. In particular, FIRE strength resists ICE, and WATER strength resists FIRE. This mapping alone does not demonstrate a defect.
When that value affects damage
Likely: the helper also checks the incoming element against the defender's affinity (unit byte +0x185), using the 10-by-10 byte matrix at 0x140689B40 (file offset 0x688140). Let U be the offensive contribution for this component, and R be the weighted equipment sum described above. It returns:
| Incoming element versus defender affinity | Matrix value | Contribution returned |
|---|---|---|
| Incoming element beats the defender's affinity | 1 | max(U - R, 0) |
| Defender's affinity beats the incoming element | 2 | min(-R, 0) |
| Same element, neutral relationship, or no mapped element | 0 | 0 |
Likely: for the six-element cycle, only two defender affinities can produce a nonzero contribution for any incoming element. For ICE these are AIR (first branch) and FIRE (second branch). LIGHT against DARK and DARK against LIGHT both use the first branch; the matrix has no value-2 branch for these two elements. A missing defender also returns zero. These conditions are executable logic; changing formula weights alone cannot make the neutral branch use resistance.
Likely: U is the action's elemental bonus amount times the attacker's element_atk weight, plus the battlefield element times field_element. 0x14038bff0 also handles elemental buff-card contributions; each component goes through the same helper. In the spell action builder 0x140451230, the action's bonus amount is the larger of the effect slot's byte +0x42 and the selected weapon's element_bonus. It is separate from the spell's ordinary amount.
Likely: the helper's result joins the percentage term in 0x140388bb0, alongside the separate attacker-affinity versus defender-affinity bonus and other modifiers. The combined term is capped by tuning_values rows 79 and 80 before multiplying the stat-derived part of damage; the ATK/DEF contribution is added separately. Equipment resistance is therefore not a flat percentage of final damage.
Likely: with nonnegative equipment values and weights, the first branch can cancel U but cannot make this component negative. If U is zero, adding resistance in that branch changes nothing, even though the attack has elemental advantage. The second branch can supply a negative contribution without an offensive bonus. This distinction and the neutral branch can explain a reported lack of effect, but neither establishes developer intent or an elemental bug without a controlled observation.
Decoded-data checks
Run these SQL statements with DuckDB from the repository root. The file pages in Based on bind them to the source game-file hashes.
SELECT _row, _name_id, element_bonus,
"element_resist[3]" AS water_defense,
"element_resist[4]" AS fire_defense,
"element_resist[5]" AS ice_defense
FROM read_json_auto('data/game/battle/battle_data_release.dat/11.arms_data.jsonl')
WHERE _row IN (250, 465, 466);
SELECT _row, _name_id, "effect_kind[0]", "amount_type[0]",
"formula_id[0]", "element[0]", "unk_0x42[0]"
FROM read_json_auto('data/game/battle/battle_data_release.dat/16.effect_data.jsonl')
WHERE _row = 91;
SELECT _row, "element_atk[5]", "element_def[4]", "field_element[5]"
FROM read_json_auto('data/game/battle/battle_data_release.dat/19.hp_damage_data.jsonl')
WHERE _row IN (21, 22);
SELECT key, text
FROM read_json_auto('data/game/localize/LocalizeText_en-us.dat/54.txte.jsonl')
WHERE key SIMILAR TO 'SELECTHELP_LC_SHTXT_ELE0[2-9]_DEF_TEXT';
Confirmed: Mage Staff (arms row 250) has element_bonus 0. Robes of the Inferno (465) has FIRE defense 10; Robes of Black Ice (466) has ICE defense 10. Iceblast (effect row 91) has HP effect kind 1, amount type 3, formula 21, element 6 (ICE), and byte +0x42 zero. Formula row 21 has ICE attack weight 100 and ICE field weight 50; defender row 22 has FIRE defense weight 100. The arrays are populated and the relevant defense weight is nonzero. These decoded values do not prove their in-game effect.
Evidence and limits
REA Evidence IDs from this inspection:
| Observation | Evidence ID |
|---|---|
| Elemental helper decompilation | ev_f94f6f07d7cfbd84488e25e1f72162a50854071c3599880c4573f7721c342c33 |
| Elemental component caller decompilation | ev_f1e2a0032d12a3dc695399b3f1d231df1bb03ec2382053ff5785e820e2e064cf |
| HP formula decompilation | ev_eba0ea3b7fd043abd5aab9254f61c3e2ffbe7df4673645047fab0f15ac4b2492 |
| Spell action builder decompilation | ev_f1aed27a59f4911c3c2ff163a773a9fa36ca32f986af7bf0266355e699910e97 |
| Caller assembly, including fifth argument | ev_62696f6d095983faabfe35326f6988b3cd2b94fcfd513e859ebab4574456345c |
| Opposing-element table bytes | ev_b7bc5bf94b897bd36dc978cf101c526aaec5ee81a06f885446efbaa452b90fe1 |
| Affinity matrix bytes | ev_1bc8c4babaa1edbc2c53722f1003fe3ed6f6ae00482d52835ebc9364ab834006 |
This is one executable's static HP damage path. It does not establish PSP behavior, developer intent, another build's behavior, or the result of an in-game attack. HP restoration has a separate consumer (0x14038aaf0), documented in hp_restore_data; the opposing-element rule above must not be applied to that path without tracing it. The controlled tests and unresolved questions are in the issue report (../issues/R42-I10-elemental-resistance.md).