Decode RTINIT movement providers 5 and 11
This commit is contained in:
@@ -90,6 +90,43 @@ ROUTINE_BANK_ROLES = (
|
||||
"battle_forbidden_story_flag_id",
|
||||
)
|
||||
|
||||
MOVEMENT_PROVIDER_PARAMETER_SCHEMAS = {
|
||||
5: {
|
||||
"behavior": "approach_destination_tile",
|
||||
"parameter_fields": {
|
||||
"movement_parameter_1": "destination_tile_x",
|
||||
"movement_parameter_2": "destination_tile_y",
|
||||
},
|
||||
"completion": (
|
||||
"advance the current step's progress counter after reaching the "
|
||||
"destination tile (or its linked type-6 stage-object exit tile)"
|
||||
),
|
||||
},
|
||||
11: {
|
||||
"behavior": "cycle_destination_waypoints",
|
||||
"parameter_fields": {
|
||||
"movement_parameter_1": "destination_tile_x",
|
||||
"movement_parameter_2": "destination_tile_y",
|
||||
"movement_parameter_3": "waypoint_ordinal",
|
||||
"movement_parameter_4": "path_cost_limit_override",
|
||||
},
|
||||
"parameter_notes": {
|
||||
"waypoint_ordinal": (
|
||||
"one-based; only the ordinal matching the entity's current "
|
||||
"zero-based waypoint index executes"
|
||||
),
|
||||
"path_cost_limit_override": (
|
||||
"optional; zero/absent falls back to the entity's current FS"
|
||||
),
|
||||
},
|
||||
"completion": (
|
||||
"advance the entity's waypoint index modulo the largest authored "
|
||||
"waypoint ordinal after reaching the destination tile (or its "
|
||||
"linked type-6 stage-object exit tile)"
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
UNIT_STAT_COLUMNS = (
|
||||
"accuracy", "evasion", "physical_attack", "physical_defense",
|
||||
"magic_attack", "magic_defense", "speed", "luck", "critical_chance",
|
||||
@@ -900,6 +937,21 @@ def _movement_provider_names(names: dict[int, str]) -> dict[int, str]:
|
||||
return providers
|
||||
|
||||
|
||||
def _join_movement_provider_semantics(step: dict) -> int:
|
||||
"""Add selector-specific RTN_M semantics while retaining every raw bank."""
|
||||
selector = step.get("movement_provider_selector")
|
||||
schema = MOVEMENT_PROVIDER_PARAMETER_SCHEMAS.get(selector)
|
||||
if schema is None:
|
||||
return 0
|
||||
step["provider_behavior"] = schema["behavior"]
|
||||
joined = 0
|
||||
for raw_field, semantic_field in schema["parameter_fields"].items():
|
||||
if raw_field in step:
|
||||
step[semantic_field] = step[raw_field]
|
||||
joined += 1
|
||||
return joined
|
||||
|
||||
|
||||
def extract_banked(scr):
|
||||
"""Extract RTINIT's sparse routine sets across twenty parallel step banks."""
|
||||
writes = _routine_bank_writes(scr)
|
||||
@@ -915,6 +967,8 @@ def extract_banked(scr):
|
||||
records_by_id: dict[int, dict] = {}
|
||||
cell_assignments: dict[tuple[int, int, int], list[int]] = collections.defaultdict(list)
|
||||
bank_cells: dict[int, set[tuple[int, int]]] = collections.defaultdict(set)
|
||||
decoded_movement_step_count = 0
|
||||
decoded_movement_parameter_count = 0
|
||||
|
||||
for offset, destination, value, bank_index, record_id, slot in writes:
|
||||
bank_base = ROUTINE_BANK_ROOT + bank_index * ROUTINE_BANK_SPAN
|
||||
@@ -954,14 +1008,19 @@ def extract_banked(scr):
|
||||
}
|
||||
if movement:
|
||||
selector = movement.get("movement_provider_selector")
|
||||
movement_steps.append({
|
||||
step = {
|
||||
"slot": slot,
|
||||
**movement,
|
||||
**(
|
||||
{"provider_script": movement_providers.get(selector, "")}
|
||||
if selector is not None else {}
|
||||
),
|
||||
})
|
||||
}
|
||||
joined_parameter_count = _join_movement_provider_semantics(step)
|
||||
if selector in MOVEMENT_PROVIDER_PARAMETER_SCHEMAS:
|
||||
decoded_movement_step_count += 1
|
||||
decoded_movement_parameter_count += joined_parameter_count
|
||||
movement_steps.append(step)
|
||||
|
||||
battle = {
|
||||
ROUTINE_BANK_ROLES[bank]: final_by_bank_slot[(bank, slot)]
|
||||
@@ -1049,6 +1108,20 @@ def extract_banked(scr):
|
||||
str(selector): name
|
||||
for selector, name in sorted(movement_providers.items())
|
||||
},
|
||||
"movement_provider_parameter_schemas": {
|
||||
str(selector): {
|
||||
"provider_script": movement_providers.get(selector, ""),
|
||||
**schema,
|
||||
}
|
||||
for selector, schema in sorted(
|
||||
MOVEMENT_PROVIDER_PARAMETER_SCHEMAS.items()
|
||||
)
|
||||
},
|
||||
"decoded_movement_provider_count": len(
|
||||
MOVEMENT_PROVIDER_PARAMETER_SCHEMAS
|
||||
),
|
||||
"decoded_movement_step_count": decoded_movement_step_count,
|
||||
"decoded_movement_parameter_count": decoded_movement_parameter_count,
|
||||
"battle_provider_scripts": {
|
||||
str(selector): name
|
||||
for selector, name in sorted(battle_providers.items())
|
||||
|
||||
@@ -236,6 +236,15 @@ def profile_banked(data: dict) -> dict:
|
||||
"available_movement_provider_count": len(
|
||||
data.get("movement_provider_scripts", {})
|
||||
),
|
||||
"decoded_movement_provider_count": data.get(
|
||||
"decoded_movement_provider_count", 0
|
||||
),
|
||||
"decoded_movement_step_count": data.get(
|
||||
"decoded_movement_step_count", 0
|
||||
),
|
||||
"decoded_movement_parameter_count": data.get(
|
||||
"decoded_movement_parameter_count", 0
|
||||
),
|
||||
"available_battle_provider_count": len(
|
||||
data.get("battle_provider_scripts", {})
|
||||
),
|
||||
@@ -391,6 +400,10 @@ def render_markdown(data: dict, rows: list[dict], limit: int) -> str:
|
||||
f"{banked_profile['movement_step_count']}/"
|
||||
f"{banked_profile['movement_provider_count']} "
|
||||
f"({banked_profile['available_movement_provider_count']} dispatchable)",
|
||||
f"- selector-specific movement semantics: "
|
||||
f"{banked_profile['decoded_movement_step_count']} steps, "
|
||||
f"{banked_profile['decoded_movement_parameter_count']} parameters "
|
||||
f"across {banked_profile['decoded_movement_provider_count']} providers",
|
||||
f"- joined battle steps/used providers: "
|
||||
f"{banked_profile['battle_step_count']}/"
|
||||
f"{banked_profile['battle_provider_count']} "
|
||||
|
||||
@@ -371,6 +371,10 @@ def test_real_routine_banks() -> None:
|
||||
and len(meta["used_battle_provider_selectors"]) == 4
|
||||
and len(meta["record_field_columns"]) == 117,
|
||||
"RTINIT assembles every populated movement and battle step")
|
||||
check(meta["decoded_movement_provider_count"] == 2
|
||||
and meta["decoded_movement_step_count"] == 252
|
||||
and meta["decoded_movement_parameter_count"] == 685,
|
||||
"RTINIT reports selector-specific semantic coverage")
|
||||
check([
|
||||
layout["bank_index"]
|
||||
for layout in meta["bank_layouts"].values()
|
||||
@@ -393,6 +397,35 @@ def test_real_routine_banks() -> None:
|
||||
check(by_id[173]["movement_steps"][0]["movement_parameter_1"] == 2
|
||||
and by_id[173]["movement_steps"][0]["movement_parameter_2"] == 158,
|
||||
"RTINIT final rows reflect source-ordered conflicting overwrites")
|
||||
provider_5 = by_id[5]["movement_steps"][2]
|
||||
check(provider_5["movement_provider_selector"] == 5
|
||||
and provider_5["provider_behavior"] == "approach_destination_tile"
|
||||
and provider_5["destination_tile_x"] == 24
|
||||
and provider_5["destination_tile_y"] == 123
|
||||
and provider_5["movement_parameter_1"] == 24
|
||||
and provider_5["movement_parameter_2"] == 123,
|
||||
"RTINIT joins RTN_M005 destination semantics without replacing raw banks")
|
||||
provider_11 = by_id[87]["movement_steps"][2]
|
||||
check(provider_11["movement_provider_selector"] == 11
|
||||
and provider_11["provider_behavior"] == "cycle_destination_waypoints"
|
||||
and provider_11["destination_tile_x"] == 3
|
||||
and provider_11["destination_tile_y"] == 499
|
||||
and provider_11["waypoint_ordinal"] == 1
|
||||
and provider_11["path_cost_limit_override"] == 2,
|
||||
"RTINIT joins all four RTN_M011 waypoint parameters")
|
||||
check("provider_behavior" not in movement
|
||||
and "destination_tile_x" not in movement,
|
||||
"RTINIT does not leak provider-specific meanings onto undecoded selectors")
|
||||
check(
|
||||
meta["movement_provider_parameter_schemas"]["11"]["parameter_fields"]
|
||||
== {
|
||||
"movement_parameter_1": "destination_tile_x",
|
||||
"movement_parameter_2": "destination_tile_y",
|
||||
"movement_parameter_3": "waypoint_ordinal",
|
||||
"movement_parameter_4": "path_cost_limit_override",
|
||||
},
|
||||
"RTINIT publishes the reusable RTN_M011 parameter schema",
|
||||
)
|
||||
semantics = extract_init.field_semantics(records)
|
||||
check(
|
||||
semantics["0xeff78/20/0"]
|
||||
|
||||
@@ -16,6 +16,19 @@ def test_load_and_lint():
|
||||
check(entries[0xa57]["category"] == "story-flag", "0xa57 is category story-flag")
|
||||
check(entries[0x3234]["category"] == "story-flag", "0x3234 is category story-flag")
|
||||
check({0xa57, 0xa58, 0xa59} <= set(entries), "Lily form flags A/B/C all present")
|
||||
check(entries[0x4e085]["columns"] == {
|
||||
"0": "current_hp", "1": "current_sp", "2": "current_fs"
|
||||
}, "entity current-resource columns are curated")
|
||||
check(entries[0x4e11b]["columns"]["10"] == "movement"
|
||||
and entries[0x4e11b]["columns"]["13"] == "max_fs",
|
||||
"entity effective-stat columns are curated")
|
||||
check(entries[0x5231f]["name"] == "entity_tile_x"
|
||||
and entries[0x52351]["name"] == "entity_tile_y",
|
||||
"entity map-coordinate arrays are curated")
|
||||
check(entries[0x56b20]["name"] == "entity_patrol_waypoint_indices",
|
||||
"RTN_M011 waypoint state is curated")
|
||||
check(entries[0xb240e]["name"] == "pathfinding_movement_costs",
|
||||
"movement-cost work grid is curated")
|
||||
# lint clean against a permissive address universe (curated addrs are self-consistent)
|
||||
errors, warnings = G.lint(entries, set(entries))
|
||||
check(errors == [], f"globals.toml lints clean (errors={errors})")
|
||||
|
||||
@@ -129,6 +129,9 @@ def main() -> int:
|
||||
"conflicting_overwrite_count": 1,
|
||||
"movement_step_count": 2,
|
||||
"battle_step_count": 1,
|
||||
"decoded_movement_provider_count": 1,
|
||||
"decoded_movement_step_count": 1,
|
||||
"decoded_movement_parameter_count": 2,
|
||||
"movement_provider_scripts": {"1": "RTN_M001.BIN"},
|
||||
"battle_provider_scripts": {"1": "RTN_B001.BIN"},
|
||||
"used_movement_provider_selectors": [1],
|
||||
@@ -157,6 +160,9 @@ def main() -> int:
|
||||
assert banked_summary["reserved_bank_count"] == 1
|
||||
assert banked_summary["movement_step_count"] == 2
|
||||
assert banked_summary["battle_provider_count"] == 1
|
||||
assert banked_summary["decoded_movement_provider_count"] == 1
|
||||
assert banked_summary["decoded_movement_step_count"] == 1
|
||||
assert banked_summary["decoded_movement_parameter_count"] == 2
|
||||
|
||||
messages = profile.profile_messages(fixture)
|
||||
assert messages["population"] == 1
|
||||
|
||||
Reference in New Issue
Block a user