Decode persistent unit stat growth

This commit is contained in:
gamer147
2026-07-24 09:12:49 -04:00
parent c494b786bf
commit a876ddc269
6 changed files with 154 additions and 6 deletions

View File

@@ -31,6 +31,10 @@ def test_load_and_lint():
"entity activity and faction arrays are curated")
check(entries[0x4e693]["name"] == "entity_skill_flags",
"per-entity skill flags are curated")
check(entries[0x6f70]["name"] == "unit_stat_growth_fractions"
and entries[0x6f70]["type"] == "int[100][14]"
and entries[0x6f70]["columns"]["13"] == "max_fs",
"persistent unit stat-growth fractions are curated")
check(entries[0x53e13]["name"] == "entity_carried_item_ids"
and entries[0x53e77]["name"] == "entity_carried_item_counts",
"per-entity carried-item slots are curated")
@@ -417,6 +421,105 @@ def test_battle_triggered_passive_skill_flags_evidence():
if name == "BTL.BIN"),
"CALCDMG computes actor recovery and BTL adds it to the acting entity's HP")
def test_unit_stat_growth_fractions_evidence():
target = 0x6f70
expected_offsets = {
"ADDEXP.BIN": {0x1ea, 0x200, 0x212, 0x241, 0x24c, 0x263, 0x2a0},
"EVOLVE.BIN": {0xa2a, 0xa3a},
"GAMECLEAR.BIN": {0x331, 0x341, 0x417},
"GAMESTART.BIN": {0x942, 0xac5},
"TRAIN.BIN": {0x901, 0x917, 0x929, 0x958, 0x963, 0x975, 0x9a5, 0x9db},
"UNITECH.BIN": {0x403, 0x40e, 0x420, 0x44f, 0x45a},
}
refs = []
loaded = {}
for name, path in paths.scripts().items():
if path.read_bytes()[:8] != b"SYS4422 ":
continue
script = sys4load.load(path)
loaded[name] = script
refs.extend(
(name, instruction)
for instruction in script.instructions
if (3, target) in instruction.args
)
actual_offsets = {}
for name, instruction in refs:
actual_offsets.setdefault(name, set()).add(instruction.offset)
check(actual_offsets == expected_offsets
and len(refs) == 27
and all(instruction.opcode == 0x12c
and instruction.args[3] == (0, 14)
for _, instruction in refs),
"stat-growth fractions have exactly 27 stride-14 lookups in six scripts")
instructions = {
name: {instruction.offset: instruction for instruction in loaded[name].instructions}
for name in expected_offsets
}
growth_paths = {
"ADDEXP.BIN": {
"source": (0x1f5, 0x7e5e6),
"add": 0x20b,
"divide": 0x21d,
"current": 0x224,
"modulo": 0x257,
},
"TRAIN.BIN": {
"source": (0x90c, 0x155e9b),
"add": 0x922,
"divide": 0x934,
"current": 0x93b,
"modulo": 0x96e,
},
"UNITECH.BIN": {
"source": (0x3f1, 0x7e5e6),
"add": 0x419,
"divide": 0x42b,
"current": 0x432,
"modulo": 0x465,
},
}
growth_mechanics_ok = True
for name, path in growth_paths.items():
script = instructions[name]
source_offset, source_address = path["source"]
growth_mechanics_ok &= (
script[source_offset].opcode == 0x12c
and script[source_offset].args[1] == (3, source_address)
and script[path["add"]].label == "add"
and script[path["divide"]].label == "div"
and script[path["divide"]].args[-1] == (0, 100)
and script[path["current"]].opcode == 0x12c
and script[path["current"]].args[1] == (3, 0x69f8)
and script[path["modulo"]].label == "mod"
and script[path["modulo"]].args[-1] == (0, 100)
)
check(growth_mechanics_ok,
"level-up, training, and catch-up growth carry hundredths into current stats")
persistence_ok = all(
instructions["GAMESTART.BIN"][offset + 0xb].label == "string-lookup-set"
for offset in expected_offsets["GAMESTART.BIN"]
)
for name, first, second, copy in (
("GAMECLEAR.BIN", 0x331, 0x341, 0x351),
("EVOLVE.BIN", 0xa2a, 0xa3a, 0xa4a),
):
script = instructions[name]
persistence_ok &= (
script[first + 0xb].opcode == 0x63
and script[second + 0xb].opcode == 0x63
and script[copy].opcode == 0x1b0
and script[copy].args[-1] == (0, 14)
)
persistence_ok &= (
instructions["GAMECLEAR.BIN"][0x422].opcode == 0x1a2
)
check(persistence_ok,
"save/load, game-clear, and evolution preserve the complete fractional rows")
def test_merge_precedence():
curated, _ = G.load_toml(paths.VM_MAP / "globals.toml")
auto = G.load_auto(paths.BUILD / "global-var-map.json")
@@ -482,6 +585,7 @@ if __name__ == "__main__":
test_adv_layer_surface_slot_evidence()
test_selected_movement_route_grid_evidence()
test_battle_triggered_passive_skill_flags_evidence()
test_unit_stat_growth_fractions_evidence()
test_merge_precedence()
test_sys4load_labels_from_registry()
test_miner_finds_known_flags()