input: fix reticule jitter in aim mode — adopt the PD mouse-aim model (D332, PR #96)

Opt-in Input.PdMouseAim (F10 "PD mouse aim"): in aim mode the port only
accumulates the mouse position and the game's own crosshair integrator is
driven by it with PD's mouse damp (0.01), instead of GEPD's per-poll
overwrite fighting the weapon's ~0.8 per-tick damp (write*damp^k jitter).
Hook: #ifdef PORT block in sub_GAME_7F067FBC (aim-mode branch only),
identity when off; Rule-2 sign-off given by the maintainer.

Integrated locally from PR #96 with review fixes: finding renumbered
D337 -> D332; GE_PDMOUSEAIM lookup cached (GE_ENVSTR; it ran every poll
and aim tick); "live env" comments corrected. Windows build clean; bunker1
golden 3/3. Feel A/B owed.
This commit is contained in:
Dan Turner authored and James Dansereau committed 2026-09-23 14:29:27 -04:00
1 parent 3a7c5d06c5
commit 05e5525db9
5 files changed
+173 -21

No files matched your search

+1
View File
@@ -209,3 +209,4 @@ D328,CLOSED,"Mod support is viable and D327's ""ruled out"" was wrong:",CLOSED a
D329,FIXED,The D318/D320 guard anim pin is a tick-granularity divergence:,"FIXED — Rule-2 high-FPS fix in model.c; clk=1 no longer pins (WSL + native Windows), 0 triggers at clk=2; bunker1 golden failure predates it (on/off A/B); user playtest confirms N64-matching Ourumov…"
D330,FIXED,"#103 (v0.3.0, Win 11):","FIXED (port/src/input.c), build clean, bunker1 golden 3/3; mouse feel-check owed (a human must run it:"
D331,FIXED,Ejected shell casings never rendered on PC:,"FIXED (gunfire.c #ifdef PORT, explicit template); PP7 casing verified on native Windows (before:"
D332,FIXED,Reticule jitter in aim mode (RMB) —,FIXED (fix/mouse-pd-model) —
1 # GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
209 D329
210 D330
211 D331
212 D332
+23
View File
@@ -617,6 +617,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D329 | **The D318/D320 guard anim pin is a tick-granularity divergence: it exists only at 1 sim tick per rendered frame (port at 60 fps); at 2+ ticks/frame (GE's normal N64 regime) it cannot persist (lockstep A/B, 2026-09-23).** — full `## D329` entry at file tail | FIXED — Rule-2 high-FPS fix in model.c; clk=1 no longer pins (WSL + native Windows), 0 triggers at clk=2; bunker1 golden failure predates it (on/off A/B); user playtest confirms N64-matching Ourumov timing; watchdog stays shipped for now. |
| D330 | **#103 (v0.3.0, Win 11): Auto-Aim / Look Ahead "won't stay on" in the in-game watch options: the mouse drove the analog stick inside the watch, and a small leftward flick selected OFF.** — full `## D330` entry at file tail | FIXED (port/src/input.c), build clean, bunker1 golden 3/3; mouse feel-check owed (a human must run it: select Auto-Aim, set ON, move the mouse; it must stay ON). |
| D331 | **Ejected shell casings never rendered on PC: `g_DefaultCasingModelRenderData` (u32[15] template) read as the wider PC `ModelRenderData` gave flags 0 (third instance of the D264 pattern; found by the D290 sibling sweep, never user-reported).** — full `## D331` entry at file tail | FIXED (gunfire.c `#ifdef PORT`, explicit template); PP7 casing verified on native Windows (before: none; after: brass casing ejects and tumbles); golden 3/3; Win + Linux builds clean. |
| D332 | **Reticule jitter in aim mode (RMB) — ROOT-CAUSED from live captures; fixed by adopting the Perfect Dark port's mouse-aim model behind `Input.PdMouseAim`.** The game's damped crosshair update runs once per SIM TICK while the port's GEPD overwrite runs once per input POLL, so the value displayed is `write * damp^k` with k varying (k=1 66%, k=2 18%, k=0 17% of frames). At damp 0.8 that stepped the drawn reticule 20% whenever k changed | **FIXED (`fix/mouse-pd-model`)** — `Input.PdMouseAim` (default off, live via `GE_PDMOUSEAim`): the port accumulates only and the game's integrator is driven by the port-supplied turn with PD's near-zero damp (0.01), via a `#ifdef PORT` hook in `sub_GAME_7F067FBC`. Two earlier fixes were tried and reverted (see the `## D332` entry). **Awaiting feel-test.** |
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
@@ -13211,3 +13212,25 @@ So a player sets Auto-Aim ON with a key, touches the mouse, and it snaps back OF
## D331 — Ejected shell casings never rendered on PC (D264 pattern, third site) (2026-09-23)
Found by the sibling-site sweep that D290 prompted (porting-notes §B "one layout bug often has sibling call sites"); never user-reported, likely because a missing casing is easy to overlook. `sub_GAME_7F068EC4` (`gunfire.c`, casing draw) initialises its render data with `*(ModelRenderData *)g_DefaultCasingModelRenderData`, where the global is `u32[15] = {0, 1, 3, 0…}` (`gun.c`): the N64 template basemtx NULL, zbufferenabled TRUE, flags 3. On PC `basemtx` is 8 bytes, so the words shift: zbufferenabled = 3, **flags = 0**, and `subdraw()` gates off every node. The copy also reads past the 60-byte array into whatever the linker put next. **Fix (`#ifdef PORT`, ABI/layout class, no Rule-2):** the explicit template, as in D264/D290. **Verified (native Windows, headless):** `-level_09`, `GE_INPUTSCRIPT="1000:Z;1020:Z"`, `GE_PCDUMP=850-1000:3`, shots at frames ~970/991. Before: impact sparks only, no casing. After: a brass casing ejects right of the PP7 and tumbles across frames 973–979. Bunker 1 golden 3/3; Windows + Linux builds clean. Sweep status: every `*(ModelRenderData *)` read in `src/game` is now covered (D215 :1592, D264, D290, D331). Cross-ref: D264, D290, porting-notes §A1/§B.
## D332 — Reticule jitter in aim mode (RMB): the drawn crosshair steps because the game damps once per tick and we write once per poll
**Symptom.** Holding RMB (precision aim), the reticule jumps around. Reported in issue #89 as "mouse movement is not uniform", and independently as a jitter that is worst under uneven frame pacing.
**Root cause, measured from live captures.** `caclulate_gun_crosshair_position_rotation` (`src/game/gunfire.c`) runs once per **sim tick** and does `crosshair_x_pos = crosshair_x_pos * guncrossdamp + turn_x`. The GEPD aim path (`port/src/input.c`, `aimGepdCompute`) *overwrites* `crosshair_x_pos` once per **input poll**. So the value the game displays is `write * damp^k`, where `k` is the number of ticks between two of our writes — and `k` varies, because the poll cadence (per VI retrace) and the tick cadence drift in phase.
Measured over 1266 aim frames from two captures, with the mouse still and the accumulator constant, `game/cross` took exactly three values (`damp^0`, `damp^1`, `damp^2`); `game = cross * damp^k` fits within 0.02 on 814/834 frames:
```
k=0: 72 k=1: 664 k=2: 89 k=3: 5 k=5: 3 k=6: 1
ct=1 -> k=1 (226), k=0 (7) ct=2 -> k=2 (77), k=0 (68)
```
The drawn offset is that times `(1 - damp)`, so at `damp = 0.8` the reticule steps **20%** whenever `k` changes — on 16.5% of frames. (Diagnosed with `GE_INPUTLOG=1`, which logs the port's accumulator, the game's residue, `ct`, and the damp.)
**Ruled out by the same capture:** auto-aim (`aa=0` throughout), stick turn (`st/sv=0`), camera edge-scroll (`cam` constant), and damp changes (only 5 KISSY/HONEY flips in 839 frames — real, each a ~43% rescale jump, but far too rare to be the main effect).
**Two fixes tried and reverted.**
- *Removing the D194(b) dt-normalisation* from the GEPD accumulator. Theoretically right — a position integral wants raw displacement, not a rate — but it did not stop the jitter.
- *Pre-compensating `damp^k` with a predicted `k`.* Made it **worse**. `k` is not predictable at write time, and using the observed previous `k` is worse than doing nothing: simulating properly against the captures gives no compensation `mean|step| 0.0066`, lagged compensation `0.0170`, ideal `0.0005`. The first validation of this attempt was **circular** — it used `ct` as the number of damps, the very assumption the data contradicts.
**Fix — adopt the Perfect Dark port's model.** `fgsfdsfgs/perfect_dark`'s aim code is structurally identical to GE's (`crosspossum` == `crosshair_x_pos`, `screenx` == `turn_x`, `crossdamp` == `guncrossdamp`, the same rescale block and display formula), but PD chooses the damp **per input device**: its mouse path calls `bgunSwivelWithDamp(x, y, 0.01f)` while its stick path uses `~0.945`. GE has one damp for both, and the weapon's `CrosshairSpeed` (~0.8) is a stick value — the integrator is what smooths a stick. At 0.01 the input dominates and `k` stops mattering.
Implemented as `Input.PdMouseAim` (**default off**, because it changes aim feel): the port only accumulates (`aimGepdAccumulate`, shared with GEPD's model) and a `#ifdef PORT` block in `sub_GAME_7F067FBC` takes the port-supplied turn and sets `guncrossdamp = 0.01f`. It is an F10 overlay row ("PD mouse aim"), which is live — toggling it in game takes effect on the next poll, no restart — plus a `GE_PDMOUSEAIM` launch-time override for A/B runs without editing the ini. PD does the same thing from its own game files (`bondmove.c`/`bondgun.c`/`player.c`, behind `#ifndef PLATFORM_N64`) calling port functions, so a platform-guarded game hook is the reference's own approach.
**Also fixed here:** the first cut of PD mode skipped `aimGepdCompute` entirely, which is where the camera **edge-scroll** lived, so dragging the crosshair to the screen edge stopped moving the camera. The edge-scroll belongs to the aim position, not to whichever model owns the crosshair — it is now `aimGepdEdgeScroll()`, called from both paths.
**Owed:** sensitivity tuning (PD's 0.01 against GEPD's accumulator scale may not match PD's own calibration; `Input.AimModeSens` is the knob).
**Integrated locally on `release/v0.4.0` (2026-09-23), from PR #96 (danturn; authorship kept).** Changes on integration: renumbered D337 → D332 (next free label); `GE_PDMOUSEAIM` lookup cached via `GE_ENVSTR` (it ran every input poll and every aim tick; see the getenv sweep `d9d7cd35`), and the "live env A/B without restart" comments corrected (a running process's env is fixed; the live toggle is the F10 row); the `gunfire.c` hook is a behavioural `#ifdef PORT` hook and got the maintainer's **Rule-2 sign-off 2026-09-23**. Scope checked: the hooked `sub_GAME_7F067FBC` runs only on the aim-mode (KISSY) branch of `bondview2.c`, so hipfire and auto-aim are unaffected. Windows build clean; golden 3/3 (default off). **Owed:** hands-on feel A/B (F10 → "PD mouse aim" on/off while holding RMB).