Commit Graph
13 Commits
Author SHA1 Message Date
James Dansereau 471eecd6d3 D401: Rumble Pak -> real gamepad haptics (re-promotes D224/M-87)
Port layer only (zero src/ edits), per docs/dev/RUMBLE-PLAN.md:
- port/src/input.c: inputRumbleSupported/inputRumble -> SDL_GameControllerRumble;
  detection via SDL_GameControllerHasRumble (SDL >= 2.0.18) else
  SDL_JoystickIsHaptic + known-type Windows fallback; zeroed on pad close.
  Single global gRumbleScale (dflt 0.5) registered as Input.RumbleScale.
- port/src/libultra.c: the two M-87 blockers resolved - g_contStatus[i].status
  now sets CONT_CARD_ON for connected rumble-capable pads (so
  joyRumblePakInit arms) and osPfsInit returns PFS_ERR_DEVICE for them (so
  osMotorInit is attempted); osMotorInit/Start/Stop drive the haptics API.
- port/src/optionsoverlay.c: one global 'Rumble strength' ROW_SLIDER
  (Input.RumbleScale, 0.05 detents, %.2f) in the INPUT section +
  kResetDefaults entry. The four per-player Input.PlayerN.RumbleScale keys
  were dropped (user decision 2026-09-28); stale keys in an existing ini
  log four 'unknown key' LOG_NOTE lines and self-clean on next configSave.
- Real-pad haptics confirmed working by the user (2026-09-28).

Build env: build-pc.sh self-heal guard revision - re-exec now writes a
self-contained .ps1 (msys->native argv conversion mangles $-bearing
-Command strings; file write is conversion-free), builds PATH explicitly,
picks TMP from writable candidates (GetTempPath honours the broken
inherited TMP), self-logs to build-pc/ge007-native-reexec-*.log, and
treats a null LASTEXITCODE as 'did not execute' (PS 5.1 piped native
commands error non-terminating). OPEN: the guard still fails inside
build-pc.sh while the identical standalone .ps1 invocation works - AGENTS.md
documents the workaround. docs: RUMBLE-PLAN.md (new, tracked), findings.md
D401 entry + index rows, QOL-INVENTORY D224 -> DONE (D401),
findings-index.csv regenerated.
2026-09-27 23:23:05 -04:00
James Dansereau 067c8384d0 docs(M-106): log user QA report as D233-D239 (#60)
* 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
2026-09-12 10:00:01 -04:00
James Dansereau 037f71dff8 docs: fold PD-legacy-survey findings into D224/QOL-INVENTORY/widescreen+FPS plans
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
2026-09-10 22:55:05 -04:00
James Dansereau 59cd0e5785 docs(D224): QOL-INVENTORY - deprioritized, gamepad-only
Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG
2026-09-10 22:43:05 -04:00
James Dansereau a3b9eeabe5 docs(D226): new — player HUD scale for ammo/pickup/dialogue text
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
2026-09-10 22:39:14 -04:00
James Dansereau ebbd743232 docs(D225): new — one-function debug unlock-all for testing (all missions + all cheats)
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
2026-09-10 22:36:01 -04:00
James Dansereau f5af9f3b05 docs(D224): new — rumble fully dropped, PD wires it to real gamepad rumble
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
2026-09-10 22:24:57 -04:00
James Dansereau 208fcfe27d Merge main into feat/d216-skip-intro (M-84 + D215 findings rows)
# Conflicts:
#	docs/dev/QOL-INVENTORY.md
#	docs/dev/findings-index.csv
#	docs/dev/findings.md
#	port/src/video.c
2026-09-09 20:54:05 -04:00
James Dansereau b3bf7786ff M-84: fold D211 FOV refix + user-test findings into the QoL wave
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.
2026-09-09 20:30:21 -04:00
James Dansereau da25c4c507 feat(D216): Game.SkipIntro — boot straight to the file-select menu
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.
2026-09-09 01:07:05 -04:00
James Dansereau de31f2874b feat(D214): keyboard rebinding via [Bind] config section
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.
2026-09-09 00:09:26 -04:00
James Dansereau 7bcfbf313c feat(D211-D213): PC QoL image options — FOV slider, anisotropic filtering, on-screen FPS
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.
2026-09-09 00:02:47 -04:00
James Dansereau 6736d6ee16 docs(dev): add live research/plan docs (D172, D176a, viewmodel, QOL, widescreen, unlocked-fps)
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
2026-09-05 19:07:43 -04:00