Classify stage authoring difficulty tiers

This commit is contained in:
gamer147
2026-07-23 23:45:46 -04:00
parent 796b0e850e
commit f855c3d01f
12 changed files with 81 additions and 30 deletions

View File

@@ -1,7 +1,7 @@
<!-- DO NOT EDIT -- generated from vm-map/globals.toml by tools/globals_build.py --build -->
# 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. |

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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)*

View File

@@ -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",

View File

@@ -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([

View File

@@ -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",
)

View File

@@ -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"

View File

@@ -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",

View File

@@ -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"