Chasing what supplies the EX_ mode word. Two routes, both measured to have no power, plus one structural fact that did come out. The offset route is dead. stw ..., 184(rN) occurs 301 times in the image, lwz from +184 occurs 351 times, and 115 functions touch both +144 and +184. +184 is an ordinary small offset shared by many unrelated classes - the same shape the corpus already recorded as failing three times. Nothing narrows to a writer. The bigger result is an instrument refutation with corpus-wide reach. Asking xrefs for the callers of the two screens returns 633 sources for EACH, and the two lists are IDENTICAL, which cannot be right. Measuring the relation itself: ind_call rows 1827297 distinct targets 1710 distinct sources 6992 targets with EXACTLY 633 sources 236 236 different functions sharing an identical source count is the signature of an unresolved-indirect-call cross product, not of a call graph. Any reading that treats an ind_call edge as "X calls Y" is void, here and anywhere else in the corpus it may have been used. The control shows the other kinds are sound: sub_82286BC8 has exactly one caller, kind call. And both EX_ screens have ZERO non-ind_call edges - they are reached only through function pointers, which is why the direct graph is empty for them. What did come out: scanning the entries of all 1150 catalogued vtables in the flat .pe, both EX_ screens are slot 1 of their own class - sub_822814D8 in ANON_Class_271D5F25 and sub_8227A3A0 in ANON_Class_CAA8AD62 - while sub_82286BC8 is in no catalogued vtable at all. So the 2/0/0/1 partition from the previous commit reflects a structural difference rather than a coincidence: the two screens that select on the mode word are vtable methods of their own classes and the one that does not select is not a vtable method. Still not settled: what writes +184. Both the offset sweep and the call graph are exhausted for it. A route with actual power would be the RTTI behind those two anonymous classes, or a runtime watch on the field - not another static offset search. All seventeen artefacts byte-identical.
187 lines
9.0 KiB
Markdown
187 lines
9.0 KiB
Markdown
# ✅ The debriefing and pilot-record screens — `STAGE_RESULT`, `OVERVIEW`, `EX_OVERVIEW`
|
|
|
|
- **Where:** `tables.pak` (the menu config pak), records `STAGE_RESULT`, `OVERVIEW`,
|
|
`EX_OVERVIEW`; key lists compiled into the executable at `sub_822814D8` and
|
|
`sub_8227A3A0` (found by the base-solver, [[player-tuning-tables]]).
|
|
- **Related:** [[mission-scoring]] owns the *rules* (`Score_*` in the stage
|
|
settings); this is the *readout*. [[savegame-format]] documents the same
|
|
compiled-key-list shape for the LOAD/SAVE screen.
|
|
|
|
## The shape was already known — for other screens
|
|
|
|
[[savegame-format]] establishes it: *"the LOAD/SAVE screen's config key list is
|
|
compiled into the executable, as a pointer array of key strings — the same shape
|
|
as the Arsenal's (whose keys sit a few hundred bytes earlier and match the pak
|
|
record exactly)"*, run starting `0x820A0074`. **`sub_82286BC8`'s 18-name block is
|
|
exactly that list** — 17 of its 18 are a `tables.pak` field or record name, so it
|
|
is the documented save-screen list and nothing new. Its lone residual is
|
|
**`PLAYER_AMMO_LESS_10`**, which is not a `tables.pak` name at all.
|
|
|
|
## ✅ Two more screens, and the key lists match their records exactly
|
|
|
|
| block | names | all present in `tables.pak`? |
|
|
|---|---:|---|
|
|
| `sub_822814D8` | 24 | **24 / 24** |
|
|
| `sub_8227A3A0` | 21 | **21 / 21** |
|
|
|
|
And the arithmetic closes against the records themselves:
|
|
|
|
* **`sub_822814D8` = the debriefing screen.** 24 names = 2 screen ids
|
|
(`STAGE_RESULT`, `EX_STAGE_RESULT`) + **21 `stage_*` fields** + one sound cue
|
|
(`SE_BOSS_CORE_CHARGE`). The `tables.pak` record `STAGE_RESULT` has **exactly
|
|
21 fields** (2 objects, both 21).
|
|
* **`sub_8227A3A0` = the pilot record / career screen.** 21 names = 5 screen ids
|
|
(`LAST_RESULT`, `EX_BASE`, `EX_MAIN`, `EX_OVERVIEW`, `OVERVIEW`) + **7
|
|
`ex_overview_*`** + **9 `overview_*`**. The records `EX_OVERVIEW` and
|
|
`OVERVIEW` have **exactly 7 and 9 fields** (2 objects each).
|
|
|
|
`2+21+1 = 24` and `5+7+9 = 21`, with 21/7/9 measured independently off the pak.
|
|
|
|
### The debriefing readout, in full
|
|
|
|
```
|
|
stage_num_shoot_down_aircrafts stage_points_shoot_down_aircrafts
|
|
stage_num_shoot_down_ships stage_points_shoot_down_ships
|
|
stage_points_shoot_down_others
|
|
stage_weight_shoot_down
|
|
stage_num_main_objectives stage_points_main_objectives
|
|
stage_num_sub_objectives stage_points_sub_objectives
|
|
stage_clear_time stage_points_clear_time
|
|
stage_shoot_down_ratio stage_points_shoot_down_ratio
|
|
stage_damages_friendly_ships stage_points_damages_friendly_ships
|
|
stage_damages_wingman stage_points_damages_wingman
|
|
stage_points_friendly_fire
|
|
stage_total_points stage_rank
|
|
```
|
|
|
|
🔑 **Nine `num`/`points` pairs plus three points-only lines** — the screen shows
|
|
a raw count *and* its score contribution for kills by class, objectives, time,
|
|
ratio and both damage categories; `friendly_fire`, `shoot_down_others` and
|
|
`weight` have no counter column. That is the same partition
|
|
[[mission-scoring]] measures on the settings side.
|
|
|
|
### The career readout
|
|
|
|
`overview_points`, `_total_play_time`, `_clear_stages`, `_retry_times`,
|
|
`_shoot_down_aircrafts`, `_shoot_down_ships`, `_shoot_down_weight` — seven, and
|
|
`OVERVIEW` adds **`overview_rank`** and **`overview_medals`**. The `EX_` twin has
|
|
the seven and neither of the two, which is what makes `EX_` the reduced variant
|
|
rather than a different screen.
|
|
|
|
🟡 Not settled: what `EX_` means (a second profile? the online/leaderboard
|
|
variant?) — **but see
|
|
[stage-numbering-and-player-craft](stage-numbering-and-player-craft.md):
|
|
`_EXn` on an asset name is measured to mean "the n-th challenge mission", which
|
|
makes "`EX_` = the challenge-mission debriefing" a consistent reading of why
|
|
this twin drops `overview_rank` and `overview_medals`. Not adopted — the
|
|
selection has not been shown** — `EX_BASE`, `EX_MAIN`, `EX_STAGE_RESULT` and `EX_FONT` are keys with
|
|
**no record of that name**, so the prefix is a screen-id convention, not a table.
|
|
And `PLAYER_AMMO_LESS_10` is unexplained.
|
|
|
|
**Reproduce:** parse `tables.pak` with `tools/re-capture/unitgroup.py` and count
|
|
the fields of the three records; take the key lists from the `sub_822814D8` /
|
|
`sub_8227A3A0` / `sub_82286BC8` sections of [`../data/name-block-bases.txt`](../data/name-block-bases.txt).
|
|
|
|
## ✅ The `EX_` selection, shown at last — a mode word tested against 3
|
|
|
|
The section above left this open: "**the selection has not been shown**". It is
|
|
shown now. Both screens that reference `EX_` names carry the *same five-instruction
|
|
idiom* immediately before the choice — `sub_822814D8` at `0x822815b4` and
|
|
`sub_8227A3A0` at `0x8227a408`:
|
|
|
|
```asm
|
|
lwz r11, 4(rX) ; the owning object
|
|
lwz r11, 184(r11) ; a mode word at +184
|
|
addi r11, r11, -3
|
|
cntlzw r11, r11
|
|
rlwinm r11, r11, 27, 31, 31 ; r11 = 1 iff [+184] == 3
|
|
cmpli cr6, 0, r11, 0x0
|
|
bc 12, 4*cr6+eq, <plain> ; not 3 -> STAGE_RESULT / OVERVIEW
|
|
; else -> EX_STAGE_RESULT / EX_OVERVIEW
|
|
```
|
|
|
|
`cntlzw` of zero is 32; rotating left 27 lifts bit 5 into bit 31, so the sequence
|
|
is a branchless **"equals 3"** test. The equal-to-zero branch takes the *plain*
|
|
name, so **`EX_` is chosen exactly when the word is 3**.
|
|
|
|
A small partition over the three key-list loaders the corpus already names:
|
|
|
|
| | loads `+184` | does not |
|
|
|---|---|---|
|
|
| **references `EX_` names** | **2** | 0 |
|
|
| **does not** | 0 | **1** |
|
|
|
|
`sub_822814D8` and `sub_8227A3A0` both load it; `sub_82286BC8`, which references
|
|
no `EX_` name, has no `+184` access at all. Both off-diagonal cells are empty.
|
|
|
|
### 🔑 3 is the game's own `EXTRA` — and that is *not* `CHALLENGE`
|
|
|
|
[[challenge-mission-gate]] already established the vocabulary for this constant
|
|
at a three-way switch: `== 3` → **`EXTRA`**, `== 5 or 6` → **`CHALLENGE`**,
|
|
otherwise `FILE`. So the prefix reads literally: **`EX_` is the `EXTRA` variant**.
|
|
|
|
⚠️ **This refines the tentative reading above rather than confirming it.** That
|
|
section proposed "`EX_` = the challenge-mission debriefing" by analogy with
|
|
`_EXn` on asset names. The measured constant is `EXTRA`, which in the game's own
|
|
three-way vocabulary is a *different* kind from `CHALLENGE` (5/6). `EX_` tracks
|
|
`EXTRA` specifically.
|
|
|
|
⚠️ **The boundary of what was measured:** the `EXTRA = 3` constant is established
|
|
at **`+144`** of the stage-loader class, while the word tested here is at
|
|
**`+184`** of `[object + 4]`. Same constant, same `EX`/`EXTRA` naming, two
|
|
different offsets in what may be two different objects — **that the two fields
|
|
are the same field is not shown**, only that both compare a mode word against 3.
|
|
|
|
## 🔴 Who writes `+184` — blocked, by two independent instrument failures
|
|
|
|
The next question after the selector is what *supplies* the mode. Two routes
|
|
were tried and both are measured to have no power here.
|
|
|
|
**The offset route has no power.** `stw …, 184(rN)` occurs **301 times** in the
|
|
image, `lwz` from `+184` **351 times**, and **115 functions touch both `+144`
|
|
and `+184`**. `+184` is an ordinary small offset shared by many unrelated
|
|
classes — the same shape that the corpus already recorded as failing three
|
|
times. Nothing here narrows to a writer.
|
|
|
|
**❌ The `xrefs` `ind_call` edges are a cross product — do not use them as
|
|
callers.** Asking for the callers of the two screens returns **633 sources for
|
|
each, and the two lists are identical**, which cannot be right. Measuring the
|
|
relation itself:
|
|
|
|
| | |
|
|
|---|---|
|
|
| `ind_call` rows | **1 827 297** |
|
|
| distinct targets | 1 710 |
|
|
| distinct sources | 6 992 |
|
|
| **targets with *exactly* 633 sources** | **236** |
|
|
|
|
236 different functions sharing the identical source count is the signature of
|
|
an unresolved-indirect-call cross product, not of a call graph. ⚠️ **Any reading
|
|
that treats an `ind_call` edge as "X calls Y" is void**, here and elsewhere in
|
|
the corpus.
|
|
|
|
⚠️ The control shows the *other* kinds are fine: `sub_82286BC8` has exactly
|
|
**one** caller, kind `call`. And both `EX_` screens have **zero** non-`ind_call`
|
|
edges — they are reached only through function pointers, which is why the direct
|
|
graph is empty for them.
|
|
|
|
### ✅ What did come out: both `EX_` screens are slot 1 of their own class
|
|
|
|
Scanning the 1 150 catalogued vtables' entries in the flat `.pe`:
|
|
|
|
| function | vtable | slot |
|
|
|---|---|---|
|
|
| `sub_822814D8` (debriefing) | `ANON_Class_271D5F25` | **1** |
|
|
| `sub_8227A3A0` (career) | `ANON_Class_CAA8AD62` | **1** |
|
|
| `sub_82286BC8` (save) | *in no catalogued vtable* | — |
|
|
|
|
So each `EX_`-selecting screen is a class whose **slot-1 virtual** is its
|
|
key-list builder, and the third — which selects nothing — is not a vtable method
|
|
at all. That is consistent with the 2 / 0 / 0 / 1 partition above arriving from a
|
|
structural difference rather than a coincidence.
|
|
|
|
🔴 **Still not settled: what writes `+184`.** Both the offset sweep and the call
|
|
graph are exhausted for it. A route with actual power would be the RTTI class
|
|
behind `ANON_Class_271D5F25` / `_CAA8AD62`, or a runtime watch on the field —
|
|
not another static offset search.
|