Close RTINIT movement parameter semantics
This commit is contained in:
@@ -91,6 +91,24 @@ ROUTINE_BANK_ROLES = (
|
||||
)
|
||||
|
||||
MOVEMENT_PROVIDER_PARAMETER_SCHEMAS = {
|
||||
1: {
|
||||
"behavior": "advance_step_progress",
|
||||
"parameter_fields": {},
|
||||
"ignored_parameter_fields": {
|
||||
"movement_parameter_1": (
|
||||
"authored once (routine set 173, slot 2, value 10), but "
|
||||
"RTN_M001 never reads movement parameter bank 2"
|
||||
),
|
||||
"movement_parameter_2": (
|
||||
"authored once in the same step (value 711), but RTN_M001 "
|
||||
"never reads movement parameter bank 3"
|
||||
),
|
||||
},
|
||||
"completion": (
|
||||
"unconditionally advance the current step's progress counter and "
|
||||
"produce movement result state 1 without selecting a destination"
|
||||
),
|
||||
},
|
||||
4: {
|
||||
"behavior": "approach_stage_object_slot",
|
||||
"parameter_fields": {
|
||||
@@ -172,6 +190,26 @@ MOVEMENT_PROVIDER_PARAMETER_SCHEMAS = {
|
||||
"valid movement destination toward the selected ally"
|
||||
),
|
||||
},
|
||||
8: {
|
||||
"behavior": "approach_nearest_foreign_magic_pillar",
|
||||
"parameter_fields": {},
|
||||
"ignored_parameter_fields": {
|
||||
"movement_parameter_1": (
|
||||
"authored once (routine set 112, slot 6, value 1), but "
|
||||
"RTN_M008 never reads movement parameter bank 2"
|
||||
),
|
||||
},
|
||||
"target_selection": (
|
||||
"nearest reachable active stage object of OBINIT type 2, 3, or 4 "
|
||||
"(small, medium, or large Magic Pillar) whose runtime state/faction "
|
||||
"differs from the acting entity; unlike RTN_M015, no configured "
|
||||
"route-radius gate is applied"
|
||||
),
|
||||
"completion": (
|
||||
"produce a movement result when a reachable foreign-controlled "
|
||||
"Magic Pillar exists"
|
||||
),
|
||||
},
|
||||
10: {
|
||||
"behavior": "approach_healing_feather",
|
||||
"parameter_fields": {
|
||||
@@ -241,6 +279,55 @@ MOVEMENT_PROVIDER_PARAMETER_SCHEMAS = {
|
||||
"destination tile (or its linked type-6 stage-object exit tile)"
|
||||
),
|
||||
},
|
||||
13: {
|
||||
"behavior": "approach_faction_traversable_tile",
|
||||
"parameter_fields": {
|
||||
"movement_parameter_1": "target_faction_filter",
|
||||
},
|
||||
"parameter_defaults": {
|
||||
"movement_parameter_1": 0,
|
||||
},
|
||||
"parameter_notes": {
|
||||
"target_faction_filter": (
|
||||
"zero means any faction other than the acting entity's faction; "
|
||||
"a nonzero value selects exactly that faction id. Only one "
|
||||
"shipped step explicitly writes value 1; three use default zero"
|
||||
),
|
||||
},
|
||||
"target_selection": (
|
||||
"when the current tile is not traversable by the selected faction "
|
||||
"set, choose the nearest reachable tile whose "
|
||||
"tile_faction_traversal_masks value includes that set, then "
|
||||
"approach it"
|
||||
),
|
||||
"completion": (
|
||||
"advance the current step's progress counter after producing a "
|
||||
"valid movement destination into the selected faction's traversable "
|
||||
"territory"
|
||||
),
|
||||
},
|
||||
14: {
|
||||
"behavior": "retreat_from_nearby_enemies",
|
||||
"parameter_fields": {
|
||||
"movement_parameter_1": "maximum_threat_route_steps",
|
||||
},
|
||||
"parameter_notes": {
|
||||
"maximum_threat_route_steps": (
|
||||
"inclusive route-step radius used to collect active foreign-"
|
||||
"faction threats; shipped values are 3 or 6"
|
||||
),
|
||||
},
|
||||
"target_selection": (
|
||||
"sum route-proximity scores from every active foreign-faction "
|
||||
"entity within the threat radius, exclude occupied tiles, and "
|
||||
"choose a reachable tile with the lowest positive aggregate score "
|
||||
"(farthest from the collected threats), randomizing ties"
|
||||
),
|
||||
"completion": (
|
||||
"advance the current step's progress counter after producing a "
|
||||
"valid retreat destination"
|
||||
),
|
||||
},
|
||||
15: {
|
||||
"behavior": "approach_foreign_magic_pillar",
|
||||
"parameter_fields": {
|
||||
@@ -1074,12 +1161,12 @@ def _movement_provider_names(names: dict[int, str]) -> dict[int, str]:
|
||||
return providers
|
||||
|
||||
|
||||
def _join_movement_provider_semantics(step: dict) -> tuple[int, int]:
|
||||
def _join_movement_provider_semantics(step: dict) -> tuple[int, int, 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, 0
|
||||
return 0, 0, 0
|
||||
step["provider_behavior"] = schema["behavior"]
|
||||
joined = 0
|
||||
defaulted = 0
|
||||
@@ -1091,7 +1178,14 @@ def _join_movement_provider_semantics(step: dict) -> tuple[int, int]:
|
||||
elif raw_field in defaults:
|
||||
step[semantic_field] = defaults[raw_field]
|
||||
defaulted += 1
|
||||
return joined, defaulted
|
||||
ignored_fields = {
|
||||
raw_field: step[raw_field]
|
||||
for raw_field in schema.get("ignored_parameter_fields", {})
|
||||
if raw_field in step
|
||||
}
|
||||
if ignored_fields:
|
||||
step["ignored_movement_parameters"] = ignored_fields
|
||||
return joined, defaulted, len(ignored_fields)
|
||||
|
||||
|
||||
def extract_banked(scr):
|
||||
@@ -1112,6 +1206,7 @@ def extract_banked(scr):
|
||||
decoded_movement_step_count = 0
|
||||
decoded_movement_parameter_count = 0
|
||||
decoded_movement_defaulted_parameter_count = 0
|
||||
ignored_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
|
||||
@@ -1162,6 +1257,7 @@ def extract_banked(scr):
|
||||
(
|
||||
joined_parameter_count,
|
||||
defaulted_parameter_count,
|
||||
ignored_parameter_count,
|
||||
) = _join_movement_provider_semantics(step)
|
||||
if selector in MOVEMENT_PROVIDER_PARAMETER_SCHEMAS:
|
||||
decoded_movement_step_count += 1
|
||||
@@ -1169,6 +1265,7 @@ def extract_banked(scr):
|
||||
decoded_movement_defaulted_parameter_count += (
|
||||
defaulted_parameter_count
|
||||
)
|
||||
ignored_movement_parameter_count += ignored_parameter_count
|
||||
movement_steps.append(step)
|
||||
|
||||
battle = {
|
||||
@@ -1274,6 +1371,7 @@ def extract_banked(scr):
|
||||
"decoded_movement_defaulted_parameter_count": (
|
||||
decoded_movement_defaulted_parameter_count
|
||||
),
|
||||
"ignored_movement_parameter_count": ignored_movement_parameter_count,
|
||||
"battle_provider_scripts": {
|
||||
str(selector): name
|
||||
for selector, name in sorted(battle_providers.items())
|
||||
|
||||
@@ -248,6 +248,9 @@ def profile_banked(data: dict) -> dict:
|
||||
"decoded_movement_defaulted_parameter_count": data.get(
|
||||
"decoded_movement_defaulted_parameter_count", 0
|
||||
),
|
||||
"ignored_movement_parameter_count": data.get(
|
||||
"ignored_movement_parameter_count", 0
|
||||
),
|
||||
"available_battle_provider_count": len(
|
||||
data.get("battle_provider_scripts", {})
|
||||
),
|
||||
@@ -408,7 +411,9 @@ def render_markdown(data: dict, rows: list[dict], limit: int) -> str:
|
||||
f"{banked_profile['decoded_movement_parameter_count']} populated "
|
||||
f"parameters + "
|
||||
f"{banked_profile['decoded_movement_defaulted_parameter_count']} "
|
||||
f"explicit defaults "
|
||||
f"explicit defaults + "
|
||||
f"{banked_profile['ignored_movement_parameter_count']} "
|
||||
f"authored-but-unread parameters "
|
||||
f"across {banked_profile['decoded_movement_provider_count']} providers",
|
||||
f"- joined battle steps/used providers: "
|
||||
f"{banked_profile['battle_step_count']}/"
|
||||
|
||||
@@ -371,10 +371,11 @@ 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"] == 8
|
||||
and meta["decoded_movement_step_count"] == 508
|
||||
and meta["decoded_movement_parameter_count"] == 971
|
||||
and meta["decoded_movement_defaulted_parameter_count"] == 10,
|
||||
check(meta["decoded_movement_provider_count"] == 12
|
||||
and meta["decoded_movement_step_count"] == 632
|
||||
and meta["decoded_movement_parameter_count"] == 974
|
||||
and meta["decoded_movement_defaulted_parameter_count"] == 13
|
||||
and meta["ignored_movement_parameter_count"] == 3,
|
||||
"RTINIT reports selector-specific semantic coverage")
|
||||
check([
|
||||
layout["bank_index"]
|
||||
@@ -454,8 +455,41 @@ def test_real_routine_banks() -> None:
|
||||
and provider_15["provider_behavior"] == "approach_foreign_magic_pillar"
|
||||
and provider_15["maximum_target_route_steps"] == 3,
|
||||
"RTINIT joins RTN_M015's foreign Magic Pillar search radius")
|
||||
check("provider_behavior" not in movement
|
||||
and "destination_tile_x" not in movement,
|
||||
provider_1_residue = by_id[173]["movement_steps"][2]
|
||||
check(provider_1_residue["movement_provider_selector"] == 1
|
||||
and provider_1_residue["provider_behavior"] == "advance_step_progress"
|
||||
and provider_1_residue["ignored_movement_parameters"] == {
|
||||
"movement_parameter_1": 10,
|
||||
"movement_parameter_2": 711,
|
||||
},
|
||||
"RTINIT marks RTN_M001's authored but unread parameter cells")
|
||||
provider_8_residue = by_id[112]["movement_steps"][6]
|
||||
check(provider_8_residue["movement_provider_selector"] == 8
|
||||
and provider_8_residue["provider_behavior"]
|
||||
== "approach_nearest_foreign_magic_pillar"
|
||||
and provider_8_residue["ignored_movement_parameters"]
|
||||
== {"movement_parameter_1": 1},
|
||||
"RTINIT marks RTN_M008's authored but unread parameter cell")
|
||||
provider_13 = by_id[104]["movement_steps"][3]
|
||||
check(provider_13["movement_provider_selector"] == 13
|
||||
and provider_13["provider_behavior"]
|
||||
== "approach_faction_traversable_tile"
|
||||
and provider_13["target_faction_filter"] == 1,
|
||||
"RTINIT joins RTN_M013's explicit faction filter")
|
||||
provider_13_default = by_id[23]["movement_steps"][1]
|
||||
check(provider_13_default["movement_provider_selector"] == 13
|
||||
and provider_13_default["target_faction_filter"] == 0
|
||||
and "movement_parameter_1" not in provider_13_default,
|
||||
"RTINIT projects RTN_M013's any-foreign-faction default")
|
||||
provider_14 = by_id[130]["movement_steps"][5]
|
||||
check(provider_14["movement_provider_selector"] == 14
|
||||
and provider_14["provider_behavior"] == "retreat_from_nearby_enemies"
|
||||
and provider_14["maximum_threat_route_steps"] == 6,
|
||||
"RTINIT joins RTN_M014's threat-detection radius")
|
||||
undecoded_movement = by_id[2]["movement_steps"][1]
|
||||
check(undecoded_movement["movement_provider_selector"] == 2
|
||||
and "provider_behavior" not in undecoded_movement
|
||||
and "destination_tile_x" not in undecoded_movement,
|
||||
"RTINIT does not leak provider-specific meanings onto undecoded selectors")
|
||||
check(
|
||||
meta["movement_provider_parameter_schemas"]["11"]["parameter_fields"]
|
||||
|
||||
@@ -36,6 +36,8 @@ def test_load_and_lint():
|
||||
check(entries[0xbf6fe]["name"] == "pathfinding_filtered_route_scores"
|
||||
and entries[0xcc9f4]["name"] == "offensive_action_scope_masks",
|
||||
"movement target filtering and offensive scope are curated")
|
||||
check(entries[0x20543]["name"] == "tile_faction_traversal_masks",
|
||||
"faction-specific tile traversal masks are curated")
|
||||
check(entries[0xaba64]["name"] == "stage_object_runtime_flags",
|
||||
"stage-object targeting flags are curated")
|
||||
check(entries[0xb240e]["name"] == "pathfinding_movement_costs",
|
||||
|
||||
@@ -133,6 +133,7 @@ def main() -> int:
|
||||
"decoded_movement_step_count": 1,
|
||||
"decoded_movement_parameter_count": 2,
|
||||
"decoded_movement_defaulted_parameter_count": 1,
|
||||
"ignored_movement_parameter_count": 1,
|
||||
"movement_provider_scripts": {"1": "RTN_M001.BIN"},
|
||||
"battle_provider_scripts": {"1": "RTN_B001.BIN"},
|
||||
"used_movement_provider_selectors": [1],
|
||||
@@ -165,6 +166,7 @@ def main() -> int:
|
||||
assert banked_summary["decoded_movement_step_count"] == 1
|
||||
assert banked_summary["decoded_movement_parameter_count"] == 2
|
||||
assert banked_summary["decoded_movement_defaulted_parameter_count"] == 1
|
||||
assert banked_summary["ignored_movement_parameter_count"] == 1
|
||||
|
||||
messages = profile.profile_messages(fixture)
|
||||
assert messages["population"] == 1
|
||||
|
||||
Reference in New Issue
Block a user