diff --git a/docs/dev/GE-ENV-PROBES.md b/docs/dev/GE-ENV-PROBES.md index 4773ca05..dece97b7 100644 --- a/docs/dev/GE-ENV-PROBES.md +++ b/docs/dev/GE-ENV-PROBES.md @@ -20,7 +20,7 @@ Two classes: | Env var | File:line | What it does | Status | |---|---|---|---| | `GE_PCDUMP` | `port/fast3d/gfx_opengl.cpp:1079` (`is_pcdump`), `gfx_opengl.cpp:700`, `port/src/video.c:188`, `port/src/config.c:46` (`configGetFrameDump`) | Per-frame framebuffer PPM dump to `$PCDUMP` dir (verification-ritual golden captures, `tools_pc/framediff.py`/`pixcount.py`). Also settable via `[Debug] FrameDump` ini (env wins). **M-33/D168: the writer now emits rows top-to-bottom — captures before that fix are vertically flipped.** | **live** | -| `GE_INPUTSCRIPT` | `port/src/input.c:306` | Headless scripted controller-0 input: `":,…;…"` (`SUP/SDOWN/SLEFT/SRIGHT/SNONE` sustain, buttons pulse). Sole input source when set. | **live** | +| `GE_INPUTSCRIPT` | `port/src/input.c:306` | Headless scripted controller-0 input: `":,…;…"` (`SUP/SDOWN/SLEFT/SRIGHT/SNONE` sustain, buttons pulse; `ZHOLD`/`ZREL` sustain fire, D207 M-201). Sole input source when set. | **live** | | `GE_INPUTLOG` | `port/src/input.c:329`, `port/src/config.c:53` (`configGetInputLog`) | Logs computed pad state (buttons + stick) each poll when non-zero. Also `[Debug] InputLog` ini. Also traces D165 `menuptr est/tgt/eff/stick`. | **live** | | `GE_STARTMENU` (+ `GE_STARTMENU_PAGE`, `GE_STARTMENU_DIFF`) | `src/game/lv.c:398-402` (`#ifdef PORT`) | Boot straight into a front-end menu id (13=MISSION_COMPLETE, 10=BRIEFING, 7=MISSION_SELECT, 12=MISSION_FAILED, 6=MODE_SELECT); `_PAGE`=folder row (def 1=Dam), `_DIFF`=0..3. Crash-test a screen fast. | **live** | | `GE_UNLOCK_ALL` | `src/game/debugmenu_handler.c:1098` (`get_debug_enable_all_levels_flag`, `#ifdef PORT`) | Mission-select shows every solo level (playtest jump-to-any-level aid). Cached in a `static int c`. | **live** | diff --git a/docs/dev/findings.md b/docs/dev/findings.md index 3b632356..d4b5f58b 100644 --- a/docs/dev/findings.md +++ b/docs/dev/findings.md @@ -11370,6 +11370,13 @@ the Surface 1 spawn capture is unchanged across the strip. **Status (from §F table):** **OPEN — not newly root-caused; high confidence this is D207 reproduced, not an independent bug.** Recommend folding this repro into D207 (or closing D241 as a duplicate once D207 is reopened) rather than tracking separately. Next: re-run M-77's `GE_FORCEALARM=1` harness on `-level_09` with scripted PP7 and AK47 fire (not just generic fire) against Klobb as a control, watching `g_sndAllocatedVoicesCount` / the (currently reverted) `[D207-DROP]` counters to see whether the 8-cap is actually hit for two of the three weapons and not the third — or whether it is really the sample-envelope-length distinction above. No runtime verification possible in this environment (no ROM/display). +**2026-09-26 (M-201, v0.4.0): user still hears gunshot drop-outs "with rapid fire guns while a lot of other sound effects occur" (D322 garbling itself is gone). Headless measurements, no drop reproduced.** + +- **Static:** player shots (`gunfire.c:3296-3340`) and guard shots (`chraction.c:5630`) both stop the previous shot sound and start a new one, using at most 2 handles per hand, rate-limited by `SoundTriggerRate` against `g_GlobalTimer`. At 1 tick/frame a shot sound can start every `rate+1` ticks; at the N64's 2–3 ticks/frame the check rounds up to frame boundaries (rate 3: every 6 ticks at 3 ticks/frame vs 4 on PC), so the port plays up to ~1.5x more shot sounds (a D13-class divergence) but does not hold more simultaneous voices. `sndDeactivate` only posts `AL_SNDP_DEACTIVATE_EVT`; the slot frees in `sndDisposeSound`. +- **Runtime (Dam, invincibility + all guns, `GE_FORCEALARM=1`, PP7 held in bursts via new `GE_INPUTSCRIPT` `ZHOLD`/`ZREL`, ~100 s each):** `GE_AUDIOTRACE` `[D207-DROP]` = 0 at both FpsCap 60 and 30 (peak SFX slots 8/8 and 7/8); `GE_D322` at 60 fps: worst sample `sfx` 7/8, `pv free` 9/24, `evtq` 9/64, audio `q` 720/2880, `drop=0`. Both drop paths (`preempt-scan` = 8-cap, `no-limit` = physical pool) are covered by `[D207-DROP]`; failing sounds first retry every 33 ms (audible as late, not missing). +- **Harness limits:** `gunRequestHandWeaponChange` / the level-start equip call did not switch weapons mid-level from the harness, so the automatic-weapon case (AK47/Klobb) is untested headless. +- **Next:** capture the user's real scenario with `GE_AUDIOTRACE=1 GE_D322=1 GE_D204=1` (`scratch/playtest_audio.bat`), noting level, weapon and time of each drop-out; any `[D207-DROP]` line names the layer. If none appear, the "drop-out" is the faithful stop-then-restart of the per-hand shot sound, not a lost voice. + ## D242 — Player-death sequence UX regression vs N64 original: PC requires an LMB click to proceed to the mission-failed/file-select screen; user recalls N64 auto-plays… **Player-death sequence UX regression vs N64 original: PC requires an LMB click to proceed to the mission-failed/file-select screen; user recalls N64 auto-plays 3 death-camera angles then transitions with no input needed (M-106, user QA report).** **The 3-angle auto-replay and its no-input auto-transition both exist, unmodified, in the decomp — confirmed, not a design difference removed for the port.** `bondviewSetCameraMode(CAMERAMODE_DEATH_CAM_SP)` (`src/game/bondview2.c:901-1010`) calls `pickDeathCameraAngles()` (`bondview2.c:641-782`, picks a fresh random valid camera angle each call) to stage each replay; when it returns 0 — no further valid angle found — the code calls `bossRunTitleStage()` directly, and the source comment at `bondview2.c:1006-1007` literally reads *"...or when the three death replays are finished."* The 3-cycle count is driven by `bondviewAdvanceCameraMode()` (`bondview2.c:1041-1078`): `CAMERAMODE_DEATH_CAM_SP` → `CAMERAMODE_DEATH_CAM_MP` → `camera_mode++`, looping back to `DEATH_CAM_SP` while `camera_mode < CAMERAMODE_SWIRL` (three iterations). Each individual replay's own advance is driven purely by animation/timer state (`bondview2.c:1467-1490`): the `g_CameraAfterCinema` flag that `bondviewAdvanceCameraMode()` acts on is set automatically once `modelGetAnimFrame(bodyModel) >= modelGetAnimEndFrame(bodyModel)`, or after a `camera_transition_timer >= 180.0f` tick-timeout fallback when there is no body model — **no button check in that branch; fully automatic.** **The button-press the user is hitting is a real, intentional N64 skip-ahead shortcut, not a port-added gate:** the same code block (`bondview2.c:1483-1489`) *also* fires the identical advance immediately on `(buttons & ~oldbuttons & (CONT_A | B_BUTTON | Z_TRIG | START_BUTTON))` whenever `bonddead && redbloodfinished && deathanimfinished` — i.e. on real N64 hardware, pressing A/B/Z/Start at any point during a death replay skips straight to the end, by design. **On the port, LMB (mouse fire) is mapped to `GE_CONT_G`, which is Z_TRIG** (`port/src/input.c:664-665`: `(mb & SDL_BUTTON(SDL_BUTTON_LEFT)) ... button \|= GE_CONT_G`; binding table `input.c:351`: `{"Z",GE_CONT_G}`) — exactly one of the four original skip buttons. So an LMB click during the death sequence is GE's own original cutscene-skip input, faithfully wired through the port's control remap, not a port-invented "click to continue" prompt. **What this investigation could not determine** (no build/runtime access in this worktree): whether the *automatic*, no-input path actually completes on PC in a reasonable time. `camera_transition_timer` accumulates via `g_GlobalTimerDelta` (`bondview2.c:1470,1493`), which is set from the same `g_ClockTimer`/`speedgraphframes`/`deltaFrames` chain flagged in D240 above and investigated under D155/D186/D193/D204/D209 — if any residual in that chain stalls or mis-scales specifically in this state, the auto-advance could take far longer on PC than on N64 (or effectively never complete before the player gives up and clicks), which would make an LMB click *feel* mandatory even though the ground-truth logic never requires one. diff --git a/port/shim/stdlib.h b/port/shim/stdlib.h index d0b49ede..71f47b50 100644 --- a/port/shim/stdlib.h +++ b/port/shim/stdlib.h @@ -27,6 +27,7 @@ extern double atof(); extern double strtod(); extern long int strtol(); +extern int atoi(); extern int atexit(); /* D324: getenv was previously declared only on non-Windows, on the (wrong) * assumption that MinGW's other CRT headers leak a prototype -- config.c gets diff --git a/port/src/input.c b/port/src/input.c index c7de784d..e2be075d 100644 --- a/port/src/input.c +++ b/port/src/input.c @@ -589,7 +589,8 @@ static void inputOpenPads(void) #define INPUTSCRIPT_PULSE 6 struct scriptEntry { long frame; unsigned mask; int sx, sy; int hasStick; - int hasMouse, mdx, mdy; int hasHold, hold; }; /* D337 mouse/aim tokens */ + int hasMouse, mdx, mdy; int hasHold, hold; /* D337 mouse/aim tokens */ + int hasZHold, zhold; }; /* D207: sustained fire */ static struct scriptEntry scriptEntries[INPUTSCRIPT_MAX]; static int scriptCount = -1; /* -1 = not parsed yet, 0 = parsed empty */ static long scriptFrame = 0; @@ -626,6 +627,10 @@ static void scriptApplyToken(struct scriptEntry *e, const char *s, int n) if (n == 5 && SDL_strncasecmp("RHOLD", s, 5) == 0) { e->hasHold = 1; e->hold = 1; return; } if (n == 5 && SDL_strncasecmp("LHOLD", s, 5) == 0) { e->hasHold = 1; e->hold = 2; return; } if (n == 4 && SDL_strncasecmp("RREL", s, 4) == 0) { e->hasHold = 1; e->hold = 0; return; } + /* D207: ZHOLD / ZREL hold / release fire (Z) for automatic weapons; + * independent of RHOLD so aim + fire can be held together. */ + if (n == 5 && SDL_strncasecmp("ZHOLD", s, 5) == 0) { e->hasZHold = 1; e->zhold = 1; return; } + if (n == 4 && SDL_strncasecmp("ZREL", s, 4) == 0) { e->hasZHold = 1; e->zhold = 0; return; } e->hasStick = 1; if (n == 3 && SDL_strncasecmp("SUP", s, 3) == 0) { e->sy = STICK_MAX; return; } if (n == 5 && SDL_strncasecmp("SDOWN", s, 5) == 0) { e->sy = -STICK_MAX; return; } @@ -659,6 +664,7 @@ static void scriptParse(void) e->hasStick = 0; e->hasMouse = e->mdx = e->mdy = 0; e->hasHold = e->hold = 0; + e->hasZHold = e->zhold = 0; while (*p && *p != ';') { const char *tok = p; while (*p && *p != ',' && *p != ';') ++p; @@ -686,10 +692,10 @@ static int scriptIsActive(void) * Returns the scripted button mask for the current frame; advances the frame * counter (call exactly once per controller-0 read). */ /* D337: current sustained scripted mouse delta / aim hold (latest entry). */ -static int s_scriptMouseOn = 0, s_scriptMDX = 0, s_scriptMDY = 0, s_scriptHold = 0; +static int s_scriptMouseOn = 0, s_scriptMDX = 0, s_scriptMDY = 0, s_scriptHold = 0, s_scriptZHold = 0; static void scriptPreMouse(void) { - long bestM = -1, bestH = -1; + long bestM = -1, bestH = -1, bestZ = -1; for (int i = 0; i < scriptCount; ++i) { long d = scriptFrame - scriptEntries[i].frame; if (d < 0) continue; @@ -703,6 +709,10 @@ static void scriptPreMouse(void) bestH = scriptEntries[i].frame; s_scriptHold = scriptEntries[i].hold; } + if (scriptEntries[i].hasZHold && scriptEntries[i].frame > bestZ) { + bestZ = scriptEntries[i].frame; + s_scriptZHold = scriptEntries[i].zhold; + } } } @@ -711,6 +721,7 @@ static unsigned scriptApply(unsigned button) if (!scriptIsActive()) { return button; } + scriptPreMouse(); /* D207: refresh sustained holds even off the mouse path (idempotent) */ unsigned m = 0; long bestStickFrame = -1; for (int i = 0; i < scriptCount; ++i) { @@ -727,6 +738,7 @@ static unsigned scriptApply(unsigned button) } if (s_scriptHold == 1) m |= GE_CONT_R; /* D337 RHOLD */ if (s_scriptHold == 2) m |= GE_CONT_L; /* D337 LHOLD (Q / "LeanLeft") */ + if (s_scriptZHold) m |= GE_CONT_G; /* D207 ZHOLD (fire) */ scriptFrame++; return m; } diff --git a/port/src/optionsoverlay.c b/port/src/optionsoverlay.c index 06c7c2e8..d3e94550 100644 --- a/port/src/optionsoverlay.c +++ b/port/src/optionsoverlay.c @@ -47,6 +47,11 @@ #include "optionsoverlay.h" #include "../fast3d/gfx_api.h" +/* D324 class: -Iinclude resolves to GE's N64 stub, which does not + * declare lround; without this the call is an implicit `int lround()` + * (GCC only rescued it via its builtin signature). */ +long lround(double x); + /* ---- game symbols (rendering/UI only; see input.c for the same pattern) ---- */ struct font; struct fontchar;