Decode RTINIT movement providers 7 10 and 12
This commit is contained in:
@@ -102,6 +102,62 @@ MOVEMENT_PROVIDER_PARAMETER_SCHEMAS = {
|
||||
"destination tile (or its linked type-6 stage-object exit tile)"
|
||||
),
|
||||
},
|
||||
7: {
|
||||
"behavior": "approach_injured_ally",
|
||||
"parameter_fields": {
|
||||
"movement_parameter_1": "maximum_target_route_steps",
|
||||
"movement_parameter_2": "maximum_target_hp_percent",
|
||||
},
|
||||
"parameter_notes": {
|
||||
"maximum_target_route_steps": (
|
||||
"maximum flood-fill step distance from the acting entity; "
|
||||
"shipped values are 5 or 10"
|
||||
),
|
||||
"maximum_target_hp_percent": (
|
||||
"inclusive current-HP percentage cutoff; shipped values are "
|
||||
"50, 70, or 80"
|
||||
),
|
||||
},
|
||||
"target_selection": (
|
||||
"nearest active non-self entity of the same faction whose current "
|
||||
"HP percentage is at or below the cutoff; choose randomly among "
|
||||
"ties, then approach a reachable tile nearest that ally"
|
||||
),
|
||||
"completion": (
|
||||
"advance the current step's progress counter after producing a "
|
||||
"valid movement destination toward the selected ally"
|
||||
),
|
||||
},
|
||||
10: {
|
||||
"behavior": "approach_healing_feather",
|
||||
"parameter_fields": {
|
||||
"movement_parameter_1": "resource_index",
|
||||
"movement_parameter_2": "maximum_resource_percent",
|
||||
},
|
||||
"parameter_defaults": {
|
||||
"movement_parameter_1": 0,
|
||||
},
|
||||
"parameter_notes": {
|
||||
"resource_index": (
|
||||
"0=HP, 1=SP, 2=FS; all shipped RTINIT cells are unwritten and "
|
||||
"therefore use the zero/HP default"
|
||||
),
|
||||
"maximum_resource_percent": (
|
||||
"inclusive current/max percentage cutoff; shipped values are "
|
||||
"30 or 50"
|
||||
),
|
||||
},
|
||||
"target_selection": (
|
||||
"nearest active stage object of OBINIT type 15 (Healing Feather) "
|
||||
"or 16 (single-use red Healing Feather), then approach a reachable "
|
||||
"tile nearest that object"
|
||||
),
|
||||
"completion": (
|
||||
"produce a movement result only when the selected resource's "
|
||||
"maximum is nonzero, its current percentage is at or below the "
|
||||
"cutoff, and a reachable Healing Feather exists"
|
||||
),
|
||||
},
|
||||
11: {
|
||||
"behavior": "cycle_destination_waypoints",
|
||||
"parameter_fields": {
|
||||
@@ -125,6 +181,22 @@ MOVEMENT_PROVIDER_PARAMETER_SCHEMAS = {
|
||||
"linked type-6 stage-object exit tile)"
|
||||
),
|
||||
},
|
||||
12: {
|
||||
"behavior": "approach_destination_tile_avoiding_foreign_entities",
|
||||
"parameter_fields": {
|
||||
"movement_parameter_1": "destination_tile_x",
|
||||
"movement_parameter_2": "destination_tile_y",
|
||||
},
|
||||
"routing": (
|
||||
"same destination and completion logic as RTN_M005, but MVSEEK "
|
||||
"mode 2 masks the doubled-coordinate terrain cells occupied by "
|
||||
"active entities of another faction before its flood fill"
|
||||
),
|
||||
"completion": (
|
||||
"advance the current step's progress counter after reaching the "
|
||||
"destination tile (or its linked type-6 stage-object exit tile)"
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
UNIT_STAT_COLUMNS = (
|
||||
@@ -937,19 +1009,24 @@ def _movement_provider_names(names: dict[int, str]) -> dict[int, str]:
|
||||
return providers
|
||||
|
||||
|
||||
def _join_movement_provider_semantics(step: dict) -> int:
|
||||
def _join_movement_provider_semantics(step: dict) -> tuple[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
|
||||
return 0, 0
|
||||
step["provider_behavior"] = schema["behavior"]
|
||||
joined = 0
|
||||
defaulted = 0
|
||||
defaults = schema.get("parameter_defaults", {})
|
||||
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
|
||||
elif raw_field in defaults:
|
||||
step[semantic_field] = defaults[raw_field]
|
||||
defaulted += 1
|
||||
return joined, defaulted
|
||||
|
||||
|
||||
def extract_banked(scr):
|
||||
@@ -969,6 +1046,7 @@ def extract_banked(scr):
|
||||
bank_cells: dict[int, set[tuple[int, int]]] = collections.defaultdict(set)
|
||||
decoded_movement_step_count = 0
|
||||
decoded_movement_parameter_count = 0
|
||||
decoded_movement_defaulted_parameter_count = 0
|
||||
|
||||
for offset, destination, value, bank_index, record_id, slot in writes:
|
||||
bank_base = ROUTINE_BANK_ROOT + bank_index * ROUTINE_BANK_SPAN
|
||||
@@ -1016,10 +1094,16 @@ def extract_banked(scr):
|
||||
if selector is not None else {}
|
||||
),
|
||||
}
|
||||
joined_parameter_count = _join_movement_provider_semantics(step)
|
||||
(
|
||||
joined_parameter_count,
|
||||
defaulted_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
|
||||
decoded_movement_defaulted_parameter_count += (
|
||||
defaulted_parameter_count
|
||||
)
|
||||
movement_steps.append(step)
|
||||
|
||||
battle = {
|
||||
@@ -1122,6 +1206,9 @@ def extract_banked(scr):
|
||||
),
|
||||
"decoded_movement_step_count": decoded_movement_step_count,
|
||||
"decoded_movement_parameter_count": decoded_movement_parameter_count,
|
||||
"decoded_movement_defaulted_parameter_count": (
|
||||
decoded_movement_defaulted_parameter_count
|
||||
),
|
||||
"battle_provider_scripts": {
|
||||
str(selector): name
|
||||
for selector, name in sorted(battle_providers.items())
|
||||
|
||||
@@ -245,6 +245,9 @@ def profile_banked(data: dict) -> dict:
|
||||
"decoded_movement_parameter_count": data.get(
|
||||
"decoded_movement_parameter_count", 0
|
||||
),
|
||||
"decoded_movement_defaulted_parameter_count": data.get(
|
||||
"decoded_movement_defaulted_parameter_count", 0
|
||||
),
|
||||
"available_battle_provider_count": len(
|
||||
data.get("battle_provider_scripts", {})
|
||||
),
|
||||
@@ -402,7 +405,10 @@ def render_markdown(data: dict, rows: list[dict], limit: int) -> str:
|
||||
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"{banked_profile['decoded_movement_parameter_count']} populated "
|
||||
f"parameters + "
|
||||
f"{banked_profile['decoded_movement_defaulted_parameter_count']} "
|
||||
f"explicit defaults "
|
||||
f"across {banked_profile['decoded_movement_provider_count']} providers",
|
||||
f"- joined battle steps/used providers: "
|
||||
f"{banked_profile['battle_step_count']}/"
|
||||
|
||||
@@ -371,9 +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,
|
||||
check(meta["decoded_movement_provider_count"] == 5
|
||||
and meta["decoded_movement_step_count"] == 292
|
||||
and meta["decoded_movement_parameter_count"] == 758
|
||||
and meta["decoded_movement_defaulted_parameter_count"] == 7,
|
||||
"RTINIT reports selector-specific semantic coverage")
|
||||
check([
|
||||
layout["bank_index"]
|
||||
@@ -413,6 +414,26 @@ def test_real_routine_banks() -> None:
|
||||
and provider_11["waypoint_ordinal"] == 1
|
||||
and provider_11["path_cost_limit_override"] == 2,
|
||||
"RTINIT joins all four RTN_M011 waypoint parameters")
|
||||
provider_7 = by_id[33]["movement_steps"][4]
|
||||
check(provider_7["movement_provider_selector"] == 7
|
||||
and provider_7["provider_behavior"] == "approach_injured_ally"
|
||||
and provider_7["maximum_target_route_steps"] == 5
|
||||
and provider_7["maximum_target_hp_percent"] == 80,
|
||||
"RTINIT joins RTN_M007 injured-ally search semantics")
|
||||
provider_10 = by_id[99]["movement_steps"][0]
|
||||
check(provider_10["movement_provider_selector"] == 10
|
||||
and provider_10["provider_behavior"] == "approach_healing_feather"
|
||||
and provider_10["resource_index"] == 0
|
||||
and provider_10["maximum_resource_percent"] == 50
|
||||
and "movement_parameter_1" not in provider_10,
|
||||
"RTINIT projects RTN_M010's implicit HP default beside the raw banks")
|
||||
provider_12 = by_id[168]["movement_steps"][0]
|
||||
check(provider_12["movement_provider_selector"] == 12
|
||||
and provider_12["provider_behavior"]
|
||||
== "approach_destination_tile_avoiding_foreign_entities"
|
||||
and provider_12["destination_tile_x"] == 11
|
||||
and provider_12["destination_tile_y"] == 68,
|
||||
"RTINIT joins RTN_M012's route-mode destination semantics")
|
||||
check("provider_behavior" not in movement
|
||||
and "destination_tile_x" not in movement,
|
||||
"RTINIT does not leak provider-specific meanings onto undecoded selectors")
|
||||
@@ -426,6 +447,11 @@ def test_real_routine_banks() -> None:
|
||||
},
|
||||
"RTINIT publishes the reusable RTN_M011 parameter schema",
|
||||
)
|
||||
check(
|
||||
meta["movement_provider_parameter_schemas"]["10"]["parameter_defaults"]
|
||||
== {"movement_parameter_1": 0},
|
||||
"RTINIT publishes RTN_M010's implicit resource-index default",
|
||||
)
|
||||
semantics = extract_init.field_semantics(records)
|
||||
check(
|
||||
semantics["0xeff78/20/0"]
|
||||
|
||||
@@ -25,8 +25,14 @@ def test_load_and_lint():
|
||||
check(entries[0x5231f]["name"] == "entity_tile_x"
|
||||
and entries[0x52351]["name"] == "entity_tile_y",
|
||||
"entity map-coordinate arrays are curated")
|
||||
check(entries[0x4e021]["name"] == "entity_runtime_flags"
|
||||
and entries[0x522ed]["name"] == "entity_faction_ids",
|
||||
"entity activity and faction arrays are curated")
|
||||
check(entries[0x56b20]["name"] == "entity_patrol_waypoint_indices",
|
||||
"RTN_M011 waypoint state is curated")
|
||||
check(entries[0xaba96]["name"] == "pathfinding_remaining_route_steps"
|
||||
and entries[0xcc9f1]["name"] == "movement_search_mode",
|
||||
"movement-search reachability 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)
|
||||
|
||||
@@ -132,6 +132,7 @@ def main() -> int:
|
||||
"decoded_movement_provider_count": 1,
|
||||
"decoded_movement_step_count": 1,
|
||||
"decoded_movement_parameter_count": 2,
|
||||
"decoded_movement_defaulted_parameter_count": 1,
|
||||
"movement_provider_scripts": {"1": "RTN_M001.BIN"},
|
||||
"battle_provider_scripts": {"1": "RTN_B001.BIN"},
|
||||
"used_movement_provider_selectors": [1],
|
||||
@@ -163,6 +164,7 @@ def main() -> int:
|
||||
assert banked_summary["decoded_movement_provider_count"] == 1
|
||||
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
|
||||
|
||||
messages = profile.profile_messages(fixture)
|
||||
assert messages["population"] == 1
|
||||
|
||||
Reference in New Issue
Block a user