Fix EBINIT overlapping field ownership
This commit is contained in:
@@ -343,6 +343,36 @@ def _record_table_cell(destination, record_id):
|
||||
return matches[0] if matches else None
|
||||
|
||||
|
||||
def _resolve_parallel_record_overlaps(records):
|
||||
"""Prefer an established parallel column over a row-table range collision.
|
||||
|
||||
The global bank is flat, so a sufficiently large row-major table can
|
||||
contain an address that another INIT schema reaches as `base + entity_id`.
|
||||
A parallel base repeated by other records is stronger ownership evidence
|
||||
than one accidental in-range row/column calculation.
|
||||
"""
|
||||
parallel_records = {}
|
||||
for record in records:
|
||||
for key in record.get("fields", {}):
|
||||
parallel_records.setdefault(int(key, 0), set()).add(record["id"])
|
||||
for record in records:
|
||||
retained = {}
|
||||
for key, value in record.get("record_fields", {}).items():
|
||||
base, stride, column = (int(part, 0) for part in key.split("/"))
|
||||
destination = base + record["id"] * stride + column
|
||||
parallel_base = destination - record["id"]
|
||||
if any(
|
||||
other_id != record["id"]
|
||||
for other_id in parallel_records.get(parallel_base, ())
|
||||
):
|
||||
_store_unique(
|
||||
record["fields"], f"0x{parallel_base:x}", value, record["id"]
|
||||
)
|
||||
else:
|
||||
retained[key] = value
|
||||
record["record_fields"] = retained
|
||||
|
||||
|
||||
def extract_name(scr):
|
||||
string_addrs = [
|
||||
ins.args[0][1]
|
||||
@@ -379,6 +409,7 @@ def extract_name(scr):
|
||||
else:
|
||||
base, stride, column = cell
|
||||
cur["record_fields"][f"0x{base:x}/{stride}/{column}"] = value
|
||||
_resolve_parallel_record_overlaps(records)
|
||||
for record in records:
|
||||
if not record["record_fields"]:
|
||||
del record["record_fields"]
|
||||
|
||||
@@ -56,6 +56,30 @@ def test_real_name_tables() -> None:
|
||||
check(by_id[108]["record_fields"]["0x906f9/30/8"] == -5,
|
||||
"ITINIT item 108 preserves its paralysis-removal delta")
|
||||
|
||||
units, _ = extract_init.extract_name(sys4load.load(scripts["EBINIT.BIN"]))
|
||||
unit_by_id = {record["id"]: record for record in units}
|
||||
check(len({
|
||||
key
|
||||
for record in units
|
||||
for key in {
|
||||
**record.get("fields", {}),
|
||||
**record.get("record_fields", {}),
|
||||
}
|
||||
}) == 108, "EBINIT exposes 108 genuine populated fields")
|
||||
check(len({
|
||||
key for record in units for key in record.get("record_fields", {})
|
||||
}) == 82, "EBINIT exposes 82 genuine linked row-major columns")
|
||||
check(
|
||||
unit_by_id[456]["fields"]["0x6fb86"] == 12585
|
||||
and "0x4e693/300/91" not in unit_by_id[456].get("record_fields", {}),
|
||||
"EBINIT unit 456 keeps its battle sprite in the parallel asset column",
|
||||
)
|
||||
check(
|
||||
unit_by_id[600]["fields"]["0x7a37e"] == 80
|
||||
and "0x4e693/300/35" not in unit_by_id[600].get("record_fields", {}),
|
||||
"EBINIT unit 600 keeps its starting level in the parallel level column",
|
||||
)
|
||||
|
||||
objects, _ = extract_init.extract_name(sys4load.load(scripts["OBINIT.BIN"]))
|
||||
object_by_id = {record["id"]: record for record in objects}
|
||||
check(object_by_id[17]["name"] == "針"
|
||||
@@ -374,6 +398,11 @@ def test_field_semantics() -> None:
|
||||
] == [1, -1, 4, -4],
|
||||
"paired story and final-boss records preserve victory-target sign",
|
||||
)
|
||||
check(
|
||||
unit_by_id[456]["semantic_fields"]["unit_battle_sprite_asset_id"] == 12585
|
||||
and unit_by_id[600]["semantic_fields"]["unit_starting_level"] == 80,
|
||||
"overlapping EBINIT writes retain their established parallel semantics",
|
||||
)
|
||||
|
||||
stages, meta = extract_init.extract_mixed(
|
||||
sys4load.load(extract_init.resolve("STINIT"))
|
||||
|
||||
Reference in New Issue
Block a user