From bb6fb9024b6339f5ae3217a27f78264583db1969 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Fri, 28 Aug 2026 00:21:26 +0000 Subject: [PATCH] re: r0 is not a base register - the last shortlisted row withdrawn sub_825F2CF0 / sub_825F2F88 (r0, 30 names each, 97% disc names) are not name-block readers at all: they are the compiler's VMX save/restore helper pair. 145 instructions each, 72 differ, every difference stvx<->lvx over v14..v127. Root cause: in `addi rD, rA, N` the rA slot reads as literal zero when it names r0, so `addi r11, r0, -N` is `li r11, -N` - vector spill offsets, not displacements. The solver excluded r1 only; the bogus base landed inside the stage-settings name block, dense enough to "resolve" 30 real field names. name_block_bases.py now skips r0 as well. Rows 277 -> 261, functions 190 -> 176, non-64K bases 170 -> 154, data-table rows 53 -> 50; 154+107=261 and 277-16=261. Artefact diff -100/+3, every removed row line carries r0, control sub_82341A20 r30 = 0x82088F94 217/226 untouched. Nothing lost: all 30 names are a strict subset of sub_8230D1F8 r29 (129 names), the stage-settings loader already in the corpus. String-xref join agrees - FinalPassBG, FogMinDistance, ScreenColorR, ExposureKey_BeginValue each exist once with exactly one xref, from sub_8230D1F8. Fourteen other artefacts byte-identical; name-block-bases.txt byte-identical on a second run. --- docs/re/BACKLOG.md | 56 +++++++++++ docs/re/INDEX.md | 2 +- docs/re/data/name-block-bases.txt | 103 +-------------------- docs/re/structures/player-tuning-tables.md | 93 ++++++++++++++++--- tools/re-capture/name_block_bases.py | 8 +- 5 files changed, 149 insertions(+), 113 deletions(-) diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index 98d7bb1..52e100e 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -5497,3 +5497,59 @@ field**; counting "24 `Detail_*` things" would merge two unrelated tables. `Extra01
04` map onto the four challenge missions the corpus knows. And the last shortlisted row (`sub_825F2CF0`/`sub_825F2F88` post-processing, two functions on one base) is still unopened. + +--- + +## 🔮✅ 2026-08-28 — the last shortlisted row is an artefact: `r0` is not a base register + +Item (a), the final unopened row: `sub_825F2CF0` + `sub_825F2F88`, **`r0`**, 30 +names each on the same base `0x8209EB30`, 97 % disc names. The framing was "a +pair, or a clone?" — **both wrong. Neither function reads a name block.** + +🔑 **Diffing the two settles it in one look.** 145 instructions each, **72 +differ, and every difference is the same substitution**: `stvx`/`stvx128` on one +side, `lvx`/`lvx128` on the other, over v14
v127. They are the compiler's **VMX +register save / restore helper pair**. A pair, then — but the pairing is +spill/reload, not two readers of one table. + +🔮 **The root cause is a whole class of false positive.** In PowerPC +`addi rD, rA, N`, the `rA` slot **reads as literal zero when it names r0** — the +form is `li rD, N`. The 72 `addi r11, r0, -N` are vector spill offsets, plain +immediates. The solver excluded `r1` (stack pointer) and nothing else, so it took +them as displacements, voted a base, and landed `0x8209EB30` — *inside the +stage-settings name block*, where a scatter of 16-byte-spaced negative offsets is +dense enough to "resolve" 30 real field names. + +⚠ **A 97 %-disc-names row can still be an artefact.** The disc-name test measures +what the *base region* contains, not that the function reads it. That is a +correction to the 53-of-277 classification's own premise. + +✅ **Nothing is lost, and the real reader was already ours.** All 30 withdrawn +names are a **strict subset** of `sub_8230D1F8 r29` (base `0x8209FD28`, 129 +names) — the stage-settings loader. The string-xref join agrees independently: +`FinalPassBG`, `FogMinDistance`, `ScreenColorR` and `ExposureKey_BeginValue` each +exist **once** in the image with **exactly one** xref, from `sub_8230D1F8`. +`NoseCameraFOV` and `GlareType` exist **twice**, the second copy read by +`sub_822AE628` — the hangar's game-wide defaults, the double-string-block trap +the corpus already records. + +**Fix:** `name_block_bases.py` now skips `reg in ('r0', 'r1')`. Every number moves +by exactly the 16 `r0` rows: rows **277 → 261**, distinct functions **190 → 176** +(14, not 16 — two of those functions keep a row through another register), +non-64K bases **170 → 154**, 64K bases 107 unchanged, data-table schemas +**53 → 50**. Both ways: `154 + 107 = 261` and `277 − 16 = 261`. The artefact diff +removes 100 lines and adds 3; **every removed row line carries `r0`**, the three +added lines are those counts, and the control is untouched (`sub_82341A20 r30 = +0x82088F94`, 217/226). The three withdrawn data-table rows are `sub_825F2CF0`, +`sub_825F2F88` and `sub_82222E70` (15 names, 100 % disc, same region, same +mechanism); the "six camera/fog readers" are really **three** (`sub_8247DFC0`, +`sub_823B2620`, `sub_822C7480`, all `r31`, all based in `0x8209E6xx`). + +Written up in `docs/re/structures/player-tuning-tables.md`; `INDEX.md` updated. + +🟡 Not settled: the 🟡 stalled `SpaceSize`/`GlareType`/`Nebura_*` numerics are no +closer — their reader was already known to be `sub_8230D1F8`, and this row never +was a second one. **The shortlist is now empty.** Still open: whether a `stvx`- +style helper elsewhere fooled a *non*-`r0` row (the 64K-boundary cluster is the +remaining known false-positive mode, unaddressed); `Detail_Window_Known/_Unknown`; +whether `Extra01
04` map onto the challenge missions. diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index 2b018a8..afb6277 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -106,7 +106,7 @@ files, which is how the same ground got covered twice. | [`structures/stage16-boss.md`](structures/stage16-boss.md) | The Stage 16 boss end to end — object, loader `sub_82315AE8`, collision | ✅ CONFIRMED — 19/19 of the loader's names are `Guardian`/`Core` fields; 5 disc fields have no string in the image (two same-length controls do) | | [`structures/leaderboard-screen.md`](structures/leaderboard-screen.md) | The leaderboard screen — `sub_8219E560`, 12 panel elements, 4 board scopes, 6 modes | ✅ CONFIRMED — 18 names = 13 records + 5 fields; all 12 `Detail_*` records named, zero residual; ⚠ 12 `Detail*` records and 12 `Detail*` fields are DISJOINT | | [`structures/result-screens.md`](structures/result-screens.md) | The debriefing and pilot-record screens — `STAGE_RESULT` 21 fields, `OVERVIEW` 9, `EX_OVERVIEW` 7 | ✅ CONFIRMED — the compiled key lists `sub_822814D8` (24/24) and `sub_8227A3A0` (21/21) match the `tables.pak` records exactly; `2+21+1=24`, `5+7+9=21` | -| [`structures/player-tuning-tables.md`](structures/player-tuning-tables.md) | **`PlayerParams`** — the player parameter table: `Booster` flight model, special attacks, 8 analog curves, the S16 boss | ✅✅ `sub_822F9498` is `PlayerParams`'s loader, not the unit loader; **the analog block's reader SOLVED — `sub_821A6CF0`, base `0x820A1630`**, via the new base-solver (277 name-block loaders image-wide, control 217/226 on `sub_82341A20`); 🟡 nothing selects `Booster` | +| [`structures/player-tuning-tables.md`](structures/player-tuning-tables.md) | **`PlayerParams`** — the player parameter table: `Booster` flight model, special attacks, 8 analog curves, the S16 boss | ✅✅ `sub_822F9498` is `PlayerParams`'s loader, not the unit loader; **the analog block's reader SOLVED — `sub_821A6CF0`, base `0x820A1630`**, via the new base-solver (261 name-block loaders image-wide after the `r0` correction, control 217/226 on `sub_82341A20`); 🟡 nothing selects `Booster` | | [`structures/hud-config.md`](structures/hud-config.md) | The in-game HUD configuration — 16 records, 419 asset paths, and a per-stage `ResourceTable` | ✅ CONFIRMED — 0 of 419 config paths resolve as pak entries | | [`structures/mission-script-manifest.md`](structures/mission-script-manifest.md) | `Stage\script.tbl`'s 11 non-`MISSION` fields, and the mission dialogue table they name | ✅ CONFIRMED — 1 of 7 values resolves (`message\MissionDialogMessage.tbl`, 200 records), controls in the same sweep | | [`structures/stage-numbering-and-player-craft.md`](structures/stage-numbering-and-player-craft.md) | The disc's stage numbering (S01–S16, S18–S29) and the player's craft per stage | ✅✅ CONFIRMED — two independent files close on 6 + 15 + 1 + 6 = 28 | diff --git a/docs/re/data/name-block-bases.txt b/docs/re/data/name-block-bases.txt index 96a33b8..82a4f4f 100644 --- a/docs/re/data/name-block-bases.txt +++ b/docs/re/data/name-block-bases.txt @@ -4,7 +4,7 @@ strings in the image: 7140 addi instructions: 282736 -## 277 functions read a name block through a base register +## 261 functions read a name block through a base register function reg base resolved / displacements sub_82341A20 r30 0x82088F94 217 / 226 sub_822215D0 r11 0x820A0000 205 / 206 @@ -81,14 +81,11 @@ sub_8284B398 r11 0x820B0000 40 / 42 sub_822F9498 r31 0x82026F24 39 / 83 sub_826FBC38 r11 0x82050000 38 / 60 - sub_826AE390 r0 0x82026F74 37 / 306 sub_82338EE0 r31 0x82026F18 36 / 78 sub_823AE908 r31 0x82026F24 33 / 71 sub_823BDAA8 r11 0x820B0000 33 / 35 sub_826AB050 r11 0x82040000 31 / 33 sub_82561C70 r11 0x820A90D8 30 / 93 - sub_825F2CF0 r0 0x8209EB30 30 / 64 - sub_825F2F88 r0 0x8209EB30 30 / 64 sub_822E3EC8 r11 0x820B0000 29 / 32 sub_82627B70 r11 0x82030000 29 / 29 sub_82297550 r11 0x820B0000 27 / 41 @@ -174,8 +171,6 @@ sub_8257E270 r5 0x820AE7AC 21 / 31 sub_8257E6C0 r5 0x820AE7AC 21 / 31 sub_82658790 r11 0x82030000 21 / 26 - sub_82780C10 r0 0x820A9EAD 21 / 51 - sub_827D9DD0 r0 0x82074503 21 / 149 sub_821A8578 r11 0x82023F00 20 / 25 sub_8233C368 r28 0x8208583C 20 / 21 sub_8236B608 r11 0x820B0000 20 / 20 @@ -190,7 +185,6 @@ sub_8232BAA0 r31 0x82034220 17 / 25 sub_823B63E0 r31 0x820AE72C 17 / 30 sub_82440878 r31 0x82026FF0 17 / 33 - sub_824B4908 r0 0x82073D58 17 / 122 sub_8265A260 r11 0x82030000 17 / 18 sub_827A5058 r11 0x82050000 17 / 18 sub_8219BB30 r11 0x820244AC 16 / 26 @@ -201,10 +195,8 @@ sub_82324178 r31 0x82026F14 16 / 28 sub_82473448 r31 0x8202EBFC 16 / 29 sub_82562BB8 r3 0x820A918C 16 / 20 - sub_825C2980 r0 0x82052A40 16 / 22 sub_826A0040 r11 0x820A01AC 16 / 32 sub_82804AD0 r11 0x820A792C 16 / 43 - sub_82222E70 r0 0x8209E6D8 15 / 27 sub_8227F8E0 r31 0x820A0E9C 15 / 20 sub_82282DB0 r31 0x820A0E8C 15 / 20 sub_822E3EC8 r10 0x820B0000 15 / 15 @@ -216,9 +208,7 @@ sub_823C0C58 r31 0x82034270 15 / 18 sub_82565C80 r11 0x820A5574 15 / 42 sub_825DA790 r26 0x820A3B4C 15 / 20 - sub_8265DFB8 r0 0x82074ECA 15 / 98 sub_82664700 r11 0x82030000 15 / 16 - sub_82787E38 r0 0x82074B66 15 / 76 sub_8285F2C8 r5 0x820AE7AC 15 / 15 sub_82860290 r3 0x820AE7AC 15 / 15 sub_82860670 r3 0x820AE7AC 15 / 15 @@ -226,7 +216,6 @@ sub_8220ED30 r31 0x820A0E9C 14 / 21 sub_8221F700 r11 0x820A0000 14 / 18 sub_82288E70 r11 0x820B0000 14 / 23 - sub_822CC130 r0 0x82026F74 14 / 31 sub_822F2E58 r31 0x820A97E4 14 / 27 sub_82308AB8 r31 0x82034200 14 / 27 sub_8230BB58 r11 0x820B0000 14 / 15 @@ -241,7 +230,6 @@ sub_823B2620 r31 0x8209E710 14 / 24 sub_823D3BE0 r31 0x820B47D4 14 / 24 sub_82529708 r25 0x820AAA6C 14 / 20 - sub_8252C4E8 r0 0x82074617 14 / 70 sub_825635C8 r31 0x8209E230 14 / 22 sub_8261F2C0 r11 0x8208AB54 14 / 21 sub_82659DF0 r11 0x82030000 14 / 14 @@ -254,8 +242,6 @@ sub_823800A8 r31 0x82001C4C 13 / 21 sub_823A3C30 r31 0x82034270 13 / 18 sub_823BF6D8 r31 0x82034270 13 / 17 - sub_82435448 r0 0x82027040 13 / 52 - sub_824B42E8 r0 0x820743A3 13 / 66 sub_824E76D0 r10 0x8209FC64 13 / 22 sub_8257DD20 r4 0x820342D0 13 / 15 sub_8257DD20 r5 0x820342D0 13 / 15 @@ -266,7 +252,6 @@ sub_82621400 r11 0x82030000 13 / 14 sub_82622448 r11 0x8202B13C 13 / 18 sub_8263D478 r11 0x82030000 13 / 16 - sub_826A7BF0 r0 0x82074DCE 13 / 85 sub_827030F8 r11 0x82050000 13 / 13 sub_827B0098 r11 0x82050000 13 / 15 sub_82193648 r11 0x820A8278 12 / 22 @@ -280,12 +265,11 @@ sub_82625890 r11 0x82030000 12 / 14 sub_826ACC98 r11 0x82040000 12 / 18 sub_826FA758 r11 0x820A81F0 12 / 26 - sub_82766DB0 r0 0x8207454A 12 / 71 sub_82766DB0 r11 0x82050000 12 / 29 sub_827D7C00 r11 0x82060000 12 / 14 ## Confidence split - bases with a non-zero low half (a real `addis`+`addi` pair): 170 + bases with a non-zero low half (a real `addis`+`addi` pair): 154 bases on a 64K boundary (LOW CONFIDENCE, see below) : 107 0x820B0000 x82 rows 0x82030000 x10 rows @@ -297,7 +281,7 @@ ## Is the row a DATA-TABLE schema? (names that are IDXD record/field names on the disc) distinct IDXD record+field names disc-wide: 13450 - rows that are >=50% disc names and >=8 names: 53 / 277 + rows that are >=50% disc names and >=8 names: 50 / 261 sub_82341A20 r30 0x82088F94 217 names 89% disc sub_822215D0 r11 0x820A0000 205 names 98% disc sub_8230D1F8 r29 0x8209FD28 129 names 97% disc @@ -306,8 +290,6 @@ sub_822AE628 r11 0x820B0000 81 names 67% disc sub_823BDAA8 r11 0x820B0000 33 names 88% disc sub_82561C70 r11 0x820A90D8 30 names 50% disc - sub_825F2CF0 r0 0x8209EB30 30 names 97% disc - sub_825F2F88 r0 0x8209EB30 30 names 97% disc sub_82297550 r11 0x820B0000 27 names 67% disc sub_822A2F00 r11 0x820B0000 27 names 70% disc sub_823BDAA8 r10 0x820B0000 25 names 100% disc @@ -328,7 +310,6 @@ sub_82286BC8 r11 0x820B0000 18 names 100% disc sub_821AA1B0 r31 0x820A97CC 16 names 94% disc sub_82804AD0 r11 0x820A792C 16 names 88% disc - sub_82222E70 r0 0x8209E6D8 15 names 100% disc sub_8227F8E0 r31 0x820A0E9C 15 names 100% disc sub_82282DB0 r31 0x820A0E8C 15 names 100% disc sub_823479B8 r28 0x820864F4 15 names 100% disc @@ -1118,12 +1099,6 @@ assembler.prd_stack->Size() == 1 , ExpAdjust=%d MiniFetch=FALSE, PrefetchCount=%d MiniFetch=TRUE - sub_826AE390 (r0, base 0x82026F74) names 37: - vs_3_sw vs_3_0 vs_2_x vs_2_sw vs_2_a vs_1_1 vs_1_0 vs.3.xps vs.3.swx vs.3.sw - vs.3.0 vs.2.x vs.2.sw vs.2.a vs.1.1 vs.1.0 tx_1_0 ps_3_sw ps_3_0 ps_2_x ps_2_sw - ps_2_b ps_2_alpha2 ps_2_a ps_1_4 ps_1_3 ps_1_2 ps_1_1 ps_1_0 ps.3.sw ps.3.0 - ps.2.x ps.2.sw ps.2.b ps.2.a ps.1.4 ps.1.3 - sub_82338EE0 (r31, base 0x82026F18) names 36: ps.1.3 vs_3_swx vs_3_0 ps_3_sw vs_2_x vs_2_sw ps_3_0 vs_2_a ps.2.b vs_1_1 ps_2_x vs_1_0 vs.3.xps ps_2_sw ps_2_b vs.3.sw vs.3.0 ps_2_alpha2 vs.2.x vs.2.sw vs.2.a @@ -1252,22 +1227,6 @@ float4 FontPixelShader( VS_OUT In ) : COLOR0 C_PAD_DECODER \x8F\x89\x8A\xFA\x89\xBB Pattern - sub_825F2CF0 (r0, base 0x8209EB30) names 30: - FinalPassBG FinalPassToneRatio FogMinDistance FogMaxDistance FogMinAmount FogMaxAmount - FirstAdaptedLuminance ExposureKey_BeginValue NoseCameraFOV NoseCameraPosX - NoseCameraPosY NoseCameraPosZ NearCameraFOV NearCameraPosX NearCameraPosY - NearCameraPosZ FarCameraFOV FarCameraPosX FarCameraPosY FarCameraPosZ SplinterCell - BGRotateScale_%1d GlareType Contrast ColorLayerA ScreenColorR ScreenColorG - ScreenColorB FinalPassType FinalPassDR - - sub_825F2F88 (r0, base 0x8209EB30) names 30: - FinalPassBG FinalPassToneRatio FogMinDistance FogMaxDistance FogMinAmount FogMaxAmount - FirstAdaptedLuminance ExposureKey_BeginValue NoseCameraFOV NoseCameraPosX - NoseCameraPosY NoseCameraPosZ NearCameraFOV NearCameraPosX NearCameraPosY - NearCameraPosZ FarCameraFOV FarCameraPosX FarCameraPosY FarCameraPosZ SplinterCell - BGRotateScale_%1d GlareType Contrast ColorLayerA ScreenColorR ScreenColorG - ScreenColorB FinalPassType FinalPassDR - sub_822E3EC8 (r11, base 0x820B0000) names 29: g_mWorldViewProjection g_mWorldViewProjectionLast g_mObjectToView g_mObjectToViewLast g_mWorld g_Params g_UVParams g_LayerParams g_MapEnable g_MapLayerEnable ColorMap @@ -2145,16 +2104,6 @@ float4 FontPixelShader( VS_OUT In ) : COLOR0 Constant is both a struct member and an array element. defconst: %s Register type not found. Multi-dimensional arrays are not supported. Bad variable name. - sub_82780C10 (r0, base 0x820A9EAD) names 21: - 757,228 rou_e009 rou_e008 rou_e006 rou_e005 rou_e003 rou_e004 rou_f202 rou_f201 - rou_f105 rou_f101 rou_f003 rou_f001 rou_e201 ConditionToDevelop rou_e108 rou_e106 - rou_e105 rou_e015 rou_e010 rou_e007 - - sub_827D9DD0 (r0, base 0x82074503) names 21: - hyphensuperior Gravesmall Asmall Msmall colonmonetary notequal sixinferior - Dotaccentsmall Ringsmall seveneighths onethird centinferior Aacutesmall 001.002 - Semibold greaterequal Acircumflex Uacute Ugrave eacute edieresis - sub_821A8578 (r11, base 0x82023F00) names 20: texdp3 vs_3_sw vs_3_0 vs_2_x vs_2_sw vs_2_a vs_1_1 vs_1_0 vs.3.xps vs.3.sw vs.3.0 vs.2.x vs.2.sw vs.2.a vs.1.1 vs.1.0 ps_3_sw ps_3_0 ps_2_x ps_2_sw @@ -2274,10 +2223,6 @@ float4 FontPixelShader( VS_OUT In ) : COLOR0 ps_2_a ps.2.x ps_1_4 ps_1_2 ps.1.0 ps.1.2 ps_1_1 ps.2.b ps_1_0 ps.1.3 ps.3.sw ps.2.a ps.3.0 ps.1.1 ps_1_3 ps.1.4 ps.2.sw - sub_824B4908 (r0, base 0x82073D58) names 17: - Ntilde Lslash acircumflex onesuperior logicalnot fouroldstyle Uacute adieresis - atilde ccedilla eacute Ugrave Ydieresis Zcaron notequal product bracketleft - sub_8265A260 (r11, base 0x82030000) names 17: More than one buffer type specified. Buffer type "%s" does not use a size value. @@ -2347,11 +2292,6 @@ float4 FontPixelShader( VS_OUT In ) : COLOR0 mob_n040 rou_e202 rob_e001 mob_n500 mob_n043 mob_n042 mob_n041 rob_f001 rou_f104 rou_f102 rou_e107 1092,457 FlightTime 1066,491 ClearTimes rob_f002 - sub_825C2980 (r0, base 0x82052A40) names 16: - FOGEND BORDERCOLORA FOGCOLORA BORDERCOLORG BORDERCOLORB YUVCONSTANTSC33 - YUVCONSTANTSC31 YUVCONSTANTSC22 YUVCONSTANTSC23 YUVCONSTANTSC32 YUVCONSTANTSC03 - YUVCONSTANTSC13 YUVCONSTANTSC21 YUVCONSTANTSC11 YUVCONSTANTSC12 FOGCOLORG - sub_826A0040 (r11, base 0x820A01AC) names 16: PGHUD_RANGE_BAR STAGE_ID_7 PGHUD_RANGE_BAR_EM AsteroidDefinition STAGE_ID_8 STAGE_ID_9 STAGE_ID_10 STAGE_ID_11 StageResource STAGE_ID_12 STAGE_ID_13 STAGE_ID_14 STAGE_ID_15 @@ -2362,11 +2302,6 @@ float4 FontPixelShader( VS_OUT In ) : COLOR0 PGMANUVA_NUM3EFF PGHUD_ARMS_NUM%d EnumerateUnit SilhouetteModel PGMANUVA_NUM3 PGHUD_TYPE_CN WEAPONS SE_main_arm1_attach 903,343 PGTARGET_SP_READY_DLH - sub_82222E70 (r0, base 0x8209E6D8) names 15: - Generic NearCameraPosY Nebura_Size GravityFactor FarCameraPosX FarCameraFOV - YawAdjustment EnumerateAIParams RollAdjustment Player NoseCameraFOV FogMinAmount - FinalPassToneRatio NearCameraPosZ FarCameraPosZ - sub_8227F8E0 (r31, base 0x820A0E9C) names 15: ForceBootPlayerCharacterID_11 ForceBootPlayerCharacterID_15 ForceBootPlayerCharacterID_12 ForceBootPlayerCharacterID_14 ForceBootPlayerCharacterID_13 ForceBootPlayerCharacterID_3 @@ -2429,10 +2364,6 @@ float4 FontPixelShader( VS_OUT In ) : COLOR0 Camera TYPE_END_EVENT A/START MISSION11 MISSION12 MISSION7 MISSION8 MISSION9 MISSION10 MISSION15 MISSION16 MISSION17 MISSION18 MISSION13 MISSION14 - sub_8265DFB8 (r0, base 0x82074ECA) names 15: - Asmall Bsmall Csmall Dsmall Esmall Fsmall Gsmall Hsmall Ismall Jsmall Ksmall - Lsmall Msmall Nsmall Osmall - sub_82664700 (r11, base 0x82030000) names 15: p->xUR1 = __vspltisw(1); %s = __vmsum3fp(%s, %s); @@ -2448,10 +2379,6 @@ float4 FontPixelShader( VS_OUT In ) : COLOR0 %s = __vsel(%s, %s, %s); - sub_82787E38 (r0, base 0x82074B66) names 15: - Ucircumflex bsuperior Zcaron osuperior ssuperior atilde nineoldstyle icircumflex - Yacute Udieresis edieresis Psmall zerosuperior aacute dsuperior - sub_8285F2C8 (r5, base 0x820AE7AC) names 15: %4d / %4d %4d / %4d @@ -2558,10 +2485,6 @@ float4 FontPixelShader( VS_OUT In ) : COLOR0 199,333 360,333 BrokenData 260,367 441,367 AUTO_SAVED CLEARED 260,333 441,333 %03d:%02d:%02d 513,367 FlightTime 703,367 RatioOfClear - sub_822CC130 (r0, base 0x82026F74) names 14: - vs_3_sw vs.3.sw vs_1_0 ps_2_alpha2 vs.1.0 vs.2.a vs_2_x vs_1_1 vs.3.xps ps.1.1 - vs_2_sw vs_3_0 vs.2.x ps.1.3 - sub_822F2E58 (r31, base 0x820A97E4) names 14: STAGE14 STATE_GAME_CLEAR STAGE04 STAGE06 MISSION_SELECT LOAD_BASES STATE_STAND_BY STAGE13 STAGE11 STAGE07 STAGE10 STAGE09 STAGE12 STAGE08 @@ -2692,10 +2615,6 @@ float4 FontPixelShader( VS_OUT In ) : COLOR0 LuminanceFX_BrightB LuminanceFX_TonedRatio LuminanceFX_Vignetting LuminanceFX_VignetSharpness - sub_8252C4E8 (r0, base 0x82074617) names 14: - scedilla cacute parenright semicolon quoteright .notdef percent parenleft hyphen - Scedilla dcroat Ccaron asciicircum exclam - sub_825635C8 (r31, base 0x8209E230) names 14: DOF_FarFocal FogLinear FogMinDistance Nebura_Size Nebura_MiddleNear FogMaxAmount Nebura_SpeedScale UnsharpMask_R Nebura_NearAmount FinalPassDB FinalPassBR FinalPassBG @@ -2773,14 +2692,6 @@ float4 FontPixelShader( VS_OUT In ) : COLOR0 __vspltw(%s, 0) __vspltw(%s, 1) __vsel(%s, %s, %s) __vcfux(%s, %d) __vspltisw(0) __vspltw(%s, 2) p->xSR2 __lvx(%s, 0) - sub_82435448 (r0, base 0x82027040) names 13: - ps_1_1 ps_1_4 ps_2_a ps_1_3 ps_1_2 ps.2.x ps_1_0 ps.3.sw ps.3.0 ps.2.sw ps.2.b - ps.1.4 ps.2.a - - sub_824B42E8 (r0, base 0x820743A3) names 13: - Dieresissmall figuredash dollarinferior Medium Adieresissmall Atildesmall Aringsmall - Agravesmall Aacutesmall Ethsmall threequartersemdash agrave partialdiff - sub_824E76D0 (r10, base 0x8209FC64) names 13: AccPitchFactor AV_AxisMode_Min DecPitchFactor AV_AxisMode_Max GaugeMaximum ChargeMinimum ChargeMaximum AttackTime_Minimum StageResource BackGroundID @@ -2842,10 +2753,6 @@ float4 FontPixelShader( VS_OUT In ) : COLOR0 Internal error 0x%08X while generating code. Please send this shader to xboxds@xbox.com. Microcode comparison failed. Please send your HLSL code to xboxds@xbox.com. - sub_826A7BF0 (r0, base 0x82074DCE) names 13: - periodsuperior Ksmall Jsmall Lsmall Bsmall Csmall Dsmall Esmall Fsmall Gsmall - Hsmall Ismall Msmall - sub_827030F8 (r11, base 0x82050000) names 13: Assertion failed: %s (%s:%u) d:\xenon\jun06\core\private\xtl\graphics\xgraphics\ucode\ssm\statecompiler\ssmstatecompiler.cpp @@ -2925,10 +2832,6 @@ float4 FontPixelShader( VS_OUT In ) : COLOR0 LANGUAGE PGMARKTGT PGMARKLOCKON PGMANUVA_EFF3 PGMARK_GAUGE_SUB_BASE FrameAnimationType SYSTEM PGMARK_SHIELD_SUB_BASE PGMARK_GAUGE_BAR PGMARK4 PGMARK3 PGMARK2 - sub_82766DB0 (r0, base 0x8207454A) names 12: - notequal lessequal product \x82\x42[`.null Scedilla cacute scedilla B[`.null Cacute - Oslashsmall Uacutesmall Udieresissmall - sub_82766DB0 (r11, base 0x82050000) names 12: instruction options arrays are not supported in inline microcode predication alloc option setGradient instructions muls_prev2 instruction kill instructions diff --git a/docs/re/structures/player-tuning-tables.md b/docs/re/structures/player-tuning-tables.md index a09fb9d..00e4f9d 100644 --- a/docs/re/structures/player-tuning-tables.md +++ b/docs/re/structures/player-tuning-tables.md @@ -151,7 +151,8 @@ against the right answer's 217/226. ✅ **Control, with no prior knowledge: the tool recovers `r30 = 0x82088F94` for the unit loader `sub_82341A20`, resolving 217 of 226 displacements.** It also independently recovers `sub_8230D1F8` (129/132), `sub_822F9498` (90/91) and -`sub_822AE628` (81/108) — and finds **277** such functions image-wide. +`sub_822AE628` (81/108) — and finds **261** such functions image-wide +(**277** before the `r0` correction at the end of this file). ✅ **The answer: `sub_821A6CF0`, `r29 = 0x820A1630`, 22 of 24 displacements.** In code order it names @@ -204,7 +205,7 @@ HP 42 000 with levels at 25 000 / 10 000 and a lock-on release cycle One record, `Generic`, one field — the **name** `eff_n0071` with an empty value. An effect id and nothing else. Not identified. -## ✅ Classifying all 277 rows objectively — 53 are data-table schemas (2026-08-27) +## ✅ Classifying all rows objectively — 50 are data-table schemas (2026-08-27) The earlier pass classified by eye and by function. This one is **by row** and uses an objective test: **are the row's names IDXD record/field names on the @@ -212,8 +213,11 @@ disc?** (13 450 of those disc-wide.) `name_block_bases.py` now prints it. | | rows | |---|---:| -| **≄50 % disc names, ≄8 names — a data-table schema** | **53** | -| everything else (engine/XDK vocabulary, key lists, noise) | 224 | +| **≄50 % disc names, ≄8 names — a data-table schema** | **50** | +| everything else (engine/XDK vocabulary, key lists, noise) | 211 | + +⚠ Written as 53 / 224 of 277; the `r0` correction below withdrew 16 rows, three +of them from the 53. The two axes below are quoted at their original values. Split against the base-confidence axis: `solved` bases (non-zero low half) split 34 / 136, `round` bases 16 / 91 — so **a round base is not the same question as a @@ -229,7 +233,7 @@ no `docs/re/` file): | `sub_823BDAA8` r10 | 25 | `Motion_stand`, `Motion_stand_b1`, `Motion_attackA_start`
 — **motion names**, the `EnumMotions` family `DefTables` declares | | `sub_82315AE8` r11 | 20 | `InitRotation`, `MaxRotationSpeed`, `RotationAccel`, `MaxVerticalSpeed` — the **`Guardian` record's own fields**, i.e. the S16 boss loader | | `sub_8219E560` r11 | 18 | `Detail_Rank`, `MISSIONS`, `DETAIL_TITLE`, `Detail_Board_Permanent` — the **leaderboard screen** keys | -| `sub_825F2CF0` / `sub_825F2F88` r0 | 30 each | `FinalPassBG`, `FinalPassToneRatio`, `FogMin/MaxDistance` — **post-processing**, two functions with the same base | +| ~~`sub_825F2CF0` / `sub_825F2F88` r0~~ | ~~30 each~~ | 🔮 **WITHDRAWN — not a name block at all.** See the `r0` correction below | ⚠ Four rows that *look* new are not: `sub_82297550`, `sub_822A2F00`, `sub_822A9C18`, `sub_82288028` mix arsenal fields the corpus owns @@ -238,10 +242,10 @@ no `docs/re/` file): coordinates as strings** — `757,228`, `903,343`, `1092,457` — which is why their disc-overlap sits at 53–70 % rather than ~100 %. -## ✅ Mining the 277: what the base-solver's index actually contains (2026-08-27) +## ✅ Mining the index: what the base-solver's index actually contains (2026-08-27) -277 rows over **190 distinct functions** (a function can read more than one -block). Classified: +277 rows over **190 distinct functions** — a function can read more than one +block — or **261 over 176** after the `r0` correction below. Classified: **🔮 The tool's false-positive mode, now named.** 107 rows solve to a base on a **64K boundary** — a bare `addis rX, r0, 0xHHHH` with no `addi` of its own, so any @@ -256,9 +260,9 @@ loader `sub_82341A20` (217), stage settings `sub_8230D1F8` (129), `PlayerParams` (`sub_82320B48` → `ORDER_WINGMAN_*`), missile guidance (`sub_8236B608`, `sub_8237BB78` → `st1_up_aperture` etc., [[weapon-datasheet-static]]), shell movement (`sub_82261F70` → `Spiral_BeginTime`, [[weapon-struct-runtime]]), -substructures (`sub_823479B8` → `ParentStructureID`), and six camera/fog readers -(`sub_825F2CF0`, `sub_825F2F88`, `sub_8247DFC0`, `sub_823B2620`, `sub_82222E70`, -`sub_822C7480`). +substructures (`sub_823479B8` → `ParentStructureID`), and ~~six~~ **three** camera/fog readers +(`sub_8247DFC0` r31, `sub_823B2620` r31, `sub_822C7480` r31 — all three bases in +the same `0x8209E6xx` region; the other three were `r0` artefacts, below). **🔑 The find: `sub_8233C368` is the `AIParams` loader.** `r28`, base `0x8208583C`, 20 names — `Enumerate_AIs`, `FiringLength`, `GuardLength`, @@ -289,3 +293,70 @@ positions and look-at tags, with `menu2` having sub-shots `2_0`–`2_4b`. 🟡 None of those five were opened; the index says what each names, not what each means. + +## 🔮 CORRECTION (2026-08-28) — `r0` is not a base register; 16 rows withdrawn + +The last shortlisted row was `sub_825F2CF0` + `sub_825F2F88`, **`r0`**, 30 names +each on the same base `0x8209EB30`, 97 % of them disc names. The question I set +was "a pair, or a clone?" **Both answers are wrong: neither function reads a name +block.** + +Diffing the two, instruction for instruction, settles it in one look — 145 +instructions each, **72 differ and every difference is the same substitution**: + +``` +825F2CF0 addi r11, r0, -288 825F2F88 addi r11, r0, -288 +825F2CF4 stvx v14, r11, r12 825F2F8C lvx v14, r11, r12 +825F2CF8 addi r11, r0, -272 825F2F90 addi r11, r0, -272 +825F2CFC stvx v15, r11, r12 825F2F94 lvx v15, r11, r12 + 
 stvx / stvx128, v14 
 v127 
 lvx / lvx128, v14 
 v127 +``` + +They are the compiler's **VMX register save / restore helper pair** — a spill of +v14–v127 and its matching reload. So they *are* a pair rather than a clone, but +the pairing is `stvx`/`lvx`, not two readers of one table. + +🔑 **The root cause, and it is a class of false positive, not one row.** In +PowerPC `addi rD, rA, N` the `rA` slot **reads as literal zero when it names r0** — +the form is `li rD, N`. The 72 `addi r11, r0, -N` are the vector spill offsets, +plain immediates. The solver had excluded `r1` (the stack pointer) and nothing +else, so it took those offsets as displacements, voted a base, and landed +`0x8209EB30` — **inside the stage-settings name block**, where a scatter of +16-byte-spaced negative offsets is dense enough to "resolve" 30 real field names. + +**A 97 %-disc-names row can still be an artefact.** The disc-name test measures +what the *base region* contains, not that the function reads it. + +✅ **Nothing is lost.** All 30 of the withdrawn names are a **strict subset of the +real reader's block**: `sub_8230D1F8 r29`, base `0x8209FD28`, 129 names — +the stage-settings loader the corpus already owns ([[stage-settings-table]]). +The string-xref join confirms it independently: `FinalPassBG`, `FogMinDistance`, +`ScreenColorR`, `ExposureKey_BeginValue` each exist **once** in the image and +each has **exactly one** xref, from `sub_8230D1F8`. `NoseCameraFOV` and +`GlareType` exist **twice** — the second copy is read by `sub_822AE628`, the +hangar's game-wide defaults, which is the trap the corpus already records. + +**Fix and its effect.** `name_block_bases.py` now skips `reg in ('r0', 'r1')`. +Every number moves by exactly the 16 `r0` rows and nothing else: + +| | before | after | +|---|---:|---:| +| rows | 277 | **261** | +| distinct functions | 190 | **176** | +| bases with a non-zero low half | 170 | **154** | +| bases on a 64K boundary (low confidence) | 107 | 107 | +| data-table schemas (≄50 % disc, ≄8 names) | 53 | **50** | + +Both ways: `154 + 107 = 261`, and `277 − 16 = 261`. The diff against the previous +artefact removes 100 lines and adds 3; **every removed row line carries `r0`**, +the three added lines are those counts, and the control is untouched +(`sub_82341A20 r30 = 0x82088F94`, 217/226). Two of the 16 functions keep a row +through another register, which is why 190 − 176 = 14, not 16. + +The three withdrawn data-table rows are `sub_825F2CF0`, `sub_825F2F88` and +`sub_82222E70` (15 names, 100 % disc — the same `0x8209E6D8` stage-settings +region, and by the same mechanism). + +🟡 What this does **not** settle: the 🟡 stalled `SpaceSize` / `GlareType` / +`Nebura_*` numerics are no closer — their reader was already known to be +`sub_8230D1F8`, and the r0 row never was a second one. diff --git a/tools/re-capture/name_block_bases.py b/tools/re-capture/name_block_bases.py index 57d49d1..f88b6a4 100644 --- a/tools/re-capture/name_block_bases.py +++ b/tools/re-capture/name_block_bases.py @@ -57,7 +57,13 @@ def main(): for name in sorted(byf): for reg in sorted(byf[name]): D = sorted(set(byf[name][reg])) - if len(D) < MIN_GROUP or reg == 'r1': # r1 is the stack pointer + # r1 is the stack pointer. r0 is NOT a base register at all: in + # `addi rD, r0, N` the RA slot reads as literal zero, so the form is + # `li rD, N` and the displacements are plain immediates. Left in, the + # VMX save/restore helper pair sub_825F2CF0 / sub_825F2F88 (72 x + # `addi r11, r0, -N`, the vector spill offsets) solved a base and + # "named" 30 strings, 97% of them disc names -- a pure artefact. + if len(D) < MIN_GROUP or reg in ('r0', 'r1'): continue # Vote: every (string, displacement) pair implies one candidate base. # The true base collects a vote from each name it explains, so it wins