From f855c3d01ff9408b86c94cb404aa1acfe698cca3 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Thu, 23 Jul 2026 23:45:46 -0400 Subject: [PATCH] Classify stage authoring difficulty tiers --- docs/global-reference.md | 3 ++- docs/name-resolution.md | 5 +++-- docs/phase-a-slice-plan.md | 10 ++++++---- docs/script-inventory.md | 5 +++-- docs/tools-reference.md | 3 ++- docs/vm-mapping-plan.md | 4 ++-- tools/extract_init.py | 27 +++++++++++++++++++-------- tools/init_table_profile.py | 12 ++++++++---- tools/test_extract_init.py | 17 ++++++++++++++--- tools/test_globals.py | 6 ++++-- tools/test_init_table_profile.py | 8 +++++++- vm-map/globals.toml | 11 +++++++++++ 12 files changed, 81 insertions(+), 30 deletions(-) diff --git a/docs/global-reference.md b/docs/global-reference.md index f89eb18..fc6acfc 100644 --- a/docs/global-reference.md +++ b/docs/global-reference.md @@ -1,7 +1,7 @@ # Global Variable Reference (generated) -5054 globals (417 curated, 4637 auto shape-inferred). Source of truth: `vm-map/globals.toml`. +5055 globals (418 curated, 4637 auto shape-inferred). Source of truth: `vm-map/globals.toml`. ## choice-output @@ -247,6 +247,7 @@ | `0xed095` | stage_map_max_tile_y | high | investigation | STINIT2's inclusive bottom map bound indexed by stage id. The maximum shipped value 800 explains MPINIT's final authored doubled grid row at Y 1600. | | `0xed47d` | stage_minimap_atlas_origin_y | high | investigation | FIELD and SELSTAGE copy this to the minimap drawing origin. SELSTAGE subtracts it from stage_map_min_tile_y to crop the selected stage's vertically packed minimap strip and labels the following 150 atlas rows. | | `0xed865` | stage_clear_base_spendable_point_rewards | high | investigation | STAGECLEAR multiplies this base award by the turn/party-performance percentage, divides by 100, adds any card bonus, and credits the capped shared_spendable_points balance. | +| `0xedc4d` | stage_authoring_difficulty_tiers | med | inference | Authoring-only stage challenge/progression tier. No shipped script reads the array and the native engine can only reach VM globals through script operands, so it has no runtime effect. The ordinal correlates strongly with base clear points and encounter progression: late story maps are tier 6, EX maps use tiers 5 and 7, and the final EX map alone is tier 8. The descriptive name is correlation-based. | | `0xee035` | stage_scjump_decision_ids | high | investigation | FORT dispatches column zero before entering a stage or for an event-only row. FIELD and a few scene return paths dispatch column one after clear and column two after failure or forced-retreat outcomes; all 174 populated references resolve through SCINIT. Columns: 0=entry, 1=clear, 2=failure. | | `0xeebed` | stage_extra_dungeon_flags | high | investigation | Marks the eight shipped EX dungeons. FORT, SELSTAGE, and management screens combine it with stage_clear_state when deciding whether to show and apply the stage's post-clear coin rewards. | | `0xeefd5` | stage_clear_coin_quantities | high | investigation | FORT and SELSTAGE render the three clear-reward quantities beside coin icons. Management screens grant the same values as item ids 91..93 after a cleared or EX-marked stage. Columns: 0=bronze_coin_item_91, 1=silver_coin_item_92, 2=gold_coin_item_93. | diff --git a/docs/name-resolution.md b/docs/name-resolution.md index 0eeab2f..a4226c7 100644 --- a/docs/name-resolution.md +++ b/docs/name-resolution.md @@ -691,8 +691,9 @@ The v1 map labels *shapes and tables*; the next increments add *meaning*, cheape effects, with skill and weapon-class selection joined. STINIT2 closes 74 real stage rows (correcting 247 description strings formerly misread as records), six pre/post-clear text slots, progression and story gates, map/minimap geometry, rewards, and 174 resolved entry/clear/failure transitions. With - RTINIT's used-provider surface closed and ITMES/SKMES/VIMES/EIMES/CIMES/MAMES joined and INFOMES/MES - classified, use live/native observation for STINIT2's sole script-unconsumed `0xedc4d` column next; + RTINIT's used-provider surface closed, ITMES/SKMES/VIMES/EIMES/CIMES/MAMES joined, INFOMES/MES + classified, and STINIT2's engine-dead authoring tier retained at medium confidence, continue with + consumer-led tables that still have shipped readers; never assign one universal meaning to a parameter bank whose meaning varies by provider selector. 2. **Label remaining 2D record tables by their readers** — cross-reference which scripts read each `rec[sN]` table and infer purpose from context, preserving reserved rows and sparse cells. RECOVER's diff --git a/docs/phase-a-slice-plan.md b/docs/phase-a-slice-plan.md index f6c381e..fdf9d45 100644 --- a/docs/phase-a-slice-plan.md +++ b/docs/phase-a-slice-plan.md @@ -3215,8 +3215,10 @@ to the shared `STINIT.BIN` selector (74/74). Regressions protect the corrected record geometry, complete instruction accounting, all six text slots, display kinds, story gates, unlock groups, map/minimap coordinates, rewards, SCJUMP joins, -and loader joins. The 66 populated values at `0xedc4d` have no script consumer and remain explicit -as unresolved provenance. +and loader joins. The 66 populated values at `0xedc4d` have no script consumer. Their ordinal 1..8 +distribution tracks authored challenge/reward progression: late story maps are tier 6, EX maps use +tiers 5 and 7, and the final EX map alone is tier 8. Because the native engine can reach VM globals +only through script operands, the array is now named `stage_authoring_difficulty_tiers` at medium +confidence and explicitly classified as engine-dead authoring metadata. -**Next:** use live/native observation only to settle STINIT2's unconsumed `0xedc4d` column; the -remaining script-visible stage-definition surface is closed. +**Next:** continue consumer-led naming of the remaining two-dimensional tables; STINIT2 is closed. diff --git a/docs/script-inventory.md b/docs/script-inventory.md index a736b4d..b91eb2d 100644 --- a/docs/script-inventory.md +++ b/docs/script-inventory.md @@ -179,8 +179,9 @@ all 296 populated lines divide into three uncleared and three cleared slots. The contains main-progression/EX flags, seven required and seven forbidden story gates, shared unlock groups, display numbers, map/minimap geometry, performance-scaled point and bronze/silver/gold coin rewards, and entry/clear/failure SCJUMP decisions. All 174 transitions resolve through SCINIT and -all 74 loader ids resolve to STINIT.BIN. The populated `0xedc4d` column has no script reader and -remains explicitly unresolved. +all 74 loader ids resolve to STINIT.BIN. The populated `0xedc4d` column has no script reader; its +1..8 progression/reward correlation is retained as the medium-confidence, engine-dead +`stage_authoring_difficulty_tiers` array. RTINIT is a seventh banked shape. Its 3,336 writes populate twenty parallel `1000 × 20` banks: ten movement banks consumed by MVRTN/RTN_M providers and ten battle banks consumed by BTRTN/RTN_B providers. diff --git a/docs/tools-reference.md b/docs/tools-reference.md index a1c61e5..77b569a 100644 --- a/docs/tools-reference.md +++ b/docs/tools-reference.md @@ -155,7 +155,8 @@ matrix, retaining three uncleared and three cleared lines per stage. The schema event-only, and EX presentation; main-progression, unlock-group, and seven-column story gates; tile/doubled-grid/minimap geometry; point and bronze/silver/gold coin rewards; all 174 entry/clear/failure decisions through SCINIT; and all 74 loader ids to STINIT.BIN. The 66 populated -cells at `0xedc4d` remain raw and explicitly unresolved because no script reads the column. +cells at `0xedc4d` are exposed as medium-confidence authoring-only difficulty tiers: their 1..8 +ordering tracks challenge/reward progression, but no shipped script reads the array. Footer-mode MPINIT classifies all 1,472 copies as fifty-cell rows within one stride-53 terrain atlas. It derives grid Y from each destination, retains 127 implicit-zero gaps, and joins STINIT2's inclusive diff --git a/docs/vm-mapping-plan.md b/docs/vm-mapping-plan.md index a143fc6..67f1eaa 100644 --- a/docs/vm-mapping-plan.md +++ b/docs/vm-mapping-plan.md @@ -68,9 +68,9 @@ - [x] **2.3k — Extract `CDINIT`'s card-generation lists.** Numeric/dispatch specialization classifies all 1,565 instructions into nine selectors and 383 weighted candidates, joins every card to CDINIT2, and links seven live selectors to 246 STINIT card objects. FIELD proves the turn-scaled cumulative selection rule, 100-slot scan, 50-slot clear prefix, and two-column effective required-story-gate contract. - [x] **2.3l — Extract `CDINIT2`'s card definitions.** Name specialization classifies all 558 instructions into 81 cards inside a reserved 100-row registry. FIELD proves six type-specific effect paths and the engine-dead third required-flag column; ITINIT, SCINIT, ILINIT, and SYS4INI resolve all item, event, condition, and visual references, while STAGECLEAR proves the deferred point-award accumulator. - [x] **2.3m — Extract `BTANINIT`/`BTANINIT2`'s battle-animation registry.** BTANINIT2 specialization classifies all 1,018 instructions into 122 sparse six-effect timelines, 573 effect references, paired start delays, and 111 complete durations. BTANINIT dispatch specialization classifies all 4,472 instructions into 202 visual/audio/hit-pulse definitions. BTL proves movie/sprite playback, blend, anchor, geometry, sound, and pulse semantics; SKINIT/ITINIT resolve skill and weapon-class selection. -- [x] **2.3n — Extract `STINIT2`'s stage-definition registry.** Name/string-matrix specialization corrects the generic 321-record view to 74 sparse stages plus 296 strings in six pre/post-clear description slots, classifying all 1,634 instructions. FORT, FIELD, SELSTAGE, and STAGECLEAR prove main/EX flags, seven-column story gates, unlock groups, numbered/event presentation, map/minimap geometry, base point and three-coin rewards, and the loader contract. All 174 entry/clear/failure decisions resolve through SCINIT and all 74 loader ids resolve to STINIT; only the script-unconsumed `0xedc4d` column remains unnamed. +- [x] **2.3n — Extract `STINIT2`'s stage-definition registry.** Name/string-matrix specialization corrects the generic 321-record view to 74 sparse stages plus 296 strings in six pre/post-clear description slots, classifying all 1,634 instructions. FORT, FIELD, SELSTAGE, and STAGECLEAR prove main/EX flags, seven-column story gates, unlock groups, numbered/event presentation, map/minimap geometry, base point and three-coin rewards, and the loader contract. All 174 entry/clear/failure decisions resolve through SCINIT and all 74 loader ids resolve to STINIT. The remaining `0xedc4d` array has no shipped reader and is retained at medium confidence as the authoring-only stage difficulty tier: values 1..8 correlate with challenge/reward progression but have no runtime effect. - [x] **2.4 — Partial global-var map BUILT + wired into the disassembler.** `tools/global_map.py` → `build/global-var-map.{json,md}` (16,354/49,435 globals labelled: string tables, `*INIT` field arrays, 122 record tables w/ strides, current-entity index pointers). `sys4load` renders the labels inline (`=rec[s30]`, `=current-entity-index?`). See `docs/name-resolution.md`. -- [ ] **2.5 — Grow the global-var map (future, incremental).** Static first: RTINIT is closed at 1,043/1,043 movement steps; the RECOVER/ILINIT condition ABI, ALINIT recipes, AFINIT affinity/progression tables, CTINIT name palette, CVINIT character-voice registry, LAINIT terrain definitions, MPINIT stage-terrain atlas, SPINIT H-scene gallery, TRINIT training actions, CDINIT/CDINIT2 card selection/effects, BTANINIT/BTANINIT2 battle animations, and STINIT2's complete script-visible stage surface are closed; ITMES/SKMES/VIMES/EIMES/CIMES/MAMES are joined to their definitions; and the non-table INFOMES/MES ABIs are classified. Next, use live/native observation for STINIT2's sole script-unconsumed `0xedc4d` column, then continue remaining consumer-led 2D table naming. Full detail: `docs/name-resolution.md` → "Future step — growing the map". Packed `call-script` ids, SCJUMP decision-to-scene dispatch, the shipped RTINIT movement-provider join, and the six completed message joins are resolved. +- [ ] **2.5 — Grow the global-var map (future, incremental).** Static first: RTINIT is closed at 1,043/1,043 movement steps; the RECOVER/ILINIT condition ABI, ALINIT recipes, AFINIT affinity/progression tables, CTINIT name palette, CVINIT character-voice registry, LAINIT terrain definitions, MPINIT stage-terrain atlas, SPINIT H-scene gallery, TRINIT training actions, CDINIT/CDINIT2 card selection/effects, BTANINIT/BTANINIT2 battle animations, and STINIT2's complete stage surface are closed; ITMES/SKMES/VIMES/EIMES/CIMES/MAMES are joined to their definitions; and the non-table INFOMES/MES ABIs are classified. Next, continue remaining consumer-led 2D table naming. Full detail: `docs/name-resolution.md` → "Future step — growing the map". Packed `call-script` ids, SCJUMP decision-to-scene dispatch, the shipped RTINIT movement-provider join, and the six completed message joins are resolved. ## Phase 3 — Name the unnamed opcodes *(top ~20 BEFORE Phase 4; the rest on demand)* diff --git a/tools/extract_init.py b/tools/extract_init.py index 26baad3..662b28f 100644 --- a/tools/extract_init.py +++ b/tools/extract_init.py @@ -708,7 +708,7 @@ STAGE_DEFINITION_ARRAYS = { "map_max_tile_y": (MAP_STAGE_MAX_Y, 1), "minimap_atlas_origin_y": (0xED47D, 1), "clear_base_spendable_point_reward": (0xED865, 1), - "unresolved_parameter_0xedc4d": (0xEDC4D, 1), + "authoring_difficulty_tier": (0xEDC4D, 1), "scjump_decision_ids": (0xEE035, 3), "extra_dungeon_flag": (0xEEBED, 1), "clear_coin_quantities": (0xEEFD5, 3), @@ -6130,11 +6130,11 @@ def extract_stage_definitions(scr): if coin_quantities[column] ], } - unresolved = scalar( - "unresolved_parameter_0xedc4d", stage_id + difficulty_tier = scalar( + "authoring_difficulty_tier", stage_id ) - if unresolved: - record["unresolved_parameter_0xedc4d"] = unresolved + if difficulty_tier: + record["authoring_difficulty_tier"] = difficulty_tier records.append(record) field_counts = { @@ -6167,6 +6167,9 @@ def extract_stage_definitions(scr): "clear_base_spendable_point_reward": ( "stage_clear_base_spendable_point_rewards" ), + "authoring_difficulty_tier": ( + "stage_authoring_difficulty_tiers" + ), "scjump_decision_ids": "stage_scjump_decision_ids", "extra_dungeon_flag": "stage_extra_dungeon_flags", "clear_coin_quantities": "stage_clear_coin_quantities", @@ -6265,9 +6268,16 @@ def extract_stage_definitions(scr): "clear_coin_reward_cell_count": field_counts[ "clear_coin_quantities" ], - "unresolved_parameter_population": field_counts[ - "unresolved_parameter_0xedc4d" + "authoring_difficulty_tier_population": field_counts[ + "authoring_difficulty_tier" ], + "authoring_difficulty_tier_counts": { + str(tier): sum( + record.get("authoring_difficulty_tier") == tier + for record in records + ) + for tier in range(1, 9) + }, "consumer_contract": { "FORT.BIN": ( "enumerates named rows, applies required/forbidden story " @@ -6910,7 +6920,8 @@ def write_data_index(data_dir: Path) -> None: "slots, progression and story gates, numbered/EVENT/EX presentation, map and", "minimap geometry, point/coin rewards, and all 174 SCINIT-resolved entry, clear,", "and failure decisions. All rows resolve their shared STINIT loader reference;", - "the unconsumed 0xedc4d column remains explicit rather than receiving a guess.", + "the unconsumed 0xedc4d column is retained as a medium-confidence,", + "authoring-only difficulty tier rather than being assigned runtime behavior.", "", "MPINIT's dedicated terrain-atlas schema exposes 1,472 authored rows of a sparse", "53-column half-tile grid. It joins STINIT2's doubled tile-bound rectangles to 66", diff --git a/tools/init_table_profile.py b/tools/init_table_profile.py index ce0f824..62fb27c 100644 --- a/tools/init_table_profile.py +++ b/tools/init_table_profile.py @@ -615,8 +615,11 @@ def profile_stage_definitions(data: dict) -> dict: "clear_coin_reward_cell_count": data.get( "clear_coin_reward_cell_count", 0 ), - "unresolved_parameter_population": data.get( - "unresolved_parameter_population", 0 + "authoring_difficulty_tier_population": data.get( + "authoring_difficulty_tier_population", 0 + ), + "authoring_difficulty_tier_counts": data.get( + "authoring_difficulty_tier_counts", {} ), } @@ -818,8 +821,9 @@ def render_markdown(data: dict, rows: list[dict], limit: int) -> str: f"{stage_profile['stage_count']}", f"- clear coin reward cells: " f"{stage_profile['clear_coin_reward_cell_count']}", - f"- unresolved 0xedc4d cells: " - f"{stage_profile['unresolved_parameter_population']}", + f"- authoring-only difficulty tiers: " + f"{stage_profile['authoring_difficulty_tier_population']} " + f"({stage_profile['authoring_difficulty_tier_counts']})", ]) elif definition_profile := profile_card_definitions(data): lines.extend([ diff --git a/tools/test_extract_init.py b/tools/test_extract_init.py index 6dfa341..bd9db9b 100644 --- a/tools/test_extract_init.py +++ b/tools/test_extract_init.py @@ -2079,9 +2079,20 @@ def test_stage_definitions() -> None: "STINIT2 resolves the spendable-point and three coin reward columns", ) check( - meta["unresolved_parameter_population"] == 66 - and by_id[167]["unresolved_parameter_0xedc4d"] == 8, - "STINIT2 preserves the one still-unresolved populated stage column", + meta["authoring_difficulty_tier_population"] == 66 + and meta["authoring_difficulty_tier_counts"] + == { + "1": 7, + "2": 11, + "3": 17, + "4": 5, + "5": 12, + "6": 9, + "7": 4, + "8": 1, + } + and by_id[167]["authoring_difficulty_tier"] == 8, + "STINIT2 preserves the inferred authoring-only difficulty tiers", ) diff --git a/tools/test_globals.py b/tools/test_globals.py index 9a88a67..fe12a6e 100644 --- a/tools/test_globals.py +++ b/tools/test_globals.py @@ -124,8 +124,10 @@ def test_load_and_lint(): and entries[0xec0f5]["name"] == "stage_display_number_minor" and entries[0xed47d]["name"] == "stage_minimap_atlas_origin_y" and entries[0xed865]["name"] - == "stage_clear_base_spendable_point_rewards", - "STINIT2 stage numbering, minimap, and point rewards are curated") + == "stage_clear_base_spendable_point_rewards" + and entries[0xedc4d]["name"] + == "stage_authoring_difficulty_tiers", + "STINIT2 numbering, minimap, points, and authoring tiers are curated") check(entries[0xee035]["columns"] == {"0": "entry", "1": "clear", "2": "failure"} and entries[0xeebed]["name"] == "stage_extra_dungeon_flags" diff --git a/tools/test_init_table_profile.py b/tools/test_init_table_profile.py index 21db1e2..f2a9f9c 100644 --- a/tools/test_init_table_profile.py +++ b/tools/test_init_table_profile.py @@ -430,7 +430,12 @@ def main() -> int: "resolved_scjump_reference_count": 12, "resolved_loader_script_count": 5, "clear_coin_reward_cell_count": 4, - "unresolved_parameter_population": 4, + "authoring_difficulty_tier_population": 4, + "authoring_difficulty_tier_counts": { + "1": 1, + "2": 2, + "3": 1, + }, "records": [{}, {}, {}, {}, {}], } stage_summary = profile.profile_stage_definitions(stage_fixture) @@ -442,6 +447,7 @@ def main() -> int: assert "- stage definitions: 5/1000 rows" in rendered_stages assert "- mapped/event-only stages: 4/1" in rendered_stages assert "- SCJUMP joins: 12/12" in rendered_stages + assert "- authoring-only difficulty tiers: 4" in rendered_stages training_fixture = { "table": "TRAINING", diff --git a/vm-map/globals.toml b/vm-map/globals.toml index 30d3e9a..fea1615 100644 --- a/vm-map/globals.toml +++ b/vm-map/globals.toml @@ -3366,6 +3366,17 @@ source = "investigation" confidence = "high" depends_on = ["0x671b", "0x4dfbb"] +[[global]] +address = "0xedc4d" +name = "stage_authoring_difficulty_tiers" +category = "data-table" +type = "int[1000]" +value_domain = "ordinal 1..8 across all 66 mapped stages; event-only rows are zero" +usage = "Authoring-only stage challenge/progression tier. No shipped script reads the array and the native engine can only reach VM globals through script operands, so it has no runtime effect. The ordinal correlates strongly with base clear points and encounter progression: late story maps are tier 6, EX maps use tiers 5 and 7, and the final EX map alone is tier 8. The descriptive name is correlation-based." +source = "inference" +confidence = "med" +depends_on = ["0xed865", "0xe782f", "0xe784d", "0xeebed"] + [[global]] address = "0xee035" name = "stage_scjump_decision_ids"