From 413014b8f6e729dde989d57f2896378d9fe384e2 Mon Sep 17 00:00:00 2001 From: James Dansereau <106631954+jkdansereau@users.noreply.github.com> Date: Sun, 27 Sep 2026 10:01:27 -0400 Subject: [PATCH] D343: file-select Options label opens the settings overlay; F10 idle-timer fix One Rule-2 #ifdef PORT draw hook in constructor_menu05_fileselect (user sign-off 2026-09-27). The label's hit test and open request live in optionsoverlay.c; the open is applied on the input thread. While the overlay is open on file select, g_MenuTimer is held at 0 so the screen no longer drops to the legal screen after 30 s. --- docs/dev/OPTIONS-MENU-PLAN.md | 6 +++ docs/dev/findings-index.csv | 1 + docs/dev/findings.md | 29 ++++++++++++ docs/porting-notes.md | 10 ++++ port/include/optionsoverlay.h | 9 ++++ port/src/input.c | 4 ++ port/src/optionsoverlay.c | 86 +++++++++++++++++++++++++++++++++++ src/game/front.c | 9 ++++ 8 files changed, 154 insertions(+) diff --git a/docs/dev/OPTIONS-MENU-PLAN.md b/docs/dev/OPTIONS-MENU-PLAN.md index be3c7eed..a16314ff 100644 --- a/docs/dev/OPTIONS-MENU-PLAN.md +++ b/docs/dev/OPTIONS-MENU-PLAN.md @@ -85,6 +85,12 @@ or are flagged "restart". ## 4. Alternative if an in-fiction surface is required later +> **2026-09-27: route B chosen (v0.4.0 intake).** The entry point is on the +> file select screen, not a main options menu (GE's front end has none). M1 +> landed as D343: an "Options" label that opens the F10 overlay, via one +> Rule-2 `#ifdef PORT` draw hook. M2 (a GE-styled screen that reuses the +> overlay's rows) follows. + Route (B): a new front-end screen `constructor_menuXX_pcoptions` + `menuXX_pcoptions_navigation` in `front.c` under `#ifdef PORT`, reached from a new "PC OPTIONS" row on the main options menu. Each row calls a diff --git a/docs/dev/findings-index.csv b/docs/dev/findings-index.csv index 993f16e9..4c79d3c8 100644 --- a/docs/dev/findings-index.csv +++ b/docs/dev/findings-index.csv @@ -220,3 +220,4 @@ D338,FIXED,"Input.AimRange (F10 ""Aim range""):",FIXED (2026-09-26 bookkeeping: D339,OPEN,"#92 (low-end ~30 fps, Celeron N3060 / HD 400) —","OPEN (2026-09-26, user:" D341,FIXED,"Intro gun-barrel blood drip draws as static blocks/lines instead of dripping (user, v0.4.0, 2026-09-26; death blood fine).","FIXED (M-201, user-verified 2026-09-27):" D342,FIXED,"File select: folder bodies and Bond photos vanish after selecting a file and backing out (user, v0.4.0, 2026-09-26).","FIXED (M-201, user-verified):" +D343,PARTIAL,"File select: PC ""Options"" label opens the settings overlay (v0.4.0 feature, M1 of the route-B options screen, 2026-09-27); also fixes F10 on file select jumpin…",LANDED (M1): one Rule-2 #ifdef PORT draw hook (user sign-off 2026-09-27); click + idle timer A/B verified headless; golden 3/3. diff --git a/docs/dev/findings.md b/docs/dev/findings.md index 1e58dccc..f1a98f89 100644 --- a/docs/dev/findings.md +++ b/docs/dev/findings.md @@ -628,6 +628,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D339 | **#92 (low-end ~30 fps, Celeron N3060 / HD 400) — measured CPU budget: game thread ~0.2–0.35 ms/frame, render-thread display-list pass 0.4–1.8 ms/frame on this box; CPU very likely not the bottleneck. New `GE_PERFSTAT=1` probe (render phases, tris/batches, game-thread busy, GPU timer query).** — full `## D339` entry at file tail | OPEN (2026-09-26, user: review toward the end of v0.4.0; needs a test machine with similar HD 400-class hardware). Earlier: INVESTIGATING — probe landed; target-hardware numbers needed before any fix. | | D341 | **Intro gun-barrel blood drip draws as static blocks/lines instead of dripping (user, v0.4.0, 2026-09-26; death blood fine).** — full `## D341` entry at file tail | FIXED (M-201, user-verified 2026-09-27): two fast3d defects — the 1-cycle blood texrect sampled a stale tile-1 declaration via the D236 LOD rule, and the in-place-regenerated dyn-pool texture hit a stale address-keyed cache entry. | | D342 | **File select: folder bodies and Bond photos vanish after selecting a file and backing out (user, v0.4.0, 2026-09-26).** — full `## D342` entry at file tail | FIXED (M-201, user-verified): scratch placed at the N64 offset +0xA000 of `ptr_logo_and_walletbond_DL` landed inside the larger PC wallet model; moved past its 0x17000 reservation. | +| D343 | **File select: PC "Options" label opens the settings overlay (v0.4.0 feature, M1 of the route-B options screen, 2026-09-27); also fixes F10 on file select jumping to the legal screen after 30 s.** — full `## D343` entry at file tail | LANDED (M1): one Rule-2 `#ifdef PORT` draw hook (user sign-off 2026-09-27); click + idle timer A/B verified headless; golden 3/3. User playtest owed. | Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS threads, compiled GE's real `src/sched.c`, and brought in PD's fast3d software @@ -13531,3 +13532,31 @@ Implemented as `Input.PdMouseAim` (**default off**, because it changes aim feel) **Status: FIXED (M-201, user-verified 2026-09-27).** +## D343 — File select: PC "Options" label + F10 idle-timer fix (M1, 2026-09-27) + +**Feature (v0.4.0 intake):** PD PC reaches its extended settings through its own menus (Options > Extended). GE has no generic menu system (each `front.c` screen is hand-written) and no front-end options screen, so this is "route B" from `docs/dev/OPTIONS-MENU-PLAN.md` §4: a native-looking entry point on the file select screen. M1 adds the entry point and reuses the existing F10 overlay; M2 (a GE-styled screen) is next. + +**Game-code edit (Rule 2, user sign-off 2026-09-27, a feature not an ABI fix):** one additive `#ifdef PORT` block in `constructor_menu05_fileselect` (`src/game/front.c`), right after the Erase label, calling `optionsFileSelectLabel(DL)`. Nothing else in `src/game` changed. Drawing inside GE's own frame means the label fades with screen transitions and gets the D335 pillarbox for free. + +**Port side (`port/src/optionsoverlay.c`, `port/src/input.c`):** +- The label is ASCII "Options" (issue #87 / D295) in Zurich Bold, right-aligned with the Erase text (x 392) at y 24, in the dark strip above the right-hand folder. The bottom row is full; the cursor can reach this spot (clamp 20..420 x 20..310). White normally, gold (`0xEBD879FF`, the folder text colour) when hot. +- Hot = cursor over the text (+4 px), overlay closed, no pending menu change (`menu_update == -1`), and no erase confirmation open (`folder_selected_for_deletion < 0`). The label is clear of every folder and Copy/Erase hit box, so a press on it only triggers front.c's "clear the Copy/Erase mode" path. +- The click is A/Z/Start via `joyGetButtonsPressedThisFrame` (the game's interface tick runs before the constructor each frame, so the latched press is valid). A left click is A in menus. It plays `DOOR_LOCK_SFX` (222, Copy/Erase's sound). +- **Threads:** the hook runs on the game's main thread, but the overlay's open flag is read by the scheduler thread (input poll + `gfx_run`). So the hook only posts an `SDL_atomic_t` request, which `optionsOverlayPollRequests()` applies from `inputComputePad(0)` (the same shape as the D314 wheel queue). +- `textMeasure` returns a height of 0 for text without a trailing newline, which collapsed the first hit box to one row. The height is now measured from `"Options +"`. + +**Bug fixed along the way (existing since F10 landed):** while the overlay is open it gives front.c a neutral pad, so `interface_menu05_fileselect`'s idle timer (`g_MenuTimer`, `front.c:~2304`) never resets. After 30 s the screen went to the legal screen and the intro attract loop. The hook now sets `g_MenuTimer = 0` while the overlay is open, which is the same write front.c makes when a button is pressed. This is scoped by construction: the hook only runs on MENU_FILE_SELECT, the one screen where `g_MenuTimer` is an idle timer. Elsewhere it is the intro/cast-roll animation clock and must keep running, so a blanket "front-end menu" gate would have frozen the intro logos under F10. + +**Verified (Windows native, 640x480 defaults-only ini + `MouseEnabled=0`):** +- Click: `GE_STARTMENU=5 GE_INPUTSCRIPT="300:SUP,SRIGHT;360:SNONE;370:SLEFT;376:SNONE;420:A"` puts the cursor at (386,20), hot=1, press, and logs `optionsoverlay: opened`. The frame shows the overlay over file select. +- Idle A/B (`GE_OPTIONSOVERLAY=1`, 45 s, TEMP switch since removed): with the fix, `current_menu` stays 5 for the whole run. Without it, 5 → 23 → 0 (legal) → 1 → 2 (logos). +- With the overlay closed, the file-select frame differs from the pre-change frame only in the label box (788 px, x 500-571 / y 35-55 at 640x480). There is no file-select golden; the golden gate (Bunker 1) is 3/3. +- Captures at 854x480 (16:9) and 1120x480 (21:9) show the label and highlight correctly pillarboxed. + +**Harness note:** in headless menu runs the menu pointer follows the real OS mouse (WI-2 absolute tracking), so the maintainer's desktop mouse moves the in-game cursor mid-script. Set `[Input] MouseEnabled = 0` in the test ini (porting-notes §E). + +**Owed:** a user playtest: mouse hover/click and pad on the label, F10 and label opening the same overlay, leaving it open for more than 30 s, and 16:9 / 21:9. Then M2 (a GE-styled screen) per the local plan. + +**Status: LANDED (M1), user playtest owed.** + diff --git a/docs/porting-notes.md b/docs/porting-notes.md index 9fcde9d2..0d8896ba 100644 --- a/docs/porting-notes.md +++ b/docs/porting-notes.md @@ -1280,6 +1280,16 @@ GE on N64 renders gameplay at ~20–30 fps, so `g_ClockTimer` (= `speedgraphfram ## E. Process / method notes +- **Headless front-end scripts: turn the mouse off (D343).** In menus the + pointer follows the real OS mouse (WI-2 absolute tracking), so the desktop + mouse moves the in-game cursor mid-`GE_INPUTSCRIPT`. Put + `[Input] MouseEnabled = 0` in the pinned test ini. Script "frames" were + about one per rendered frame on the file select screen; calibrate with a + log, not by assumption. +- **`textMeasure` height is 0 without a trailing newline (D343).** It only + counts completed lines. Measure `"text +"` when you need a height (front.c's + folder text does this); the width is unaffected. - **Periodic audio glitches: measure the host audio device before touching the pipeline (D322).** A clock-regular overflow/garble pattern (~5 min period, ~45 s bursts, independent of level and load) looked like a port timing beat diff --git a/port/include/optionsoverlay.h b/port/include/optionsoverlay.h index df4add48..566652f6 100644 --- a/port/include/optionsoverlay.h +++ b/port/include/optionsoverlay.h @@ -43,6 +43,15 @@ void optionsOverlayScroll(int dir); * overlay is closed. Called by fast3d after running the game DL. */ Gfx *optionsOverlayEmit(void); +/* D343: file-select "Options" label. Called from the one #ifdef PORT hook in + * constructor_menu05_fileselect (game thread): draws the label, and on a + * click posts an open request. */ +Gfx *optionsFileSelectLabel(Gfx *gdl); + +/* Applies a pending label-open request. Called from the input poll + * (scheduler thread, controller 0) before the overlay's swallow gate. */ +void optionsOverlayPollRequests(void); + #ifdef __cplusplus } #endif diff --git a/port/src/input.c b/port/src/input.c index e2be075d..f7840b72 100644 --- a/port/src/input.c +++ b/port/src/input.c @@ -933,6 +933,10 @@ unsigned inputComputePad(int idx, signed char *stick_x, signed char *stick_y) return 0; } + /* D343: apply a pending open from the file-select "Options" label (posted + * on the game thread; the overlay's open flag belongs to this thread). */ + if (idx == 0) optionsOverlayPollRequests(); + /* F10 options overlay: while it is open, controller 0 is fully swallowed * (neutral pad, no stick) and the nav keys / wheel / gamepad drive the * overlay instead. Mirrors the WI-1 "cursor free in a stage -> withhold diff --git a/port/src/optionsoverlay.c b/port/src/optionsoverlay.c index d3e94550..7272d365 100644 --- a/port/src/optionsoverlay.c +++ b/port/src/optionsoverlay.c @@ -1097,3 +1097,89 @@ Gfx *optionsOverlayEmit(void) } return s_buf; } + +/* ------------------------------------------------------------------------ */ +/* D343: "Options" label on the file-select screen (route B entry point, */ +/* docs/dev/OPTIONS-MENU-PLAN.md §4). Drawn from the single #ifdef PORT hook */ +/* in constructor_menu05_fileselect, so it is part of GE's own front-end */ +/* frame (fades with it, D335 pillarbox applies). Clicking it opens the F10 */ +/* overlay. */ +/* */ +/* Thread map: the hook runs on the game's main thread; the overlay's open */ +/* flag is read by the scheduler thread (input poll + gfx_run). So the hook */ +/* only posts a request, which optionsOverlayPollRequests() applies from the */ +/* input poll (same shape as the D314 wheel queue). */ +/* ------------------------------------------------------------------------ */ + +extern struct font *ptrFontZurichBold; +extern struct fontchar *ptrFontZurichBoldChars; +extern float cursor_h_pos; +extern float cursor_v_pos; +extern s32 folder_selected_for_deletion; +extern int menu_update; /* MENU enum (signed, -1 = none) */ +extern s32 g_MenuTimer; +extern u16 joyGetButtonsPressedThisFrame(s8 contpadnum, u16 mask); +extern ALBank *g_musicSfxBufferPtr; /* music.h; sndPlaySfx comes via snd.h */ + +#define FSOPT_MENU_INVALID (-1) +#define FSOPT_PRESS_MASK (0x8000 | 0x2000 | 0x1000) /* A | Z | START, as front.c */ +#define FSOPT_SFX_DOOR_LOCK 222 /* DOOR_LOCK_SFX: Copy/Erase's own click sound */ +#define FSOPT_TEXT_RIGHT 392 /* right edge of the Erase label's text */ +#define FSOPT_TEXT_Y 24 /* dark strip above the right-hand folder */ +#define FSOPT_HIT_PAD 4 + +static const char kFsOptLabel[] = "Options"; /* ASCII only: issue #87 / D295 */ +static const char kFsOptLabelNL[] = "Options\n"; +static SDL_atomic_t s_openRequest; + +void optionsOverlayPollRequests(void) +{ + if (SDL_AtomicSet(&s_openRequest, 0) && !optionsOverlayIsOpen()) { + optionsOverlayToggle(); + } +} + +Gfx *optionsFileSelectLabel(Gfx *gdl) +{ + s32 h = 0, w = 0; + s32 x, y; + int hot; + + /* The overlay swallows pad 0, so front.c's idle timer never resets and + * the file select screen jumped to the legal screen after 30 s. Hold it + * at 0 while the overlay is open -- the same write front.c makes when a + * button is pressed. This hook only runs on MENU_FILE_SELECT, the one + * screen where g_MenuTimer is an idle timer (elsewhere it is the intro / + * cast-roll clock, which must keep running). */ + if (optionsOverlayIsOpen()) { + g_MenuTimer = 0; + } + + s32 unusedw = 0; + + /* textMeasure only counts height for completed lines, so measure the + * height with a trailing newline (front.c's folder text does the same). */ + textMeasure(&unusedw, &w, (char *)kFsOptLabel, ptrFontZurichBoldChars, ptrFontZurichBold, 0); + textMeasure(&h, &unusedw, (char *)kFsOptLabelNL, ptrFontZurichBoldChars, ptrFontZurichBold, 0); + x = FSOPT_TEXT_RIGHT - w; + y = FSOPT_TEXT_Y; + + hot = !optionsOverlayIsOpen() + && menu_update == FSOPT_MENU_INVALID + && folder_selected_for_deletion < 0 + && cursor_h_pos >= (float)(x - FSOPT_HIT_PAD) + && cursor_h_pos <= (float)(x + w + FSOPT_HIT_PAD) + && cursor_v_pos >= (float)(y - FSOPT_HIT_PAD) + && cursor_v_pos <= (float)(y + h + FSOPT_HIT_PAD); + + if (hot && joyGetButtonsPressedThisFrame(0, FSOPT_PRESS_MASK)) { + sndPlaySfx((struct ALBankAlt_s *)g_musicSfxBufferPtr, FSOPT_SFX_DOOR_LOCK, NULL); + SDL_AtomicSet(&s_openRequest, 1); + } + + /* Same font and white as Copy/Erase; gold (the folder text colour) while + * the cursor is over it. */ + return textRender(gdl, &x, &y, (char *)kFsOptLabel, ptrFontZurichBoldChars, + ptrFontZurichBold, hot ? 0xEBD879FF : 0xFFFFFFFF, + viGetX(), viGetY(), 0, 0); +} diff --git a/src/game/front.c b/src/game/front.c index 310eceee..90567f76 100644 --- a/src/game/front.c +++ b/src/game/front.c @@ -2794,6 +2794,15 @@ Gfx *constructor_menu05_fileselect(Gfx *DL) folder_option_ERASE_bound.right = (f32) (textsize.p[0] + textpos.p[1]); +#ifdef PORT + /* D343: PC "Options" label (opens the port settings screen). Draw + * only; the hit test and the open live in port/src/optionsoverlay.c. */ + { + extern Gfx *optionsFileSelectLabel(Gfx *gdl); + DL = optionsFileSelectLabel(DL); + } +#endif + copypos.f[0] = 225.0f; copypos.f[1] = 285.0f;