D281: AllUnlocked on a fresh install now yields the game's own fresh-slot layout

D259 seeded only the volumes on all-zero slots. Because the AllUnlocked read
patch re-CRCs every slot, a fresh eep survived validation as five folder-1
slots with options 0: sight-on-screen off (no crosshair while aiming = the
"RMB aim broken" report) plus auto-aim/look-ahead/ammo off, and the game
reset/rebuilt the duplicates mid-validate. All-zero slots now become what
fileValidateSaves + fileBuildWriteNewSave would build (folder saves from
BLANKSAVEDATA via the game's own setters, leftovers free).

Verified: fresh eep + AllUnlocked + SAVELOG -> 00/21/42/63/80, all CRC OK,
no repair writes. Golden 3/3 (x2 reruns); Windows + Linux builds clean.
This commit is contained in:
James Dansereau committed 2026-09-23 13:27:28 -04:00
1 parent 27a560cd0b
commit 971c2d9f62
3 files changed
+49 -18

No files matched your search

+1 -1
View File
@@ -96,7 +96,7 @@ D270,?,Backfilled (M-132) — this label was never given its own row; only code
D271,FIXED,"D236 wall, third pass —",FIXED-but-insufficient (src/game/bg.c #ifdef PORT) —
D276,OPEN,"D236 wall, eighth pass —","REFRAMED — oml=0xc81049d8/fmt=3 is the primary suspect now, not D269's CI8 pair; 0x0c184b50 unattributed; wall still open, see D236"
D279,?,Backfilled — this label was consumed by an undocumented partial session (2026-09-14 ~23:00) that left artifacts in scratch/ (d279_run.log… —,undocumented — artifacts only
D281,OPEN,"Game.AllUnlocked (D257) still breaks audio, and can also break right-mouse aim, when enabled before any save has ever been written (v0.2.1 user testing… —","OPEN — user-reported regression, not root-caused; mitigation is user-facing guidance only (release notes:"
D281,FIXED,"Game.AllUnlocked (D257) still breaks audio, and can also break right-mouse aim, when enabled before any save has ever been written (v0.2.1 user testing… —","FIXED (2026-09-23, port/src/libultra.c):"
D282,FIXED,"QoL ask (v0.2.1 user testing, 2026-09-15):","FIXED (port/src/input.c, #ifdef not needed —"
D283,OPEN,"Steam Deck first-run preset (D-unnumbered in port/src/video.c/main.c, videoApplySteamOSDefaults()) did not apply for a user who added the game via Steam… —","OPEN — mechanism understood, root cause (Desktop-vs-Game-Mode STEAMOS env var difference) unverified on real hardware; user-facing workaround documented in README/release notes."
D280,?,"D236 wall, ninth pass —","REFRAMED again — no fix; next steps in detail section, see D236"
1 # GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
96 D271
97 D276
98 D279
99 D281
100 D282
101 D283
102 D280
+3 -1
View File
@@ -495,7 +495,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D271 | **D236 wall, third pass — suspect #3 (draw density/culling) CONFIRMED as a real over-draw; the D106 guard was the cause (M-129).** — full `## D271` entry at file tail | FIXED-but-insufficient (`src/game/bg.c` `#ifdef PORT`) — **landed** `532092c7` (M-139 R2 extraction, probes stripped); wall still open, see D236 |
| D276 | **D236 wall, eighth pass — the true painter of the visible weave is a fully-opaque IA8 blocky-noise texture, not either of D269's named CI8 classes (M-134).** — full `## D276` entry at file tail | REFRAMED — `oml=0xc81049d8`/`fmt=3` is the primary suspect now, not D269's CI8 pair; `0x0c184b50` unattributed; wall still open, see D236 |
| D279 | **Backfilled — this label was consumed by an undocumented partial session (2026-09-14 ~23:00) that left artifacts in `scratch/` (`d279_run.log`…** — full `## D279` entry at file tail | undocumented — artifacts only |
| D281 | **`Game.AllUnlocked` (D257) still breaks audio, and can also break right-mouse aim, when enabled before any save has ever been written (v0.2.1 user testing…** — full `## D281` entry at file tail | OPEN — user-reported regression, not root-caused; mitigation is user-facing guidance only (release notes: don't enable until a save exists, treat as experimental). |
| D281 | **`Game.AllUnlocked` (D257) still breaks audio, and can also break right-mouse aim, when enabled before any save has ever been written (v0.2.1 user testing…** — full `## D281` entry at file tail | FIXED (2026-09-23, port/src/libultra.c): the AllUnlocked patch now reproduces the game's fresh-slot result (folder saves with DEFAULT_OPTIONS + one free slot) instead of five folder-1 slots with options 0 (no aim sight). Verified by fresh-eep SAVELOG; audio half owed a user check. |
| D282 | **QoL ask (v0.2.1 user testing, 2026-09-15): front-end menu navigation (main menu, file select, mission-select map) currently requires the RIGHT analog stick —…** — full `## D282` entry at file tail | FIXED (`port/src/input.c`, `#ifdef` not needed — port-layer-only file), feel unconfirmed. |
| D283 | **Steam Deck first-run preset (D-unnumbered in `port/src/video.c`/`main.c`, `videoApplySteamOSDefaults()`) did not apply for a user who added the game via Steam…** — full `## D283` entry at file tail | OPEN — mechanism understood, root cause (Desktop-vs-Game-Mode `STEAMOS` env var difference) unverified on real hardware; user-facing workaround documented in README/release notes. |
| D280 | **D236 wall, ninth pass — fog math VERIFIED N64-faithful; the IA8 noise class VISUALLY confirmed as the weave's painter (M-135).** No quick win for trees. — full `## D280` entry at file tail | REFRAMED again — no fix; next steps in detail section, see D236 |
@@ -11695,6 +11695,8 @@ Superseded history below (the original OPEN investigation, root cause not found
**Status (from §F table):** OPEN — user-reported regression, not root-caused; mitigation is user-facing guidance only (release notes: don't enable until a save exists, treat as experimental).
**Root cause + fix (2026-09-23, v0.4.0):** D259 emulated only part of the fresh-slot path. Without AllUnlocked, an all-zero slot fails `fileValidateSaves`' CRC → `fileResetSave` → a *free* `BLANKSAVEDATA` slot (DORESET, vol 0xFF, `options = DEFAULT_OPTIONS` 0x3A), and then `fileBuildWriteNewSave` fills the first free slot for each folder without a save. The AllUnlocked read patch re-CRCs every slot, so all-zero slots instead **survived** as five non-free slots all claiming folder 1, with `options = 0` (sight-on-screen OFF = no crosshair when aiming, which is the "RMB aim broken" symptom; auto-aim, look-ahead and ammo display also off). The game then reset/rebuilt the duplicate folder slots mid-validate. **Fix (`port/src/libultra.c` `geEepromPatchAllCheats`, port-only):** all-zero slots take the folders no real slot holds, in order, built exactly as `fileBuildWriteNewSave` does (`BLANKSAVEDATA` + `fileSetSaveFoldernum`/`fileSetSaveFlagDoReset(FALSE)`/`fileSetSelectedBond`, the game's own setters); leftovers become free `BLANKSAVEDATA`. Real saves keep their bytes. **Verified (native Windows, fresh zero eep, `AllUnlocked=1`, `SkipIntro=1`, `GE_SAVELOG=1`):** `fileValidateSaves` now sees `00/21/42/63/80` (folders 1–4 + one free slot, the layout a normal fresh install produces), all CRC OK and no repair writes; the eep on disk stays pristine (read-time patch). Before: five `00` slots. Golden 3/3 (AllUnlocked off by default, so the change is inert there); Windows + Linux builds clean. **Owed:** the audio half of the report was never mechanised. The volumes were already right after D259, and the folder-collision churn this removes is the most plausible remaining cause. User check: fresh install (move `ge007.eep` aside) → enable All unlocked → play a level; audio and RMB-aim crosshair should both work.
## D282 — QoL ask (v0.2.1 user testing, 2026-09-15): front-end menu navigation (main menu, file select, mission-select map) currently requires the RIGHT analog stick —…
**QoL ask (v0.2.1 user testing, 2026-09-15): front-end menu navigation (main menu, file select, mission-select map) currently requires the RIGHT analog stick — matches N64 default control schemes but is unintuitive on a modern controller/Steam Deck, where the left stick is the expected primary-navigation input.** Distinct from the F10 options overlay, which is already left-stick/D-pad navigable (`port/src/optionsoverlay.c`). Scope: front-end menu input in `src/game/front.c` (or wherever N64 stick reads are gated by the active `controldef`) — likely wants a port-layer input remap for these menu contexts specifically, not a general control-scheme change. **M-141: root cause found + fixed, port-only.** `front.c`'s menu-nav code reads `joyGetStickX/Y` — the same emulated N64 "Control Stick" channel (`stick_x`/`stick_y`) that in-level gameplay uses. `port/src/input.c`'s gamepad poll routes that channel to the LEFT physical stick by default, but `naturalPitchMode` (D194/D238's SOLITARE-style scheme, **default-on**) reroutes it to the RIGHT stick for continuous look — and since menus share the channel, they silently inherited that reroute too. The poll already computed an unused `padMenuMode` flag (`current_menu != GE_MENU_RUN_STAGE/GE_MENU_INVALID`, previously only gating shoulder-button weapon-cycle edges); moved it earlier and added a `padMenuMode` branch that always sources `stick_x`/`stick_y` from the left stick outside a running stage, regardless of `naturalPitchMode` — matching the F10 overlay's existing left-stick convention (`optionsoverlay.c`). Zero effect on in-level control feel (the branch is inert whenever `current_menu == GE_MENU_RUN_STAGE`). Verified: build green; `-level_09`/`-level_20` unregressed (1100+ frames each, no crash) — bare `-level_XX` launches skip the front end entirely, so this only proves the gameplay path (unaffected) stays clean. **In-menu feel not interactively verified this session** (no physical gamepad available in this environment) — needs a human playtest with a controller to confirm the menu now navigates on the left stick as expected.
+45 -16
View File
@@ -1279,26 +1279,55 @@ static void geEepromPatchAllCheats(u8 *buf)
memset(&slots[5], 0, sizeof(ge_save_slot));
int changed = 0;
for (int i = 0; i < 5; i++) {
/* D259: a fresh ge007.eep is zero-filled, so every slot reads as
* all-zero. Normally such a slot fails fileValidateSaves' CRC and is
* reset to BLANKSAVEDATA (music_vol/sfx_vol = 0xFF); but this patch
* gives the slot a valid CRC below, so it survives with volume 0
* -> silence. Emulate fileResetSave: seed max volume on all-zero
* slots only -- real saves (incl. a deliberately muted one) keep
* their bytes. */
{
/* D259 + D281: a fresh ge007.eep is zero-filled, so every slot reads as
* all-zero. Without this patch such a slot fails fileValidateSaves' CRC
* and becomes a free BLANKSAVEDATA slot (fileResetSave), after which
* fileBuildWriteNewSave fills the first free slot for each folder with
* no save, in folder order. This patch gives every slot a valid CRC
* below, so an all-zero slot would instead SURVIVE as-is: five slots all
* claiming folder 1 with volume 0 (D259: silence) and options 0 (D281:
* sight-on-screen, auto-aim, look-ahead and ammo display all off, which
* reads as "RMB aim broken"). D259 seeded only the volumes. Reproduce
* the game's full result instead: all-zero slots take the folders that
* no real slot holds, in order, built exactly as fileBuildWriteNewSave
* builds them (BLANKSAVEDATA + folder + not-free + bond); any left over
* become free BLANKSAVEDATA slots. Real saves keep their bytes. */
{
extern void fileSetSaveFoldernum(void *save, u32 folder);
extern void fileSetSaveFlagDoReset(void *save, s32 enable);
extern void fileSetSelectedBond(void *save, s32 bond);
static const ge_save_slot blank = {
0, 0, 0x80 /* SAVEFLAGS_SET(0,0,BOND_BROSNAN,1): free */, 0x00,
0xFF, 0xFF, 0x3A /* DEFAULT_OPTIONS */, 0, 0, 0, 0, {0}
};
int allzero[5], present[4] = {0, 0, 0, 0};
for (int i = 0; i < 5; i++) {
const u8 *raw = (const u8 *)&slots[i];
int allzero = 1;
allzero[i] = 1;
for (int b = 0; b < (int)sizeof(ge_save_slot); b++)
if (raw[b]) { allzero = 0; break; }
if (allzero && (slots[i].music_vol != 0xFF ||
slots[i].sfx_vol != 0xFF)) {
slots[i].music_vol = 0xFF;
slots[i].sfx_vol = 0xFF;
changed = 1;
if (raw[b]) { allzero[i] = 0; break; }
if (!allzero[i] && !(slots[i].completion_bitflags & 0x80 /* DORESET */)) {
int f = slots[i].completion_bitflags & 0x7; /* SAVEFLAG_FOLDER */
if (f < 4) present[f] = 1; /* MAX_FOLDER_COUNT */
}
}
int folder = 0;
for (int i = 0; i < 5; i++) {
if (!allzero[i]) continue;
slots[i] = blank;
while (folder < 4 && present[folder]) folder++;
if (folder < 4) {
fileSetSaveFoldernum(&slots[i], (u32)folder);
fileSetSaveFlagDoReset(&slots[i], 0);
fileSetSelectedBond(&slots[i], folder);
present[folder] = 1;
}
changed = 1;
}
}
for (int i = 0; i < 5; i++) {
/* Cheat ids are level ids 0..19 (CHEAT_INPUT_BUFFER_SIZE == 20):
* bits 0-7 in _1, 8-15 in _2, 16-19 in the low nibble of _3. */
if (slots[i].unlocked_cheats_1 != 0xFF ||