* docs(M-106): log user QA report as D233-D239 (delegate triage)
7 new open findings from local-delegate triage of a fresh user QA pass:
- D233: indoor geometry vanishes near doors while world objects still draw
(portal/frustum-cull suspect; screenshots committed, D106/D222 neighbours)
- D234: Dam guard towers invisible except the first (instance suspect)
- D235: Facility wrong textures, reported as a regression (bisect vs
pre-D217-fix build first)
- D236: Surface 1 billboard trees render as a texture-sheet wall, not
discrete camera-facing sprites
- D237: F10 overlay QoL — wheel scroll, more exposed settings incl.
Game.NoHitFlash, category grouping
- D238: mouse sensitivity rework — separate RMB aim sensitivity, single
linked master by default, fix up/down vs left/right asymmetry
- D239: click-to-lock is already default; drop always-grab entirely if
parity isn't cheap (re-confirms M-82, closes D192 by deletion)
findings-index.csv regenerated via tools_pc/gen_findings_index.py.
Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG
* docs(M-106): file D230 — in-level music quality (bass instrument corrupted)
Repro obtained from the user, deterministic (not intermittent): Control
(elevator + regular track, throughout), Cavern (elevator, throughout),
Runway (a bass instrument enters ~5-7s in and stays bad). User confirmed
against original N64 audio that a bass instrument belongs on Runway --
instrument selection is correct, only its sound is wrong on PC. Determinism
scopes this to a static decode/synthesis bug (csplayer.c/synthesizer.c or
bank loading), not the D147/D152/D202 live voice-stealing family.
Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG
* docs(M-106): dedupe D230 -- fold M-106 repro into the original M-105 filing
The M-105 session already filed D230; the M-106 delegate triage didn't
know that (branched off a stale local main before pulling) and filed a
second D230 row with the refined repro. Merged into one entry -- kept
the M-105 filing as the base, folded in the M-106 deterministic repro
(Control/Cavern/Runway) and the bass-instrument-is-correct clarification.
findings-index.csv regenerated via tools_pc/gen_findings_index.py.
Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG
* docs(D235): clarify trigger is death/restart, not a Facility-only regression
User clarification: the wrong-texture symptom isn't specific to Facility
and isn't a general build regression -- it's triggered by dying/
restarting a level (seen on other levels too). Reframes the leading
hypothesis from "bisect against #47/#49" to a texpool/CI-cache reset gap
on level re-entry (D217/D228 neighbourhood). Updated GRAPHICS-BACKLOG.md
+ findings.md; findings-index.csv regenerated.
Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG
Reviewed docs/dev/notes/PD-LEGACY-SURVEY.md (local Qwen agent's read-only
survey of the PD PC port for N64-legacy concepts worth modernizing/
noting) and folded its 8 candidates in:
- D224 (rumble): the stated fix is NOT sufficient alone - two upstream
blockers (CONT_CARD_ON never set; osPfsInit returns PFS_ERR_NOPACK
instead of PFS_ERR_DEVICE) sit before osMotorInit is ever reached.
Both must land alongside the SDL_GameControllerRumble wiring.
- WIDESCREEN-FOV-PLAN: recorded two deliberate GE-vs-PD divergences so
they're never mistaken for gaps - full-frame render (no PD-style
overscan crop) and keeping the SCREEN_RATIO_16_9 path GE already ships
(PD deleted its equivalent) as the widescreen mechanism.
- QOL-INVENTORY per-pad tuning row: PD's incremental hot-plug with
remembered seat is the natural implementation shape, sidesteps GE's
boot-latch controller-mask merge.
- D223 note: PD's per-player PC-controls-mode (save-compat shim) filed
for later, only relevant if P2+ keyboard input is ever a real ask.
- UNLOCKED-FPS-PLAN: PD's TickRateDivisor/ExtraSleep noted as a fallback
lever only if this plan stalls and GE stays fixed-tick.
Candidates 6/7 (MP-setup persistence, PD's PC-only gameplay options)
correctly judged net-new / rule-#2-cost-too-high by the survey - no
action, not re-noted elsewhere.
Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG
User ask: adjustable HUD scale for the ammo counter, bottom-left pickup/
item status text, and top-of-screen dialogue. All three route through
textRenderOutlined() (textrelated.c:688, doc comment confirms ammo
counter + bottom-left HUD messages), called from exactly two in-game
sites in bondview2.c (:10005 ammo/pickup, :10163/10165 dialogue) -
distinct from the many other callers in front.c/options.c/mpmenu.c
(menu/UI text), which must NOT be scaled by this feature (own layout,
D211 already established HUD anchors stay put). Underlying mechanism:
plain RDP gSPTextureRectangle at native glyph size, dsdx/dsdy=0x400
(1:1 texel:pixel), no scale factor in the chain. Two implementation
directions logged (surgical wrapper at the 2-3 call sites vs. a scale
param threaded through the shared function). No PD precedent. Not
designed or implemented - research only.
Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG
User ask: a debug toggle that always enables all level access and all
cheats, for easier testing. Root-caused a single choke point:
fileGetIsCheatUnlocked() (file2.c:391) is a pure, side-effect-free
save-bitfield read that BOTH the mission-select lock (file.c:59) and the
cheat-menu availability check (frontCheckIfCheatIsUnlocked, front.c:1009)
route through. Short-circuiting it to always-true under an env flag
unlocks both surfaces in one change. Bonus: exposes leftover Rare dev
cheats otherwise unreachable (CHEAT_LINEMODE wireframe - useful for
D222-style culling bugs, CHEAT_BONDPHASE, CHEAT_DEBUG_POS/UNK5). Plan:
#ifdef PORT env-gated (GE_DEBUG_UNLOCKALL), default off = zero behavior
change, no save-data mutation. Not implemented yet.
Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG
Survey of the PD PC port for other N64-legacy things worth dropping/
modernizing (per user ask, following D223's control-preset finding).
D224: GE genuinely has a rumble subsystem (motor.c, joy.c) but the
port's osMotorInit/Start/Stop are hard stubs returning -1 / NOPACK
unconditionally - every rumble event (recoil, explosions) computes and
calls into a dead path. PD's PC port wires the equivalent straight
through to SDL_GameControllerRumble via inputRumbleSupported/inputRumble
+ a per-pad RumbleScale config + options-menu slider - same 'pretend the
N64 accessory is there' pattern this port already uses for osMemSize/
Expansion Pak, just applied to a real capability instead of faked.
Straightforward port-only fix, no game-logic touch.
Also checked and found NOT applicable here: PD's Controller Pak
formatting UI has no GE equivalent (front.c has no PFS/pak menu code),
so there's no dead UI to hide there.
Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG
D211 didn't affect the in-level view (user test): the fast3d
gfx_apply_fov_scale hook only recognised *pure* perspective matrix loads,
but GE draws the world through g_CurrentPlayer->field_10E0 -- a
projection x view matrix pre-combined on the CPU (bondview2.c:8224) --
which failed the guard. Only the pause/watch model + sky FOV changed.
Fix: scale fovy game-side at the guPerspectiveF chokepoint
(src/fr.c, #ifdef PORT, frFovY *= portFovScale, clamp 160deg), which
lands before the CPU combine. Removed the fast3d hook + gfx_api.h decl;
port/src/video.c now sets f32 portFovScale. Gated to
lvlGetCurrentStageToLoad() != LEVELID_TITLE so the front end (which
renders through the same path) keeps its authored FOV.
User-verified: menu unaffected, in-level view widens (Dam tunnel).
Caveat: currentPlayerSetCameraScale keeps nominal FOV for frustum-cull
/ screen<->world math (edge geometry culls slightly early at high scale).
Adds src/fr.c as a #ifdef PORT game-code touch (D181 policy class).
Docs (this test pass):
- D214: config.c sections by the last dot, so binds live in [Input.Bind]
not [Bind]. Doc-only fix (finding, index, QOL-INVENTORY); code unchanged.
- D217 (OPEN): viewmodel + 3rd-person Bond model textures render garbled
-- stale-palette CI-texture bug (D161 family), surfaced by D215.
- D218 (LOGGED, parked): wide FOV exposes the per-level FarFog
draw-distance boundary; Video.DrawDistance fix proposed.
New s32 portSkipIntro (port/src/video.c, configRegisterInt Game.SkipIntro),
read once in src/game/lv.c at LEVELID_TITLE stage load, in the else branch of
the existing GE_STARTMENU #ifdef PORT block. When set it performs the same
state change every intro stage's own !is_first_time_on_main_menu branch does
(front.c): is_first_time_on_main_menu=FALSE; prev_keypresses=TRUE;
maybe_is_in_menu=TRUE; menu_update=MENU_FILE_SELECT. So it reuses the game's
own post-intro route rather than inventing one; the legal screen + logo
attract loop is skipped. GE_STARTMENU still wins when both are set.
Default 0 => unchanged. Verified headless: default boot frame 90 = the
TWYCROSS classification screen; SkipIntro=1 frame 90 = the SELECT FILE menu
(folders/photos/cursor render, no crash across repeated boots). Screenshots
eyeballed.
findings.md D216 + index; QOL-INVENTORY row marked done.
port/src/input.c: 12 gameplay actions (Forward/Back/StrafeLeft/StrafeRight/
TurnLeft/TurnRight/Fire/Aim/Action/Cancel/LeanLeft/Start), each bound to a
comma-separated list of up to 4 SDL scancode names via Input.Bind.* keys.
inputRebuildBinds() parses them in inputInit() after configLoad();
SDL_GetScancodeFromName resolves names, unknowns warn + skip. The hardcoded
keyDown(ks, SDL_SCANCODE_*) block in inputComputePad is replaced with
actHeld(ks, IA_*). Mouse buttons (fire/aim) stay hardwired.
File-driven, no UI -- matches the PD port. A fresh ge007.ini gets the full
[Bind] block with defaults visible.
Defaults reproduce the previous FPS layout exactly: all 20 default key names
round-trip to the identical SDL_Scancode the old code used (0 mismatches in a
SDL_GetScancodeFromName test). Build clean; verify.sh dam/facility/bunker1
PASS crash-free.
findings.md D214 + index; QOL-INVENTORY row marked done.
Three port-only knobs, each defaulting to byte-identical behaviour:
- Video.FovScale (D211): multiplicative half-angle scale on every perspective
projection matrix as it is loaded (gfx_apply_fov_scale, gfx_pc.cpp). Columns
0+1 scaled together so H/V FOV widen uniformly and aspect is preserved; ortho
(HUD/menu) matrices untouched. Composes with the game's per-frame aim-zoom
rewrites for free. Percent of vertical FOV, 50-150, default 100 => guarded
early-return (zero FP ops, golden dumps byte-identical). WIDESCREEN-FOV-PLAN
Phase 4, landed standalone.
- Video.Anisotropy (D212): expose fast3d's already-implemented GL anisotropy
hook as config (1-16). New gfx_set_anisotropy_level() extern-C wrapper clamps
to the GL max. Default 4 = the value fast3d already hardcoded => no delta.
- Video.DisplayFPS (D213): PD-parity top-right FPS readout, sampled once per
emitted frame (optionsoverlay.c fpsTick). Config-only; default 0 => emit
still returns NULL with the panel closed.
F10 overlay gains 'Anisotropic' and 'FOV scale %' rows (DisplayFPS is file-only
- the panel is at its 13-row layout limit, matching PD).
Build clean (ntsc-final); bunker1 framediff worst_cell 12.219 vs 12.221
baseline = render noise, no-op confirmed. Interactive eyeballs owed (see
docs/HANDOFF.md M-83).
findings.md: D211-D213 rows + index regen. WIDESCREEN-FOV-PLAN Phase 4 and
QOL-INVENTORY rows marked done.
All referenced from HANDOFF.md / GRAPHICS-BACKLOG.md but untracked:
- D172-PARTICLE-COLOR-RESEARCH.md: magenta/cyan blood root-cause candidate
(unhandled GBI opcode 0xB9 in ROM particle records + D146 guard)
- D176a-SKY-NOTES.md: RDPHALF sky-tri wire format decoded; Path B draft state
- VIEWMODEL-RESEARCH.md: 1P weapon model static audit, three ranked suspects
- QOL-INVENTORY.md / WIDESCREEN-FOV-PLAN.md / UNLOCKED-FPS-PLAN.md: parked
Phase-4 proposals