From 1e11e1a5fded55b875f6776e398e08980ea328c1 Mon Sep 17 00:00:00 2001 From: James Dansereau <106631954+jkdansereau@users.noreply.github.com> Date: Tue, 15 Sep 2026 09:28:06 -0400 Subject: [PATCH] v0.2.1 polish: pull Game.ScreenShakeIntensity from the F10 menu (D181) User testing found the slider "basically useless" -- viShake() only scales explosion/effect shake (explosion.c), never the always-on walking head-bob or a getting-shot reaction, which is what "Screen shake" implies to a player. Removed the menu row until it covers all screen-shake/view-bob sources, not just explosions. Config var + fr.c hook untouched. Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi --- docs/dev/findings.md | 2 +- port/src/optionsoverlay.c | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/dev/findings.md b/docs/dev/findings.md index 89ad801b..64ffaae6 100644 --- a/docs/dev/findings.md +++ b/docs/dev/findings.md @@ -438,7 +438,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D173 | **Third-person Bond model spawns too high** (user QA report): the player-representing figure at level start floats well above the ground "a lot of the time" while the actual player spawn is correct; same on the Cuba end credits — JB ~6 ft in the air above Natalya. D75 animated-model family but a *position* defect, not absence (M-33: gun-barrel Bond renders fine). Suspects: intro/credits puppet chr spawn position vs model base transform; anim root-motion accumulation (`modelSetAnimFrame2WithChrStuff`, cf. D156); packed-float `coord3d` decode in intro setup. "Player spawning OK" localises it to the render-side model, not `g_CurrentPlayer`. See `GRAPHICS-BACKLOG.md` D173. | OPEN — observed, not investigated. **M-87 user playtest: still present** — "Bond floating in the sky / cutscene glitch thing still exists." Related, newly reported: **in some cutscenes Bond spins in circles** (named instance: end of Cradle, when Trevelyan falls) — likely the same actor-position/anim-root class rather than a new bug; logged under D148/D160 (`GRAPHICS-BACKLOG.md`) since it's cutscene-triggered. **M-106 investigation (static only — no build/runtime access in this worktree, see note below):** checked the two most D209-shaped candidates for a puppet Y-position aliasing bug and RULED OUT both. (1) `chrSpawnAtPad`/`chrSpawnAtCoord`/`chrAdjustPosForSpawn` (`src/game/chraction.c:10653-10794`) — the puppet-spawn path a cutscene `guard_play_animation`-style AI cmd would use. `PadRecord.stan` (`src/bondtypes.h:1744-1751`) is a ROM-serialized *pointer* field (`StandTile *stan` at offset 0x28) — exactly the tail-pointer shape D122/D126/D132 warn about — and `tools_pc/d88_emit.py`'s `emit_pad()` zeroes it on conversion with a comment claiming it's "dead-on-load, always set before use." **Verified this claim is TRUE, not a converter bug**: every `PadRecord` literal in the N64 source asset tables (checked `assets/obseg/setup/UsetupdamZ.c:37-51`) initialises `stan` to the literal `0` — the ROM binary never carries a live pointer there on N64 either, so zeroing it on PC is byte-identical behaviour, not a regression. `chrAdjustPosForSpawn` receiving a NULL seed `StandTile*` is therefore the original game's normal case, not a port defect. (2) The anim-root-motion Y-accumulation chain (`modelTickAnim`→`modelSetAnimFrame2WithChrStuff`, `model.c:3060+`, `pos24 = pos34 + rotate(pos)` at `model.c:3322`) was already exhaustively audited for exactly this class of bug during D193/D209 (M-80/M-81) and found self-consistent, with the `padding[]`-union sweep that found D209/D210 explicitly noting `act_anim`/`act_dead` (the union arms active during a played-animation/dead puppet) are "layout-stable" (`porting-notes.md` §A, D210 entry) — i.e. that sweep already covers the cutscene-puppet actiontype, and it came back clean. So the two most probable D209-style aliasing sites for this specific symptom are both cold. **Did not find the actual root cause.** Did not get to: the *separate* Cuba end-credits case (`front.c` `init_menu18_displaycast`/`constructor_menu18_displaycast`, `front.c:7821-8320`+ — a **different**, single-actor "cast roster" display model shared by the intro roster and the credits roster, using `getsuboffset`/`subcalcpos`/`cast_rootpos_smoothed` root-motion smoothing, not the in-level chr path at all) — spot-checked `getsuboffset` (`model.c:684-700`) and it is `#MATCH`-verified clean, but did not trace the smoothing math or confirm this is even the code path the "JB above Natalya" report hits (that report implies two actors on screen at once, which this single-`cast_model` code doesn't obviously support — may be a distinct, not-yet-located two-actor cutscene-puppet path). **Confidence: LOW-MEDIUM that this is the D209-style struct-aliasing family at all** — the two most likely aliasing sites check out clean, which argues *against* the shared-root-cause theory for D173 specifically (unlike D160, where the analogous check also came back clean but for a different reason — no pointer fields to alias in the first place). Next investigator: get a live Cradle (`-level_41`) capture of Trevelyan's chr during the death sequence with a position-logging probe (none shipped yet — would need a new `#ifdef PORT` env-gated trace, not added this session per the no-build-verification constraint), and separately locate the actual two-actor Cuba credits code path (grep for wherever `intro_char_table`/cast display is invoked with two simultaneous models, if it exists — not found this session). | | D179 | **Packaged build crashes after the logos — the D43/D69 model + bg sidecars are ROM-derived and absent from any build that has no ROM (M-34, alpha-release QA).** The first `goldeneye-pc-port-*-win64.zip` from CI (bundle-win.sh) shows the Rare/Nintendo logos (compiled-in `assets/rarewarelogo.c`) then AVs at `0x6b157a88`/`0x70157a88` — symbolised: `modelPromoteNodeOffsetsToPointers` (`model.c`) ← `load_object_fill_header` (`objecthandler_2.c`) ← first prop/item model load. Root cause is **not** a code regression: the port loads PC-layout model geometry from `data/pcmodels-/{pcmodels.bin,manifest.csv}` and stage bg/stan from `data/pccg-/{pccg.bin,manifest.csv}`, both produced offline by `tools_pc/d43_emit.py` / `d69_emit.py` from the ROM (see `port/src/pcmodels.c` / `pccg.c`; `pcmodelsReserveSize` logs *"pcmodels.bin not found — model loads will fail"* and returns 0). CI has no ROM so it never runs the emit scripts, and the two `data/` dirs are (correctly) gitignored ROM-derived game data — **cannot be committed or shipped** (converted Nintendo/Rare geometry, DLs, collision, stan nav; = distributing assets). A local build with those dirs present runs clean (verified M-34: 1741 frames, `romdataInit ... mapped at 0x10000000`, `pcmodels: 512 sidecars`, `pccg: 73 sidecars`, no crash — so `0x10000000` is *not* the problem here). The `docs/building.md` sidecar-gen step was also missing entirely. **Fix = release-side, backlogged** (`docs/BACKLOG.md` → "Alpha release"): bundle the emit scripts + their committed inputs as a pure-stdlib-Python asset-prep tool the user runs once against their own ROM (~5 MB output, no MIPS toolchain). Secondary/latent: the fixed-address `VirtualAlloc((LPVOID)0x10000000)` in `romdata.c` has no working fallback (`"using heap copy — direct ROM reads will fail"` then limps into the same crash) — didn't bite M-34 but will on a machine where something occupies `0x10000000`; harden separately (reserve earliest in `main`, or retry low bases and derive all segment math from the base obtained). | Diagnosed, not fixed — release packaging gap + latent `romdata.c` fallback | | D180 | **Native-PC input pass (M-XX QoL run, `qol/native-pc-input-menu`, port-only).** Three parts, all config-gated with default = prior behaviour: **(WI-1) `Input.MouseCaptureMode`** (0 = legacy always-grab; 1 = Quake-style click-to-lock) in `port/src/input.c` + `port/src/video.c` — in mode 1 the OS cursor is free until a click lands in the game window (`SDL_MOUSEBUTTONDOWN` → `inputNotifyClick`), and ESC / focus-loss / entering any front-end menu (`current_menu != MENU_RUN_STAGE`) frees it again; re-entering a stage while still "armed" re-locks so unpausing needs no click. `reconcileGrab()` runs once per controller-0 poll. Mouse buttons are suppressed from the game while the cursor is free in-stage (no phantom fire). Controller paths untouched. **(WI-2) absolute-cursor menu tracking** — when capture mode is on and the cursor is free in a menu, the D165/D169 pointer P-controller takes its target from the real cursor's absolute window position mapped onto the live virtual front-end rect (`getPlayer_c_screen*`), giving true 1:1 tracking instead of the relative-delta estimator. Relative path unchanged for legacy/grabbed. **(B3) `Input.MouseAimSpeed` default 25 → 16** (aim mode still overshot at 25 per the backlog). | LANDED, port-only, no `#ifdef PORT`. Build 241/241, `-level_09` framediff 3/3 within threshold (unregressed), `GE_STARTMENU` menu boot crash-free. **Feel-checks owed** (headless can't drive the mouse): click-to-lock ergonomics, absolute menu tracking, the new aim-speed default. | -| D181 | **`Game.ScreenShakeIntensity` — first route-(b) `src/` gameplay-cosmetic hook (M-XX QoL run).** `src/fr.c viShake()` gains a single `#ifdef PORT` line — `intensity *= portScreenShakeScale;` (extern `f32`, defined + `configRegisterFloat`'d 0.0–10.0 in `port/src/video.c`) — before the existing clamp. Default `1.0f` ⇒ exact no-op, every headless golden dump byte-unaffected; `0.0` disables explosion/effect screen shake, up to `10.0` exaggerates it. N64 build (no `-DPORT`) keeps the original line verbatim. Precedent per `docs/BACKLOG.md` "Fun features → screen-shake intensity slider" and AGENTS.md #2's documented-exception path. Same policy class as a future FOV hook. | LANDED behind config, default = original. Build green. | +| D181 | **`Game.ScreenShakeIntensity` — first route-(b) `src/` gameplay-cosmetic hook (M-XX QoL run).** `src/fr.c viShake()` gains a single `#ifdef PORT` line — `intensity *= portScreenShakeScale;` (extern `f32`, defined + `configRegisterFloat`'d 0.0–10.0 in `port/src/video.c`) — before the existing clamp. Default `1.0f` ⇒ exact no-op, every headless golden dump byte-unaffected; `0.0` disables explosion/effect screen shake, up to `10.0` exaggerates it. N64 build (no `-DPORT`) keeps the original line verbatim. Precedent per `docs/BACKLOG.md` "Fun features → screen-shake intensity slider" and AGENTS.md #2's documented-exception path. Same policy class as a future FOV hook. | **Menu row pulled (v0.2.1, user testing).** The hook is correct on its own narrow terms — `viShake()` is called only from `explosion.c`, so the slider does exactly what it says (scales explosion/effect shake) — but that's a much narrower scope than the "Screen shake" label implies to a player: it does not touch the always-on walking head-bob or any getting-shot reaction, so a user swinging it from 0 to max during ordinary play sees nothing and reasonably calls it broken. Removed from `port/src/optionsoverlay.c`'s row table until it covers all screen-shake/view-bob sources, not just explosions; config var (`portScreenShakeScale`) and the `fr.c` hook are untouched. Build green. | | D184 | **F10 port-layer options overlay — the approach-(C) surface from `OPTIONS-MENU-PLAN.md` (M-37).** New `port/src/optionsoverlay.c` + `port/include/optionsoverlay.h`: an immediate-mode overlay drawn as its own fast3d 2D DL, appended after the game DL in `gfx_pc.cpp gfx_run()` (`optionsOverlayEmit()` → NULL when closed ⇒ **zero bytes appended, golden dumps byte-identical**). F10 toggles (`video.c videoPumpEvents`, next to F12); while open, `input.c inputComputePad(0)` returns a neutral pad and routes arrows/enter/wheel/mouse-buttons to `optionsOverlayHandleInput()` (mirrors the WI-1/D180 swallow). ESC closes; `configSave()` on close. `config.c` gains `configForEachOption()` + a `configSetOptionMeta()` label/step/enum side table (no per-key knowledge in config.c). Live knobs (VSync/FpsCap/TextureFilter via `videoRequestLiveConfig()`; mouse + screen-shake via the registered pointer) apply immediately; MSAA tagged "(restart)". v1 rows per plan §3: VSync, FpsCap, MSAA, TextureFilter, MouseAimSpeed, MouseTurnSpeed, MouseInvertY, MouseCaptureMode, ScreenShakeIntensity. | **LANDED, port-only, no `#ifdef PORT` in `src/`.** Build links 241/241. **Runtime verification NOT run this session** — the headless env kills the GUI process after ~8 s and `gfx_opengl_dump_bound_fbo` writes 0-byte PPMs here, so framediff / 60 s soak / overlay-frame eyeball are all owed on an interactive machine. Overlay-closed no-op is code-evident (emit returns NULL before touching the DL buffer). | | D211 | **PC FOV slider — `Video.FovScale` (M-83 QoL wave, port-only, `WIDESCREEN-FOV-PLAN.md` Phase 4).** fast3d applies a multiplicative half-angle scale to every *perspective* projection matrix as it is loaded (`gfx_apply_fov_scale()` in `gfx_pc.cpp gfx_sp_matrix`, on the `G_MTX_PROJECTION | G_MTX_LOAD` path only — GE always reloads its view projection via `guPerspectiveF` at `fr.c:725`, so a relative `G_MTX_MUL` onto an already-scaled matrix cannot compound). Columns 0 and 1 are scaled by the same factor `tan(half_orig)/tan(half_adj)` → H and V FOV widen uniformly, aspect ratio exactly preserved (no stretch), and because the game rewrites fovy every frame for aim-zoom the scale composes with zoom for free. Ortho matrices (`P[3][3]==1`) are left untouched so HUD/menu 2D is unaffected. `Video.FovScale` = percent of the original vertical FOV, 50–150, **default 100 ⇒ `g_fov_scale == 1.0f` ⇒ guarded early-return, zero FP ops on the matrix ⇒ golden dumps byte-identical**. F10 overlay row "FOV scale %". Math checked offline (`s=1.25 → fovy 60°→75°`, aspect held at 1.3333). | **PARTIAL — M-84 refix: game-side FOV scale (`src/fr.c`, `#ifdef PORT`), front-end gated, user-verified.** Original M-83 note follows. LANDED, port-only; build clean; `bunker1` framediff worst_cell 12.219 vs 12.221 baseline (= render noise, no-op confirmed). **M-84 refix — the fast3d hook only scaled *pure* perspective loads; GE draws the world through `g_CurrentPlayer->field_10E0`, a projection × view matrix pre-combined on the CPU (`bondview2.c:8224`) that failed the `gfx_apply_fov_scale` guard, so only the pause/watch model + sky FOV changed (user report).** Moved the scale game-side to the `guPerspectiveF` chokepoint (`fr.c:718`, `#ifdef PORT`, `frFovY *= portFovScale`, clamp 160°), which lands *before* the CPU combine; fast3d `gfx_apply_fov_scale`/`gfx_set_fov_scale` removed; `port/src/video.c` sets `f32 portFovScale`. Also gated to `lvlGetCurrentStageToLoad() != LEVELID_TITLE` — the front end (main menu / file+mission select / briefing) renders through the same path (`lv.c lvlRender`) and its 3D is authored at a fixed FOV. **User-verified: front end unaffected + in-level view widens (Dam tunnel at high FovScale).** M-85 headless nit check (`-level_09`, FovScale 100 vs 120): **HUD-static confirmed** — the ammo counter ("7 | 93") sits at the identical bottom-right screen position in both, and the door-arrow stays square (no 4:3 stretch); viewmodel scales down + stays bottom-right anchored. **aim-zoom-compose — M-86 static analysis: correct by construction.** `bondview2.c:3123` lerps `zoominfovy` then `viSetFovY()` stores it in `g_ViBackData->fovy`; `fr.c:737` applies `frFovY = fovy * portFovScale` **once**, at the per-frame chokepoint, *after* the zoom lerp. Hipfire and zoomed states scale by the same factor so the zoom magnification ratio is preserved exactly; no double-application; the smooth zoom interpolation is unaffected (scale is post-lerp). Residual human eyeball (low risk, cosmetic) at FovScale ~120–150 + full aim-zoom: the viewmodel (`c_perspfovy`, `bondview.c:644`) and frustum-cull (`currentPlayerSetCameraScale`) use the *unscaled* fovy, so a subtle viewmodel-scale or edge-cull-early mismatch is possible; the scope reticle is 2D screen-space, unaffected. Caveat (same root): edge geometry culls a hair early at large scale (cosmetic). **User-verified good at big FovScale.** New adjacent report → **D220**: 1P muzzle flash position shifts upward at higher FovScale (Jungle, M16) — likely the same unscaled-viewmodel-fovy root as the caveat above. Wide FOV also exposes the per-level draw-distance boundary → **D218** (logged, parked). **M-87: the frustum-cull residual is a real bug, not cosmetic** → split to **D222**. | | D212 | **Anisotropic-filtering config wiring — `Video.Anisotropy` (M-83 QoL wave, port-only).** The fast3d GL backend already implemented `set_anisotropy_level` / `get_max_anisotropy_level` and applied `GL_TEXTURE_MAX_ANISOTROPY` on mipmapped tiles with a hardcoded `current_anisotropy_level = 4`; there was no way to configure it. Added the `gfx_set_anisotropy_level()` extern-C wrapper in `gfx_pc.cpp` (clamps to `[1, GL max]`, `reset_texture_state()` so live changes take), declared in `gfx_api.h`, and `Video.Anisotropy` (1–16) in `port/src/video.c` wired through `videoApplyImageOptions()` on init + live-config. **Default 4 = the value fast3d already applied ⇒ no visual delta at defaults.** F10 overlay row "Anisotropic". | **LANDED, port-only.** Build clean. **Owed:** eyeball at 16× on a grazing surface (Depot roof / Dam walkway) vs 1×. | diff --git a/port/src/optionsoverlay.c b/port/src/optionsoverlay.c index e1abea30..f8d0c4f8 100644 --- a/port/src/optionsoverlay.c +++ b/port/src/optionsoverlay.c @@ -123,7 +123,13 @@ static struct Row rows[] = { { "Input.MouseTurnSpeed", "Mouse turn speed", ROW_SLIDER, 1, NULL, 0, 0, 100, 0,0,0,0,0 }, { "Input.SensLink", "Link aim/turn sens",ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, { "Input.MouseInvertY", "Mouse invert Y", ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, - { "Game.ScreenShakeIntensity","Screen shake", ROW_SLIDER, 0.25, NULL, 0, 0, 3, 0,0,0,0,0 }, + /* D181/Game.ScreenShakeIntensity: user testing (v0.2.1) found the slider + * "basically useless" -- viShake() is only called from explosion.c, so it + * scales explosion shake alone; it never touches the always-on walking + * head-bob or any getting-shot reaction, which is what "Screen shake" + * reads as to a player. Pulled from the menu until it covers all + * screen-shake/view-bob sources, not just explosions. Config var + fr.c + * hook stay in place. */ /* D232: the community "no damage flash" toggle (suppresses the red/green * hit-flash overlay in bondview2). */ { "Game.NoHitFlash", "No hit flash", ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 },