# ๐ŸŸก The three built-ins that appear *inside* clear conditions โ€” read, not named [`isl-phase-guards`](isl-phase-guards.md) produced the per-phase clear conditions, but three of the built-ins in them were unread, so the conditions were only half-readable. All three are read now. **None of them is named** โ€” the corpus has withdrawn two names taken from usage shape, and what I read is not enough to name these. Implementations via the vtable at `0x820A84BC` (control: built-in 69 `unit_state` โ†’ slot 184 โ†’ `0x8226ADF0`, as recorded). | built-in | slot | implementation | |---|---|---| | **104** | 288 | `0x8226BFE0` | | **141** | 428 | `0x8226C9B8` | | **7** | 40 | `0x82266C68` | ## โœ… `builtin104` is a pure getter โ€” and it is the whole tutorial clear condition ``` 8226BFE0 lwz r11, 10160(r3) ; r3 = the ScriptPhase 8226BFE4 stw r11, 164(r3) ; -> special[0] 8226BFE8 blr ``` Three instructions. It returns **`[phase+10160]`** and nothing else. So all six tutorial stages (S18โ€“S23) end on the value of a **single engine-written word** โ€” which is exactly why their exits have one dominating condition each and why `isl-builtins.md` saw built-in 104 only ever inside a poll loop. **That word has exactly one writer** in the whole image: ``` 821AAD74 lwz r11, 104(r30) 821AAD84 lwz r11, 12(r11) 821AAD88 cmpi cr6, 0, r11, 16 ; skip if == 16 821AAD90 cmpi cr6, 0, r11, 32 ; skip if > 32 821AAD98 lwz r11, 4(r10) 821AAD9C stw r29, 10160(r11) ; <- the only write ``` inside `sub_821AA1B0`, gated on a type/kind field being in `(16, 32]`. `r29` there comes from `or r29, r3, r3` โ€” the return of a preceding call. ## โœ… (2026-08-27) NAMED โ€” built-in 104 is `request_next`, and the game says so itself `r29` is the return of a **getter on an object fetched from a registry by id**: ``` 821AAD1C lwz r4, 2424(r30) ; the registry 821AAD20 addis r5, r0, 0x20FF 821AAD28 ori r5, r5, 0xFF02 ; <- the id 821AAD38 bcctrl ; registry->slot1(&out, 0x20FFFF02) 821AAD40 lwz r11, 0(r3) ; lwz r11, 8(r11) 821AAD4C bcctrl ; r29 = obj->slot2() -- GET 821AAD9C stw r29, 10160(r11) ; -> [phase+10160] 821AADA0..DD4 ; then the SAME id again, obj->slot1(0) -- CLEAR ``` So the frame loop **reads the value, publishes it to the script, and clears it**. **The id namespace has exactly three members** โ€” `0x20FFFF00`, `0x20FFFF01`, `0x20FFFF02` โ€” each built at exactly 4 sites image-wide. Two of those sites are in `sub_821D5178`, which fetches `0x20FFFF01` and `0x20FFFF02`, calls the same `slot2()` getter on each, and passes both to a log call whose format string is at `0x820A4968`: > `silph::GamePart_ReadyRoom::Impl::OnCommand - Wait() command is requested.` > `Check flow control valiables. WAIT_MODE : %d, REQUEST_NEXT : %d` Argument order settles which is which: `r4` (first `%d`, **WAIT_MODE**) is `0x20FFFF01`'s value, `r5` (second, **REQUEST_NEXT**) is `0x20FFFF02`'s. > โ‡’ **`[phase+10160]` is `REQUEST_NEXT`, and built-in 104 returns it.** That is the game's own name for the variable, not a shape-based guess. Corroboration: `GamePart_ReadyRoom::Impl::PrepareScript` sets `WAIT_MODE = 1` and `REQUEST_NEXT = 0` before an ISL script runs, and `sub_821AA1B0` clears both each frame after reading. `[phase+10160]` has **exactly one writer and one reader** in the whole image (the two above); the three other hits on offset 10160 are `lfs` on unrelated objects. So the six tutorial stages' exit condition `request_next() != 1` is the script waiting on the surrounding game part's *proceed* flag โ€” which is why S18โ€“S23 each have exactly one dominating condition. ๐ŸŸก Still unread: what the `(16, 32]` gate on `[r30+104]`'s `+12` selects, and `0x20FFFF00`, the third id (used by the `GRAPH_PATH` / `EX_FONT` / `SYSTEM` code in `sub_821D6350` and `sub_821D6A40`). ## โœ… (2026-08-27) `[phase+10152]` and `[phase+10156]` are an ANSWER and a STATE The backlog carried these as "9 and 7 writers, unread". They are read now, and they are not `request_next`'s siblings-by-adjacency: they belong to a **trio of built-ins** sitting in four consecutive vtable stubs. | built-in | vtable slot | handler | body | |---|---|---|---| | **102** | 70 | `sub_8226BF48` | the state machine below | | **103** | 71 | `sub_8226BFA8` | returns `state != 0 && answer == 1` | | 104 | 72 | `sub_8226BFE0` | `request_next` (above) | | **130** | 97 | `sub_8226BFD0` | `[phase+10152] = 0` | Built-in 102, read straight off `0x8226BF48`: ``` state = [phase+10156] state == 1 -> return 2 ; still pending state == 2 -> special[0] = (answer == 1) ; state = 0 ; return 0 otherwise -> state = 1 ; answer = 0 ; return 2 ; issue the request ``` **`2` and `0` are the dispatcher's thread codes**, so this is a *blocking* built-in: it yields the coroutine until an answer arrives, then hands it back. Built-in 103 is the **non-blocking** form of the same test, and 130 clears the answer slot. โ‡’ **`[phase+10156]` is a request STATE โ€” 0 idle, 1 pending, 2 answered โ€” and `[phase+10152]` is the ANSWER**, compared against `1`. The answer is published by `sub_821A9DC8`, which writes `[phase+10152] = [r6+4]` and `[phase+10156] = 2` **under the same `(16, 32]` gate on `[r30+104]+12`** that `request_next`'s writer uses โ€” so one engineโ†’script publish path serves both mechanisms. โš ๏ธ **A name I did not verify.** `isl.py` calls built-in 102 `prompt_yes_no`, and the mechanism above is *consistent* with a modal yes/no prompt โ€” but grepping the corpus, that name appears only in a list of built-ins unused by Stage 02, with **no derivation recorded anywhere**. What is actually read here is "a blocking request whose answer is tested against 1". 103 and 130 are therefore left unnamed rather than named off an unverified premise. ๐ŸŸก Also unread: what question is being asked. `sub_821A9DC8` has **no strings** and exactly one reference in the image โ€” a tail `b` from `0x821AC064` โ€” so the string recipe finds nothing there. ## ๐Ÿ”ดโœ… (2026-08-27) The gate is `!= 16 && <= 32` โ€” and it is a VALIDITY CHECK, not a selector ๐Ÿ”ด **First, a correction to this page's own notation.** Written as `(16, 32]` it implies a lower bound. There is none: ``` 8225EC90 cmpi cr6, 0, r4, 16 8225EC94 stw r4, 12(r30) ; <- the field IS the argument 8225EC98 bc 12, eq, ; == 16 -> bail 8225EC9C cmpi cr6, 0, r4, 32 8225ECA0 bc 12, gt, ; > 32 -> bail ``` Everything **below** 16 passes. The condition is `kind != 16 && kind <= 32`. โœ… **`X+12` is a constructor parameter, not engine state.** `sub_8225EC78(X, kind, โ€ฆ)` โ€” the function carrying the string `"script load cancel\n"` โ€” stores its second argument into `+12` and applies the identical pair of tests to it immediately, bailing out of the load. Every later occurrence re-tests the field it just stored. So the gate is that object's **invariant**, re-checked wherever it is touched, not a switch selecting between behaviours. โœ… **Where it is checked**: 42 sites image-wide with this exact `cmpi 16` โ†’ `cmpi 32` shape, **34 of them in one code region** โ€” `sub_821A9DC8`, `sub_821AA1B0`, `sub_821AB570`, `sub_821AB650` โ€” plus 3 in `sub_8225EC78`. One object, guarded everywhere. โœ… **And the object chain is now read.** `X` is installed at `0x821A78EC` (`stw r25, 104(r30)`), the previous one torn down first through `sub_8225EB60`; so `X = [GamePart+104]` is **the game part's current script instance**. From there `X+4` โ†’ `Y`, and `Y+4` โ†’ the **ScriptPhase**, `Y+72` = the answer, with `[ScriptPhase+10152]` receiving the same value. `X+12`'s value arrives as `[r21+8]`, `r21` being `sub_821A6CF0`'s own second argument. ๐Ÿ”ด **A tempting reading, killed by its control.** GamePart ids (from the `RegisterToFactory` strings) run 0โ€ฆ27, and **16 is not among them** โ€” which makes "`X+12` is a GamePart id and 16 is the unregistered one" very inviting. It fails: **1, 2 and 18 are also absent** from that list, so 16 is one of *four* gaps, not a unique one. Nothing distinguishes it. The reading is not adopted. ๐ŸŸก So what the kind **means** is still open โ€” the gate is now read, but the value's domain is not. ## โœ… (2026-08-27) The code region is NAMED: `GamePart_MainGame` `sub_821A6CF0` has no `bl` callers, only a tail `b` from `0x821AC04C`. That address sits in a run of adjustor thunks, and the table pointing at them is a **vtable at `0x820A319C`** (RTTI locator at `0x820A3198` = `0x8210BB58`): | slot | entry | resolves to | |---|---|---| | 0 | `sub_821ABFA8` | destructor | | 1 | `0x821AC040` | **`addi r3, r0, 17; blr`** โ€” returns the factory id | | 4 | `0x821AC048` | `lwz r3, 8(r3); b sub_821A6CF0` | | 5 | `0x821AC050` | `โ†’ sub_821A82A0` | | 6 | `0x821AC058` | `โ†’ sub_821A8428` | | 7 | `0x821AC060` | `โ†’ sub_821A9DC8` | | 8 | `0x821AC078` | `โ†’ sub_821A9CF0` (guarded `r4 == 9`) | | **9** | `0x821AC068` | **`โ†’ sub_821AA1B0`** โ€” the per-frame Update | | 10 | `0x821AC070` | `โ†’ sub_821AB570` | Slot 1 returning **17** is decisive: `RegisterToFactory<17, class silph::GamePart_MainGame>`. Every thunk does `lwz r3, 8(r3)` first, so these are **`GamePart_MainGame::Impl` methods** reached through the outer class. The code region this page has been reading is now named rather than inferred. ## ๐ŸŸก A lead on the kind's domain โ€” better-controlled than the last one, still not adopted The disc carries **28 mission scripts, numbered `Stage01`โ€“`Stage16` and `Stage18`โ€“`Stage29`** (as extracted; the corpus's own "all 28 `StageNN.ssb`"). **Exactly one gap: Stage17.** The gate excludes **exactly one value: 16**. Under 0-based indexing `16 โ†” Stage17`, and `<= 32` comfortably bounds indices 0โ€ฆ28. Compare the controls honestly: | reading | values missing from the domain | gate excludes | match | |---|---|---|---| | GamePart id (refuted) | **4** โ€” 1, 2, 16, 18 | 1 value | 1-of-4 โ€” worthless | | stage index (this) | **1** โ€” Stage17 | 1 value | 1-of-1 | That is a much tighter fit, and it is *still* coincidence-shaped. **Not adopted.** ### ๐Ÿ”ด (2026-08-27) The test I proposed for it DOES NOT EXIST The plan was to find the `Stage%02d` construction site and see whether it is fed `index` or `index + 1`. Two searches say there is no such site: * **No stage-shaped format string.** The `'%s%02d'` at `0x820A9C8C` has exactly **one** xref, into `sub_822929E0`, a generic string helper. Listing *every* short `%d`-bearing string in the image โ€” 43 of them โ€” turns up no `Stage`/`_S%02d` pattern at all. * **No precomputed name hash.** Using the corpus's own `name_hash`, the values for `Stage_S00โ€ฆ30`, `Stage00โ€ฆ30`, `StageNN.ssb` and `UnitGroup_S00โ€ฆ30` appear **nowhere in the image as a 4-byte word**. So the executable never builds a stage script's name; the mapping lives on the data side. **The stage-index lead cannot be settled this way**, and the next pass should not retry it. The one remaining static handle is another hop up: `X+12` arrives as `[r21+8]` in `GamePart_MainGame` vtable slot 4, so it comes from whatever the GamePartTask manager passes when it switches parts. ## โœ…โœ… (2026-08-27) SETTLED โ€” `X+12` IS A 0-BASED STAGE INDEX, and the game's own strings prove it Following the *value* instead of the filename does it in two hops. **Hop 1 โ€” where it is stored.** `sub_82260568` passes the kind as `r7` into the phase initialisers. `SilphScriptPhase`'s constructor `sub_8225FEF8` **never touches `r7`** (zero mentions in the whole function); the *base* constructor `sub_822700C0` does: ``` 822700F0 or r26, r7, r7 82270188 stw r26, 152(r30) ; -> [phase+152] ``` **Hop 2 โ€” who reads it.** `sub_82261F70` builds a table of string pointers on its stack at `r31+144โ€ฆ` and indexes it with that field: ``` 8226230C lwz r11, 152(r21) ; the stored kind 82262310 addi r10, r31, 144 ; the table 82262318 rlwinm r11, r11, 2, 0, 29 ; kind * 4 8226231C lwzx r4, r11, r10 ; table[kind] -> a STRING ``` The table is contiguous at `0x820A8880`, 20 bytes per entry: | index | string | |---|---| | 0 | `STAGE01_UNIT_MAX` | | 1 | `STAGE02_UNIT_MAX` | | โ€ฆ | โ€ฆ | | **16** | **`STAGE17_UNIT_MAX`** | | โ€ฆ | โ€ฆ | | **32** | **`STAGE33_UNIT_MAX`** | | 33 | `PLANE` โ€” the table ends | > โ‡’ **`X+12` = `[phase+152]` = a 0-BASED STAGE INDEX**, `index N โ†’ STAGE(N+1)`. The gate falls out of it exactly: * **`kind <= 32`** is the **array bound** โ€” the table has precisely 33 entries, 0โ€ฆ32, with `PLANE` at 33 where it stops. * **`kind != 16`** is **`STAGE17`** โ€” the one stage number with no `.ssb` on the disc (files run `Stage01`โ€“`Stage16`, `Stage18`โ€“`Stage29`). โœ… **This retracts the "not adopted" above.** The stage-index reading was recorded as 1-of-1 but coincidence-shaped and deliberately not believed; it is now read off the game's own string table, and **0-based is proved by `index 5 โ†’ STAGE06`**, not assumed. โš ๏ธ **A false positive caught on the way.** `0x82272D88 lwz r11, 152(r11)` inside the built-in switch looks like a read of this field. It is not: `r11` had just been loaded from `0(r31)`, so it is the **vptr** โ€” that instruction is a virtual call to **slot 38**. Offset 152 recurs; the base register decides. ## โœ… (2026-08-27) What `STAGENN_UNIT_MAX` is: two per-stage unit caps, `PLANE` and `VESSEL` The lookup in `sub_82261F70` continues straight past the table read. `r21` is the phase there; `r31` is the **stack frame** (`addi r4, r31, 452`, `lwz r11, 376(r31)` โ€” easy to misread as phase fields). ``` 82262308 bl 0x824480D0 ; open the config node -> [r31+84] 8226230C lwz r11, 152(r21) ; the 0-based stage index 8226231C lwzx r4, r11, r10 ; "STAGE(N+1)_UNIT_MAX" 82262320 bl 0x82448AA0 ; find that section 82262328 bc 4, eq, 0x82262340 ; found -> read two keys ; NOT found โ€” defaults: 82262334 stw r11(=20), 360(r21) 82262338 stw r10(=200), 356(r21) ; found: 82262348 addi r4, r11, -29932 ; 0x820A8B14 = "PLANE" 82262360 stw r11, 356(r21) 8226235C addi r4, r10, -29924 ; 0x820A8B1C = "VESSEL" 82262368 stw r3, 360(r21) ``` | phase field | config key | default | |---|---|---| | `[phase+356]` | **`PLANE`** | **200** | | `[phase+360]` | **`VESSEL`** | **20** | So the section is a per-stage pair of **unit caps** โ€” planes (fighters) and vessels (capital ships) โ€” with the section name built from the stage index and the whole thing falling back to 200/20 when a stage has no entry. All three names are the game's own. The surrounding reads name the config itself: `GP_SCRIPT` (`0x820A8244`), `SCRIPTS`, `Resource2D`, `TABLE`. The pak census counts **40 ``, ``, `` โ€” mesh and texture declarations, nothing to do with game config. The real config is a **loose INI at the disc root**, `config.ini`, whose header comment reads *ใ€Œใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณ/ใ‚ฒใƒผใƒ ใƒ‘ใƒผใƒˆๅˆๆœŸ่จญๅฎšใƒ†ใƒผใƒ–ใƒซใ€* โ€” "application / game-part initial-settings table" โ€” and notes that the `SYSTEM` section holds settings the game and every game part share. That matches every key this code touches: `SYSTEM`, `GP_SCRIPT`, `SCRIPTS`, `Resource2D`, `TABLE`, `GRAPH_PATH`, `EX_FONT`, `SCRIPT_ID`, `SCRIPT_PATH`, `BASE_INFO`, `PLANE`, `VESSEL`, `STAGENN_UNIT_MAX` โ€” INI section and key names. **And the shipped file is 400 bytes.** In full it has two sections: ``` [SYSTEM] [LANGUAGE] = eng ; default #0x01 = eng ; XC_LANGUAGE_ENGLISH #0x02 = jpn ; XC_LANGUAGE_JAPANESE โ€ฆ deu / fra / esp / ita ``` `[SYSTEM]` is **empty**, and there is no `[STAGENN_UNIT_MAX]` anywhere. | check | result | |---|---| | `.ini` files on the extracted disc | **1** โ€” `config.ini`, 400 bytes, printed in full | | `[STAGENN_UNIT_MAX]` sections in it | **0** | | `UNIT_MAX` in any pak entry | **0** | | `VESSEL` in any pak entry | **0** | The pak scan is **controlled**: the same loop over all 41 paks decompressed **26 443 entries** and found `MSG_DEMO` 192ร—, `mapmesh_box_500km` 162ร— and ` โ‡’ **The lookup always misses, so every stage runs on the defaults: > `PLANE = 200`, `VESSEL = 20`.** ๐ŸŸก A hash search was run too, in case the section were name-hashed like a pak entry: `name_hash("STAGE01โ€ฆ33_UNIT_MAX")`, `name_hash("PLANE")`, `name_hash("VESSEL")` โ€” **0 hits as pak entry keys**. Eight of the 35 hashes did appear as 4-byte words *inside* entries, seven of them in the 1.1 GB `sound.pak`; that is the chance rate for a 32-bit needle in a blob that size, not a finding. ### โœ… (2026-08-27) The absence re-tested properly โ€” and the reader is NOT INI-only ๐Ÿ”ด **Correction to the label above.** `sub_82448AA0` is not "the INI config reader". Resolving the `r4` string at **every** call site of it and of `sub_824482D0` (artefact `data/config-keys.txt`) gives **65 section names and 54 int keys** โ€” `MISSIONS`, `FONTS`, `StageResource`, `EPILOGUE_MOVIES`, `WEAPONS`, `UNITS`, `SOUNDS`, `ACHIEVEMENTS_REQUIREMENTS`, `LINE_PITCH`, `MSG_FONT_SIZE`, `SUBTITLE_Y` โ€ฆ far more than the 400-byte `config.ini` holds. And the decisive check: **`BASE_INFO`, `SYSTEM`, `MISSIONS` and `FONTS` are all present as IDXD record keys on the disc.** So this pair is a **generic named-section accessor over the IDXD `.tbl` containers**, with `config.ini` as one small extra input. โš ๏ธ **My first re-test was invalid, and only its control showed it.** IDXD record keys are **not** `name_hash` โ€” they are **`tag_hash`** (case-sensitive, modulus `0x00FFFFDF`; `unitgroup.py` documents it). Searching 190 782 records with `name_hash` returned 0 for `STAGENN_UNIT_MAX` *and* 0 for every control name, which is what exposed it. Redone with `tag_hash`: | | | |---|---| | IDXD entries parsed | **7 750 / 7 750** (matches the corpus census) | | records scanned | **190 782**, 3 496 distinct keys | | control names found as record keys | **`BASE_INFO`, `SYSTEM`, `MISSIONS`, `FONTS`** โœ… | | `STAGE01โ€ฆ33_UNIT_MAX` records | **0** | With the literal search (0 in 26 443 decompressed entries) and `config.ini` printed in full, the section exists **nowhere on the disc** โ€” not as an INI section, not as an IDXD record. The earlier worry that the `GP_HANGAR_ARSENAL` word-hit might be real is settled: it is not a record key. > โ‡’ **`PLANE = 200` / `VESSEL = 20` for every stage** โ€” now on a controlled test > rather than a scan whose reader had never been checked. ### โœ… (2026-08-27) `Stage30`โ€“`Stage33` do not exist โ€” the 33-slot table is headroom Probing the pak entry names (`name_hash`, the right hash for *entry* names) for each stage-table family across all 41 paks: | family | stages present | |---|---| | `stage\UnitGroup_S%02d.tbl` | **01โ€“16, 18โ€“29 โ€” 28 stages** | | `stage\AIParams_S%02d.tbl` | 01โ€“16, **24โ€“29 โ€” 22 stages** | | `stage\Stage_S%02d.tbl` | 0 โ€” expected; `stagetbl.py` documents that the per-stage record is *not* name-addressed | The control is built in: 28 hits out of 33 probes for `UnitGroup`, so the loop resolves entry names correctly. **S17 and S30โ€“S33 are absent**, so the 33-entry `STAGENN_UNIT_MAX` table is oversized headroom over a 28-stage game. ๐Ÿ”‘ **An independent corroboration falls out of the second row.** `AIParams` is missing for exactly **S18โ€“S23** โ€” precisely the six stages this page identifies as the tutorials by a completely different route (their only dominating condition is `request_next() != 1`). Tutorials ship no AI parameters. ### ๐ŸŸก Who consumes `[phase+356]` / `[phase+360]`: not findable by offset Image-wide there are **243 non-stack `lwz` reads from +356/+360**, spread across the UI, sound, render and script subsystems โ€” the offsets are far too common to attribute. Inside the ISL region the only hits are the two writers in `sub_82261F70` and two **vtable calls** (`0x82273400` slot 90, `0x82290ED8`). Dropping this sub-item: it needs type information the offset index does not carry. ### โœ… (2026-08-27) The two-function model is confirmed by a 64/65 vs 0/54 dissociation Taking every literal key from `data/config-keys.txt` and testing it against the 3 496 distinct IDXD record keys on the disc (`tag_hash`): | class | argument to | present as an IDXD **record key** | |---|---|---| | section names | `sub_82448AA0` | **64 / 65** | | field keys | `sub_824482D0` | **0 / 54** | The two classes are each other's control, and the split is total. It confirms the model exactly: * **`sub_82448AA0(node, NAME)` finds a RECORD**, keyed by `tag_hash(NAME)`. * **`sub_824482D0(node, KEY)` reads a FIELD**, whose name is a literal string in the record's pool โ€” so of course none of them is a record key. ๐Ÿ”‘ **The single absent section is `LANGUAGE`** โ€” which is precisely the one real section in the disc-root `config.ini`. The same API serves both stores, and the data shows the seam. So the earlier worry that "64 sections have unknown hardcoded fallbacks" was overblown: **all but one are real data on the disc**. `STAGENN_UNIT_MAX` is the genuine exception, and it is absent because its name is *built at runtime* from the stage index rather than being one of these literals. ๐Ÿ“Ž The artefact now records which pak each record lives in โ€” `tables.pak` for the global tables (`MISSIONS`, `STAGES`, `SOUNDS`, `SUB_OBJECTIVE`, `SQUADRON_ORDER_OBJECT`, `EPILOGUE_MOVIES`, `ACHIEVEMENTS_REQUIREMENTS`, `FONTS`, `BASE_INFO` โ€ฆ), **`GP_HANGAR_ARSENAL.pak` for `WEAPONS`, `UNITS`, `Camera`, `ControlTweak`, `Rendering`, `AUTO_SETTINGS`, `IGNORE`**, and the per-language `GP_MAIN_GAME_*` paks for the enum tables. ### โœ… (2026-08-27) The records DECODED โ€” and my "canonical definitions" guess was half wrong `parse()` + `named()` on each record (artefact `data/config-records.txt`): **Real, directly portable constants** โ€” these are the find: ``` Rendering Brightness 1.4 Contrast 1.0 Saturation 1.00 Hue 0.00 ExposureKeyValue 0.18 BrightPassThreshold 0.6 BrightPassOffset 1.5 LuminanceMin 0.15 LuminanceMax 3.6 BloomScale 0.5 AfterimageScale 0.05 StarScale 1.20 GlareType 8 ColorLayer R/G/B/A 0.00 ControlTweak CameraSpeed 0.50 TargetSpeed 0.50 TargetMovingRange -10.0 โ€ฆ 10.0 mov_trigger_play 20 eye_stick_play 6000 mov_stick_play 6000 Camera NoseCameraFOV 0.92 ChangeTime 0.20 ``` ๐Ÿ”ด **But `WEAPONS` and `UNITS` are not what I called them.** Last iteration I said `GP_HANGAR_ARSENAL.pak` holds "the canonical weapon/unit definitions". Half right at best: * **`WEAPONS`** is a **59-entry name roster** โ€” `No_Equipment`, `Machiene_Cannon_MG1`, `Broad_Sword_SG1`, `Frail_GP37`, `Stiletto_BG1`, `Pilum_BP`โ€ฆ โ€” an enumeration of ids, **not a stat sheet**. * **`UNITS`** is not units at all: seven fields pairing a craft slot with a pilot โ€” `Bird1-Sandra`, `Bird2-Billy`, `Bird3-Antonius`, `Bird4-Carl`, `Rhino1-Raymond`, `Rhino2-Katana`, `Rhino3-Ellen`. **The wingman roster.** * **`IGNORE`** is a **13-name blocklist** over that roster โ€” `Long_Spear_HBP`, `Twin_Saber_LG2H`, `Eagle_120AM`, `Maelstrom_Bomb`, `Cluster_Mine_B10`, and the placeholders `Wep_82`, `Wep_85`. Weapons that exist in the enumeration but are withheld. ๐Ÿ”‘ **`AUTO_SETTINGS` lists exactly 28 files** โ€” `stage01_settings.tbl` โ€ฆ `stage29_settings.tbl` โ€” a **third independent confirmation of the 28-stage count**, after the `.ssb` census and the `UnitGroup_SNN.tbl` probe. ๐ŸŸก **And the UI records are UI, not gameplay.** `SUB_OBJECTIVE` is a HUD layout โ€” `MAX_OBJECTIVE_OFFSET 353,242`, `TEXT_OFFSET 85,271`, `NUM_QUALIFY_OFFSET 309,242`, `DOT_OFFSET 0,20` โ€” with a **second variant** (`275,222` / `85,251` / `231,222` / `0,0`) in one pak, so there are two layouts. `SQUADRON_ORDER_OBJECT` is 29 `.prt` sprite names for the squadron-order menu. `MISSIONS` holds six positional members โ€” `TimeAttack`, `ScoreAttack`, `Extra01`โ€ฆ`Extra04`, the **challenge** categories, not the campaign. `STAGES` has **zero** fields. ๐ŸŸก Its neighbours belong to the same cluster: `builtin103` reads `[phase+10156]` and `[phase+10152]` (9 and 7 writers), and a sibling vtable stub clears `[phase+10152]`. The shape is an engineโ†’script status trio, but that is a description, not a name. ## โœ… `builtin7`'s mechanism โ€” and an independent confirmation ``` 82266C84 lwz r11, 324(r25) ; the unit array 82266C88 lwz r10, 4(r26) ; local[4] 82266C94 lwzx r11, r10, r11 ; -> the record 82266C98 lwz r10, 4(r11) ; the handle โ€ฆ (alive?) 82266CA4 lwz r11, 16(r11) ; rec+16 = the unit STATE 82266CA8..CBC ; bail if state == 1, 3 or 4 82266CC0 lwz r10, 12(r26) ; local[12] 82266CC8 lwz r11, 244(r25) ; [phase+244] = SYMBOL TABLE 1 82266CD8 lwzx r10, r10, r11 ; -> resolve local[12] as a symtab-1 index 82266CD0 lfd f31, 25600(r9) ; a double constant 82266CD4 lwz r9, 16(r26) ; local[16] ``` ๐Ÿ”‘ **`isl.py`'s `SYM1_SLOTS` already lists slot 12 for built-in 7**, derived purely from operand ranges. Reading the implementation shows the *mechanism* โ€” `local[12]` is indexed into `[phase+244]`, which [`isl-bytecode.md`](isl-bytecode.md) documents as symbol table 1 (routes, messages, objectives). **Two independent methods, same conclusion.** That makes the Stage 02 phase-2 condition ``` builtin7(TCT206, 1, Route_TCT206_p2S, 4294967295, 500) == 1 ``` structurally coherent โ€” a unit, a **route symbol**, and two numbers โ€” but *what* it asks about the route is not read, so it stays `builtin7`. ## ๐ŸŸก `builtin141` โ€” only the entry read ``` 8226C9D4 lwz r11, 324(r29) ; unit array 8226C9D8 lwz r10, 4(r31) ; local[4] 8226C9E8 lwz r8, 4(r8) ; handle 8226C9F0 bc โ€ฆ 0x8226CA0C ; if alive, continue 8226C9F4 addi r11, r0, 0 8226C9F8 stw r11, 164(r29) ; dead -> special[0] = 0 ``` The same unit-array opening as every unit predicate, returning 0 when the unit is gone. Everything past `0x8226CA0C` is unread. Stage 16 calls it twice with arguments differing in one position (`0` vs `-4000`), which *looks* like a coordinate โ€” and looking like one is precisely the evidence this corpus does not accept. ## ๐ŸŸก Not settled * **No name for any of the three.** For 104 that needs the meaning of `r29` at `0x821AAD9C` and of the `(16, 32]` gate; for 7 and 141, their bodies past the entry checks. * `builtin103`'s fields `[phase+10152]` / `[phase+10156]` have 9 and 7 writers, none read. * This does not change any artefact โ€” the conditions already printed `builtin104`, `builtin7`, `builtin141`, and still do.