forked from Ports/goldeneye-pc-port
3dcceaafd954db0e47c6f5fbf282c82f76e8ea1b
45
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3dcceaafd9 |
ci: route windows-build to self-hosted runner (G2); land ROM-gated sweep workflow (G1) (#65)
* ci: route windows-build to self-hosted runner (G2); land ROM-gated sweep workflow (G1)
G2: windows-build now runs on the self-hosted box (corpyvt6agent01) for
push/workflow_dispatch, using local ccache instead of the actions/cache
round-trip and skipping msys2/setup-msys2's install once packages are
already present. pull_request always stays on windows-latest — fork PRs
must never reach a box that will hold a ROM.
G1: adds .github/workflows/selfhosted.yml, a dispatch-only workflow that
builds + runs the 21-level headless sweep (tools_pc/verify.sh sweep --json)
against a runner-cached ROM. Inert until manually dispatched.
Still pending (manual, physical steps on the box — not done by this
commit, see docs/dev/notes/SELFHOSTED-RUNNER-PLAN.md rollout table):
- register corpyvt6agent01 as a runner against this repo
- confirm MSYS2 at C:\msys64 with the required mingw64 package set
- one-time ROM seed into {runner_home}/_rom-seed/
- first workflow_dispatch sweep + parity check against a local run
- flip G1 from workflow_dispatch to push:[main] once green
Claude-Session: https://claude.ai/code/session_01J5PZ3sTHiuW6MdQnepasSU
* fix(selfhosted.yml): remove invalid concurrency.max-parallel key
Found by testing: dispatching the workflow failed with a hard parse
error (HTTP 422, "Unexpected value 'max-parallel'"). GitHub Actions'
workflow-level `concurrency` block only supports `group` and
`cancel-in-progress` — `max-parallel` is a job `strategy:` key for
matrix jobs, not a concurrency-block key. Serialization was already
achieved via group + cancel-in-progress:false; the stray key just
blocked every dispatch.
Claude-Session: https://claude.ai/code/session_01J5PZ3sTHiuW6MdQnepasSU
* fix(selfhosted.yml): register the msys2 shell wrapper (setup-msys2 step)
Found by testing: the first fixed dispatch failed every msys2-shell step
with "msys2: command not found". Unlike ci.yml's windows-build, this
workflow used `shell: msys2 {0}` without ever running msys2/setup-msys2,
which is what registers that shell alias for the runner — MSYS2 being
pre-installed on the box doesn't help if Actions doesn't know the shell
exists. Adds the same setup-msys2 step ci.yml uses, with update:false
since the packages are expected already present (fast no-op per plan).
Claude-Session: https://claude.ai/code/session_01J5PZ3sTHiuW6MdQnepasSU
* ci: stop reinstalling MSYS2 per run on the self-hosted box
The self-hosted paths (selfhosted.yml; ci.yml windows-build's push/
dispatch route) no longer call msys2/setup-msys2 or pacman -S at all.
MSYS2 + the mingw64 package set are now assumed hand-maintained on
corpyvt6agent01 (docs/dev/notes/SELFHOSTED-RUNNER-PLAN.md), invoked by
calling its bash directly (`C:\msys64\usr\bin\bash.exe -leo pipefail
"{0}"`, the same invocation setup-msys2's generated wrapper uses) with
MSYSTEM=MINGW64 so /etc/profile still puts mingw64/bin on PATH.
Adds tools_pc/check-selfhosted-deps.sh: a fast, install-free check that
exits 0 immediately when the required packages (toolchain, cmake,
SDL2, zlib, python, ccache, zip, unzip) are present, or fails with the
exact `pacman -S --needed ...` line to run otherwise — so a missing
package on the box surfaces immediately with a clear fix, instead of
as a confusing failure deeper in the build.
ci.yml's windows-latest (PR) path is unchanged: ephemeral runners still
get a full msys2/setup-msys2 install every time, since there's nothing
to persist there.
Claude-Session: https://claude.ai/code/session_01J5PZ3sTHiuW6MdQnepasSU
* fix: use msys2/setup-msys2 release:false instead of a raw bash-path shell
The previous attempt (invoking C:\msys64\usr\bin\bash.exe directly as a
custom `shell:` command) failed on the actual runner with "Second path
fragment must not be a drive or UNC name" — a real limitation, not a
typo; it failed identically in both files, including the one with a
static (non-templated) shell string, so it wasn't the ${{ }} expression
either.
Root-caused by reading msys2/setup-msys2's actual source: `release:
false` makes it reuse the existing C:\msys64 as-is (hardcoded path in
that branch, confirmed in main.js) instead of downloading+installing a
fresh copy, and — verified in the same source — omitting `update` and
`install` skips every pacman call entirely (just one `uname -a` + a
pacman.conf sed tweak, no network). This is the officially documented
mechanism for self-hosted runners with a pre-existing MSYS2 install
(see the action's README, "release" section).
ci.yml's windows-build now runs setup-msys2 unconditionally but with
release/update/install all keyed off github.event_name: full install on
windows-latest (PR, ephemeral), reuse-only on the self-hosted box
(push/dispatch). selfhosted.yml always uses release:false. Both then
run tools_pc/check-selfhosted-deps.sh to verify the box's hand-
maintained packages are actually present.
Claude-Session: https://claude.ai/code/session_01J5PZ3sTHiuW6MdQnepasSU
* fix(selfhosted.yml): cp -r for the ROM sidecar directories
Found by testing with the ROM seed actually in place: pcmodels-ntsc-final
and pccg-ntsc-final are directories (not single files, unlike
ge007.ntsc-final.z64), so a plain `cp` failed with "omitting directory"
and left the seed step failing on every run even with the seed populated.
Claude-Session: https://claude.ai/code/session_01J5PZ3sTHiuW6MdQnepasSU
* ci: drive MSYS2 MINGW64 directly via ci-shell.cmd, drop msys2 shell shim
The self-hosted runs were dying with 'msys2: command not found' because the
msys2/setup-msys2 action's shell shim was never registered on the box. Replace
the dependency on that shim with a small committed launcher (tools_pc/ci-shell.cmd)
that headlessly reproduces the local 'MSYS2 MINGW64' terminal:
- MSYSTEM=MINGW64 + /mingw64/bin on PATH
- NON-login bash, so it keeps the workspace cwd (a login shell cd's to $HOME
and would break every relative path in the workflow)
- -e -o pipefail for GitHub-style error propagation
Both workflows now set defaults.run.shell to C:\msys64\ci-shell.cmd {0}; a
bootstrap step (shell: cmd) refreshes the launcher from the repo into C:\msys64
each run. selfhosted.yml drops setup-msys2 entirely; ci.yml keeps it only for
the ephemeral windows-latest PR path (which installs MSYS2 at C:\msys64).
.gitattributes: force CRLF for *.cmd so cmd.exe parses the launcher correctly.
* Revert "ci: drive MSYS2 MINGW64 directly via ci-shell.cmd, drop msys2 shell shim"
This reverts commit
|
||
|
|
650ac57fc6 |
fix(D233): fast3d trivial-reject drops visible triangles with a behind-camera vertex (#63)
gfx_sp_tri1's CPU-side trivial-reject AND-test trusted per-vertex outcodes (x<-w / x>w / y<-w / y>w / z>w) that are only valid half-space tests when w>0. A vertex that has crossed behind the camera plane (w<0 -- common when the camera is close to a large room-structure polygon, e.g. walls/ceiling trusses near a doorway) flips the comparison sense, so its outcode can come out wrong. When that spurious bit happens to match the other two genuinely correct vertices' bits, the AND-reduction fired and dropped the whole triangle before it ever reached OpenGL -- even though GL's own homogeneous clipper (which handles w<0 correctly) would have rendered the visible part of it. This reads exactly like D233 (indoor geometry intermittently vanishing near doors while props/furniture keep drawing): large near-camera polygons are far more likely to have a vertex cross behind the eye than small self-contained prop meshes, and the backface-cull code six lines below already special-cases this same w-sign hazard for its own cross-product sign -- the trivial-reject block above it never got the same treatment. Fix: skip the trivial-reject AND-test whenever any of the triangle's three vertices has w<0, deferring to GL's clipper instead. This can only ever send MORE triangles to the GPU than before, never fewer, so it cannot make anything newly invisible. Also investigated D234 (Dam guard towers, only the first renders) under the same M-106 hypothesis that it shares a root cause with D233/D222 -- did not find fast3d-side evidence for it; left open in findings.md with the ruled-out mechanisms and a next-step recommendation. D222 (FovScale breaks culling) is a separate, already-characterised game-logic bug in src/game/fr.c/bondview2.c, out of this fast3d-only fix's scope. Build verified green (MSYS2 mingw64 cmake+ninja, full link succeeds). Could not visually verify -- this worktree has no ROM/data and no interactive display; see docs/dev/findings.md D233 entry for the exact human verification steps (GE_PCDUMP on a doorway-heavy interior). docs/dev/findings.md: D233 (FIXED, high confidence on mechanism/unverified visually) + D234 (OPEN, not root-caused) entries added. docs/porting-notes.md: generalised the w<0 trivial-reject hazard as a recurring class for any future CPU-side "skip this triangle" heuristic. docs/dev/findings-index.csv regenerated. Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG |
||
|
|
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 |
||
|
|
646798be56 |
docs(M-102): fix findings-index false-positive, release <version> bug, land parked site cleanup (#53)
* docs(M-102): fix findings-index false-positive, release <version> bug, land parked site cleanup
Docs triage pass (docs/dev/notes/PROMPT-docs-triage-session.md).
- gen_findings_index.py: D194's row was silently bucketed FIXED (an open
design item) because it had no distinct status cell, so the generator's
fallback keyword search matched "fixed" somewhere inside 6,500 chars of
narrative -- the same misleading-index failure mode that cost a session
on D227 (see docs/dev/findings.md SS H "D227 -- M-98"/"M-99"). Gave D194
a real status cell. Also extended LEAD_RE/bucket() with 5 verdict tokens
already in use in the doc (ANALYZED, GUARDED, DIAGNOSTIC SHIPPED,
ROOT-CAUSED, NOT A BUG) and added a leading verdict word to 4 more rows
that had a status cell but no recognized token -- fixes all 10 rows the
generator previously bucketed "?" (D175 D174 D173 D220 D160 D133 D136
D156 D153, plus D194). findings-index.csv regenerated; --check passes;
diff reviewed, only these 10 rows moved.
- ci.yml: release-notes.md's <version> placeholders were never substituted
before `gh release create --notes-file`, so every published release body
(including the live v0.1.0 release) ships the literal text. Now sed'd to
the tag first.
- Landed docs/dev/notes/UNLANDED-docs-site-cleanup-steps3-5.{md,patch}
(parked M-101, never pushed, branch deleted): deletes the orphaned
docs/StructureGuide.md (0 tracked references), adds title/description
frontmatter to 5 published Pages docs, rewrites docs/index.md's stale
"Phase 2 of 4 / no audio yet / 19-of-21" status text to match the
current README. The 2 files whose hunks no longer applied via `git am`
(docs/index.md, docs/dev/agentic-development.md) were redone by hand
against current main.
- docs/BACKLOG.md (local, gitignored): retargeted a dead
docs/PORT-LEARNINGS.md reference to docs/porting-notes.md.
Larger triage items (findings.md monster-row convention, full
status-accuracy sweep, duplicate-defect clustering, dev-notes plan
triage, HANDOFF rotation policy) scoped but not started -- see
docs/dev/notes/FOLLOWUP-docs-triage-session.md (gitignored).
Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG
* docs/index.md: fix stale in-level-music claim before merge
This PR predates the M-102c/M-105 correction (already landed in the
README via PR #55): in-level music is NOT absent, it plays with some
tracks having wrong-sounding instruments / occasional garbling (D77
fixed, D230 filed for the residual). This branch's own rewritten Status
paragraph still said music 'doesn't yet' play -- fixing it to match
before merge so main doesn't briefly regress to a stale claim this
session already corrected elsewhere.
|
||
|
|
a24495672e |
docs: scrub stale status claims (D77 music, D227 sky, D217 green textures) (#55)
* docs: scrub stale status claims (D77 music, D227 sky, D217 green textures) The M-104 roadmap refresh flagged four live inaccuracies; this fixes all four plus files the missing music-quality finding: - README: in-level music no longer described as silent/absent (D77 fixed, PR #42); sky line no longer 'still being fixed' (D227 merged, PR #43) -- D229 green water is now the named sky-path residual. - GRAPHICS-BACKLOG.md: D77 row updated PARTIAL -> RESOLVED with root cause. - findings.md: D217 status cell led 'OPEN -- root cause characterised (M-86)' although the green defect was fixed and merged (PR #47/#49, two human playtests); now leads FIXED with the D228 split-out noted. - New D230: in-level music quality residual (wrong instruments on some tracks -- elevator named specifically -- plus occasional garbling). Scope pointed at the seq/instrument-bank decode path, not the proven mixer/trigger layers. findings-index.csv regenerated; gen_findings_index.py --check passes. * docs(D190): root-caused and closed — level 45 is MP-only, no solo setup in ROM M-48's 'Linux-only crash' was a misframing. Full chain (reproduced on Windows this session, 0xc0000005 in modelLoad via domakedefaultobj with obj=0x784f): - setup_text_pointers[45] = "UsetupimpZ" but that name is absent from the ROM file table; only the MP variant "Ump_setupimpZ" exists. BASEMENT is a 4P MP level (front.c multi_stage_setups[]) — its solo name was never shipped, like the other MP-only levels (STACK/TEMPLE/COMPLEX/CAVES/ LIBRARY). - A solo -level_45 boot requests the nonexistent file. On PC the inert rmon stubs leave fileGetIndex's dynamic-path response uninitialized -> size-0 table entry -> 0-byte load into a stale pool buffer -> the propDef walk runs over stale memory (M-48 hit add_ptr_to_objective; same garbage walk, different contents). - N64 parity: file genuinely absent there too; the boot is broken in the original as well. No fix taken (a port guard cannot synthesize a valid setup; fabricating ROM content has no ground truth). |
||
|
|
3c11e40c13 |
fix(D223): directional mouse-wheel weapon cycling (#54)
* fix(D223): directional mouse-wheel weapon cycling
Wheel-up keeps the existing 2-poll A pulse (weaponForwardOffset: fresh
A edge, Z released). Wheel-down now runs a 2-poll backward sequence:
A-only poll, then A+Z_TRIG -- the fresh-Z-edge-while-A-held combo that
weaponBackOffset requires (bondview2.c:5337-5340); ordering matters
because the edge is computed against the game's own previous poll.
moveData.triggerOn (bondview2.c:5419) requires A to be released, so
the synthesized Z edge can never fire a shot.
Roles are stable: the port forces CONTROLLER_CONFIG_HONEY
(bondview.c:1484), whose scheme gives invButtons=A_BUTTON /
shootButtons=Z_TRIG -- the same roles the pre-fix forward pulse
already relied on. While RMB aim is held the game flips controldef to
KISSY, under which neither direction cycles -- identical to the old
forward-only behavior in that state (pre-existing, not a regression).
Port-only; no src/ changes.
* docs(D223/D196): mark D223 FIXED (M-105); correct stale D196 status
D223 row now records the implemented fix and its known aim-mode
limitation. D196 was already fixed and merged (
|
||
|
|
741dfe78b9 |
fix(D227/D176a): sky texcoords are S10.5, not texels — fixes seam, over-tiling and scroll speed (#43)
* fix(D176a): sky scroll/tiling too fast -- perspective-correct tc interpolation skyPortRenderPoly (Path B, D176(a) black-sky fix) placed sky quad verts under a plain w=1 ortho projection, so fast3d's GPU pipeline interpolated texture coords linearly in screen space across the primitive. The source S/T data (unk20/unk24) is computed upstream from a world-space projection without a perspective divide -- exactly like the real N64 RDP's G_TRI_SHADE_TXTR coefficient block, which relies on real perspective- correct interpolation to divide it back out per pixel. A single sky quad's near (top) and horizon-grazing (bottom) vertices can differ up to ~38x in camera-space w. Under linear interpolation that smears the entire S/T delta evenly across every pixel instead of concentrating it near the horizon, producing a uniformly dense aliased 'hatching' over the whole sky -- reported as 'scrolls too fast / tiles too visibly'. Fix: build a custom projection matrix that gives each vertex its real camera-space w (already computed upstream, SkyRelated38.unk0c) while keeping the same screen-space x,y, so the GPU's normal perspective divide recovers the intended position but interpolates tc (and colour) with real 1/w weighting -- the same pipeline every other textured draw in the game already depends on. No port/fast3d changes; #ifdef PORT only, N64 path untouched. Verified: tools_pc/verify.sh bunker1 PASS (no regression on a non-sky level); -level_22 (Statue) and -level_41 (Cradle) GE_PCDUMP captures show the expected before/after change (uniform parallel aliasing -> a proper perspective-converging fan), and Cradle's sky is no longer black at default FOV. Defect 2 (horizon-band tessellation) and the FOV-coupled black-sky-past-frustum symptom are unchanged / still open. docs/dev/findings.md: new D176(a) subsection + summary-row update. docs/porting-notes.md: generalized the screen-space-overlay quirk to call out the w=1-vs-real-w interpolation pitfall. * chore(D176a): regen findings index * docs(D176a/D227): M-93 - human playtest of PR #43 confirms Cradle black-sky fix + partial Statue scroll improvement; new split-sky residual filed as D227 * docs(D227): M-94 - visual evidence + a concrete mechanistic hypothesis for the sky seam (per-primitive wScale quantization), not yet fixed Headless -level_22 capture (this shell can do offscreen GE_PCDUMP capture via the game binary directly, run from the repo root with the MSYS2 mingw64 PATH set -- the earlier GUI-launch flakiness in this session was specific to a live window, not this path) shows a real, static-frame crease in the sky's cloud texture. Screenshot committed: docs/img/bugs/d227-sky-seam-statue.png. Hypothesis: skyPortRenderPoly (D176(a)'s fix) computes wScale per call, and skyRenderTri/skyRenderFull issue up to 5 separate calls per frame (one per triangle in the sky fan). The position math is exact for any wScale (re-verified algebraically), but Vtx.ob[] is s16, so two triangles sharing an edge can quantize a logically-shared vertex to two slightly different values depending on which triangle's independently- chosen wScale computed it -- crack at the shared edge, reads as "two regions with a different pattern." Not yet confirmed (didn't isolate camera motion from sky-scroll motion to test the "moves at a different rate" half of the report) and not fixed -- documented the fix direction (one wScale shared across the whole fan, not recomputed per triangle) for whoever picks this up next. Cleaned up capture scratch files after pulling the one evidence shot. * docs(D227): M-94 addendum - static-camera test confirms the sky genuinely animates over time, independent of camera motion * fix(D227): M-95 - one shared wScale across the whole sky fan instead of one per triangle, closes the visible seam skyRenderTri/skyRenderFull draw up to 5 triangles per frame from a shared pool of up to 5 vertices (built once, then fan-triangulated across 1-3 calls). D176(a)'s fix had each call compute its own wScale from only its own 2-4 verts; two triangles sharing a vertex could each pick a different wScale, and since Vtx.ob[] is s16, quantize that logically-shared vertex to two different values depending on which triangle's call computed it -- a crack at the shared edge (D227, the "two sky halves" report). Fix: skyPortBeginFan()/skyPortEndFan() (new, #ifdef PORT) compute one wScale across the whole fan and skyPortRenderPoly uses it when set, falling back to the old per-call computation otherwise. N64 path unchanged (these are PORT-only additions around existing #ifdef PORT code, no #else branch touched). Verified: - Clean build, no new/duplicate symbols. - Full verify.sh sweep: 21/21 PASS, both with and without this change. - Same-frame before/after screenshots at -level_22 frame 345 (docs/img/bugs/d227-sky-seam-statue{,-fixed}.png): visible kink in the cloud streaks in the before shot, gone in the after shot, same exact frame. - bunker1's nonzero framediff worst_cell is unaffected by this change (reproduced identically pre-fix) -- confirmed pre-existing stale golden-baseline issue, unrelated to sky.c. - Cradle's black-rectangle artifact is also confirmed pre-existing (reproduced identically pre-fix) -- the separate, already-documented FOV-coupled black-sky-past-frustum-edge symptom (D218/D222 family), not touched by this fix. Not re-verified: whether the "moves at a different rate" half of the original report is also resolved, and the aspect-ratio-artifact alternate theory was never independently ruled out. Human eyeball still owed before merge, same as every other rendering fix landed this session without interactive display access. Also generalized the quirk (per-call vs per-shared-vertex-group scale quantization) to docs/porting-notes.md alongside the existing D176(a) entry. * docs(D227): M-96 - human playtest on Statue: seam/crack STILL PRESENT despite M-95 fix The M-95 wScale-sharing fix only verified the static per-primitive quantization theory (single-frame screenshot diff); it explicitly left the 'two halves move at different rates' temporal theory from the original report unverified. This playtest is that missing verification, and it says the seam is still visible in live play. D227 NOT resolved. PR #43 stays in draft pending investigation of the temporal/rate theory -- do not merge on the M-95 fix alone. * docs(D227): M-96 addendum - N64 sky should be near-static, this build scrolls extremely fast New data point from the same Statue playtest session, separate from the seam/crack: the user reports the intended N64 scroll rate is near-static, stronger than M-93's 'still reads too fast'. Bears on D176(a)'s fix magnitude itself, not just the D227 seam -- flagged as a possible shared root cause (wrong per-frame tc delta could drive both symptoms), not yet investigated. * fix(D227): M-98 - share sky S/T fold across whole fan, not per-triangle skyPortBeginFan() (M-95) shared wScale across a whole sky fan so shared vertices quantize position consistently, but skyPortRenderPoly's S/T fold (foldS/foldT -- floorf(minS/64)*64, keeps texel coords in s16 range) was still computed per-call, from only that triangle's own 2-4 verts. Same bug class as the wScale fix, one field over, never fixed -- which is why M-96's human playtest still saw the seam after M-95. Two triangles in one fan can floor a shared vertex's S/T to different multiples of 64 texels; nominally invisible (GL_REPEAT period 64), but each triangle bakes (S-fold)*32 into its own s16 Vtx buffer, so two folds 64 texels apart produce tc values 2048 apart -- a real crack at the shared edge. Fix: extend skyPortBeginFan()/skyPortEndFan() to also compute one shared fold across the whole fan, threaded into skyPortRenderPoly the same way as wScale. #ifdef PORT only, no game-logic change. Also measured (per M-96/M-97's ask): cross-correlated 11 consecutive frames at a static camera -> ~7 px/tick horizontal scroll at 640px capture width, a full screen-width sweep every ~1.5s. Confirms "extremely fast" quantitatively; root cause of that magnitude is not yet found (accumulator + vertex geometry are unmodified N64 logic, so if it's genuinely wrong the bug is in how the Path B substitution maps texel-ticks to screen pixels, not the tick rate itself -- next step spec'd in findings.md). Verified: build clean; -level_22 frame 345 (same frame M-95's before/after used) shows no visible crease vs. the pre-M-95 reference screenshot; verify.sh bunker1 PASS, worst_cell unchanged (known stale golden baseline, not a regression). Human eyeball still owed on both the seam-close and the scroll-speed question before PR #43 leaves draft. * docs(D176a): M-98 - Cradle side of PR #43 checked; tower starburst is Defect 2 Checked the Cradle side of the D227 fold-sharing fix (M-97 left this outstanding). No regression -- verify.sh cradle PASS -- but found a much more visible pre-existing artifact while there: looking up the suspension tower near spawn, roughly half the sky renders as a sharp radiating starburst instead of clouds. Confirmed pre-existing (pixel-identical with sky.c reverted to pre-M-98) and confirmed static, not the D227 "moves at different rates" symptom (pixel-identical 30 ticks apart at a fixed camera). This is the already-known, already-open D176(a) Defect 2 (horizon-band tessellation) -- the code's own fold-logic comment already predicts exactly this failure shape for a triangle spanning >1000 texels near the horizon, which looking straight up the tower produces. Not fixed (tessellation is separate, bigger work); logged with a concrete repro + screenshot so it's no longer "inconclusive." * docs(D227): M-99 - human playtest negative (seam persists, view-angle-dependent); headless repro blocked Human playtest after M-98's fold-sharing fix: the seam is still present on Statue and now confirmed to move/change with mouse/view position -- also reported still bad on Cradle and present on Surface. M-98's fix was real but is not the dominant mechanism live play is hitting, same pattern as M-95 before it. Ruled out this pass: the cross-quad water/cloud texture-scale mismatch theory, on the two IsWater==0 levels actually tested (Statue, Cradle) -- untested on any IsWater==1 level, which is now the top ranked candidate for next session. Blocked on: headless camera-pan repro via GE_INPUTSCRIPT does not work -- confirmed neither analog-stick nor C-button tokens rotate the camera, so this needs either a human playtest or a debug hook into the real mouselook path before it can be chased further headlessly. Full ranked candidate list + environment notes for next session in findings.md and (session-local) docs/HANDOFF.md. * docs(D227): M-100 - root cause PROVEN: s16 texture-coordinate overflow The sky seam is not a crack, not a quantization mismatch, and not view-dependence. One sky quad spans ~30,000 texels in S and T, but Vtx.tc is S10.5 in an s16 = +/-1024 texels, so 7 of its 8 texture coordinates overflow and wrap to arbitrary values. The fold cannot help against a span 29x the representable range. Unblocked by a user-supplied emulator-vs-ours screenshot pair on Surface: the real symptom is a radial starburst, not a crack, which is a different mechanism from what M-94..M-98 were chasing. M-95 (shared wScale) and M-98 (shared S/T fold) each fixed a real second-order bug; neither could touch this one. Both stay - per-fan consistency is exactly what will make subdivision seamless. Measured with a new env-gated GE_D227V probe (GE_D176 style) on the Cradle frame-350 camera. All four w are positive, so no projective pole. The quad is split gSP2Triangles(0,1,3, 0,3,2) on the 0-3 diagonal, and vertices 0 and 3 are exactly the two that keep one coordinate in range - hence "two halves with different patterns, split by a line", the original D227 report verbatim. Apparent view-dependence needs no view-dependent mechanism: S/T depend on view, so the wrap point moves. Confirmed by a diagnostic build: baking tc at 1-texel instead of 1/32-texel granularity (32x range, deliberately wrong scale) makes the starburst and the split completely disappear. No second defect behind it. D227 == D176(a) "Defect 2" == M-98's "Cradle tower starburst". M-98 misfiled that capture as unrelated, which is what made D227 look headlessly-unreproducible in M-99 - it reproduces statically. Fix not implemented: subdivide until span < ~960 texels/primitive. The subdivision math and cut placement are worked out in the finding (S is linear in w, so cut uniformly in w), but the vertex budget is an open decision - dynAllocateVertices is an unchecked 50 KB/frame pool and pure tessellation wants ~40% of it. * docs(D227): M-100 addendum - RDP tile shift (option D) dominates; fast3d already implements it shifts/shiftt is the RDP's native precision-vs-range knob and gfx_pc.cpp implements it fully including the left-shift case. Emitting tc at 32/2^k units with shifts = 16-k buys 2^k range with no tessellation, no fast3d change and no vertex-budget cost. k=5 covers the measured 29,830-texel span outright. * fix(D227): M-100b - sky texcoords are S10.5, not texels; drop M-82's stray *32 Fixes the sky seam/starburst, the over-tiling, and the "scrolls too fast" complaint - all three were the same bug. M-82 ("D176(a) Defect 1") read unk20/unk24 as texel counts and baked tc as (S - fold) * 32 to convert texels into tc's S10.5 units. They are not texel counts. skyRender sets them as worldpos * 0.1f and the N64 path hands them to the RDP coefficient block unscaled (skyRenderTri: S * unk34 * sp368), so they are already in the RDP's native S10.5 1/32-texel units. That stray *32 made every sky texture coordinate 32x too large, producing three symptoms previously tracked as separate bugs: 1. D227 - spans of ~30,000 texels against an s16 tc holding +/-1024, so 7 of a quad's 8 coordinates overflowed and wrapped to arbitrary values. The quad splits on the 0-3 diagonal and verts 0 and 3 are exactly the two that keep one coordinate in range, hence "two halves with different patterns" - the original D227 report verbatim. 2. Over-tiling - ~466 repeats of the 64-texel cloud per quad, not ~15. 3. M-96's "the N64 sky is nearly static, ours races", measured at ~7 px/tick in M-98. g_SkyCloudOffset advances 1 unit/tick = 1/32 texel, not 1 texel. This is why M-98's re-derivation of the tick path kept coming back clean: the accumulator was never wrong, its unit was. Fix: tc = (S - fold) 1:1, with the fold rebased to a whole tile period (SKY_TC_WRAP = 64 texels * 32 = 2048 units). The ~30,000-unit span then fits an s16 unaided - which is also why the original N64 code needs no tessellation here: it never had a range problem to solve. Kept: M-95's shared wScale and M-98's shared fold (both real bugs; per-fan consistency keeps a fan's shared vertices bit-identical). Also kept as a safety valve, since only ~4% of s16 headroom remains on the observed span: skyPortPickShift/skyPortCaptureTile/skyPortEmitTileShift, which trade tc precision for range via the RDP's own tile shifts/shiftt (port/fast3d already implements it, so no fast3d change). skyPortCaptureTile replays the exact G_SETTILE texSelect emitted with only the shift fields patched, so it cannot drift from texSelect's format/line/mask derivation. Also adds the env-gated GE_D227V per-vertex probe (GE_D176 style) that measured all of this. Verified: -level_41 Cradle frame 350 (the M-98 starburst repro) now shows soft cloud bands at the correct scale with no starburst, diagonal split or moire; GE_D227V confirms kS=kT=0 and max tc 31,513 of 32,767. -level_22 Statue frame 345 clean. verify.sh bunker1 PASS (worst_cell 10.32, the known stale-golden-baseline number). Human play-test still owed. * D229: file green/pulsating water; decouple D227's tc safety valve from it New finding D229: on IsWater levels (Frigate, Surface 2) the water renders green and pulses blue<->green on an interval. Pre-existing, proven by A/B: built plain main with none of the D227 work and captured -level_26 frame 340 - same green, only the tiling density differs. D229 is independent of D227. The gun half of the same report was NOT a bug: the branch was 2 commits behind main and those 2 commits were exactly the D217 fixes (#47 texpool alignment, #49 CI cache palette-content key). After merging main the user confirms the gun is good on Frigate. Mechanism so far: the water quad is sky.c's IsWater block1 (Frigate confirmed IsWater=1 via GE_D176, which also settles M-99's ranked item 1). sub_GAME_7F09343C binds tile 0 and tile 1 to the SAME tmem, offsets tile 1 via gDPSetTileSize(1, 90, 150, 0, 0), and cross-fades TEXEL0/TEXEL1 with a PRIM_LOD_FRAC driven by sinf(). That sine is the reported pulsing, so one texunit resolves blue and the other green. Ruled out the shade path by measurement (it spans dark blue to pale yellow, never green; sampled pixels have R and B crushed). Not yet root-caused. Code change here is only the decoupling: skyPortBeginFan takes allowShift and the water quad passes FALSE, leaving that path byte-identical to its pre-D227 behaviour. The water quad is a two-texunit draw sharing one set of vertex tc, so a tc rescale needs a matching shift on both tiles - and shifting both makes them sample identically, which flattens the cross-fade. An intermediate build that did so turned the water from pulsating into constantly green, observed live. Also fixes skyPortCaptureTile to capture after all tile setup (not just after texSelect) and to handle both tiles, so a future shift cannot clobber a later re-declaration. Cradle sky re-verified unaffected (frame 350 still correct). bunker1 golden framediff reports REGRESSION at 55.02, but plain main scores WORSE at 73.24 - the baseline is stale relative to main's D217 texture fixes, the re-baseline owed since M-83/M-84. Not caused by this branch. * docs(D227/D229): regenerate findings index; mark D227 FIXED The generated findings-index.csv was hand-edited during the main merge and when D229 was filed, so CI's 'Check generated dev indexes are current' failed on PR #43. Regenerated with tools_pc/gen_findings_index.py. Also updates the D227 row's status to lead with FIXED (M-100b) rather than the M-94 'not yet fixed' text, so the index reflects the user-confirmed fix. * docs: register GE_D227V in the env-probe index CI's 'Check generated dev indexes are current' also runs gen_env_probes.py --check, which flags any GE_* probe with live sites that has no row in docs/dev/GE-ENV-PROBES.md. GE_D227V (added in M-100 to measure the sky texcoord overflow) had none. |
||
|
|
50d73c519c |
D217 (M-90): align texpool arena bases to 8 bytes on PC — fixes solid wrong-colour weapon/model CI tiles (#47)
* D217 (M-90): align texpool arena bases to 8 bytes on PC — fixes solid wrong-colour weapon/model CI tiles Root cause: texAlignIndices pads CI index rows to ABSOLUTE 8-byte boundaries, and model-DL G_LOADTLUT palette offsets are a formula that only matches the real layout when the texpool base is 8-aligned. On N64 the memp bump allocations feeding these pools happened to be 8-multiples; PC pointer bloat (D36/D37-class struct growth) made some STAGE-bank sizes not, so weapon-pool bases land %8==4 (e.g. FACILITY). Every palette in a misaligned pool then sits 4 bytes early inside its allocation while the draw reads the N64 offset: the PP7 grip's count=1 TLUT lands on the next tenant's 8-byte header slot (texnum=26 -> LE s16 -> 0x1A00 = the green), and larger loads read a 2-entry zero pad then the real palette (the M-86 'leading zero pad' tell). BUNKER1 was clean only because its bases happened to be aligned. Fix: texInitPool rounds the arena base up to 8 under #ifdef PORT; end unchanged (pool loses <=7 bytes). No-op on N64 where bases were already aligned. ABI/layout-class edit per AGENTS.md rule 2 narrow exception — no game logic touched. Verified: FACILITY 188/188 decodes %8==0, draw reads pal0=0001 (was 1a00), 0 green pixels in frames 250-450 (pre-fix ~1300); BUNKER1 + DEPOT still clean; full verify.sh sweep 21/21 PASS. Pending: human playtest eyeball of the M-87 weapon matrix (closes defect 1). Defect 2 (CI cache content-hash key, fix/d217-model-texture-palette) remains separate/parked. findings.md: new section H 'D217 - M-90' + D217 row update; GRAPHICS-BACKLOG D217 status. * docs(D217): M-92 - human playtest of PR #47 confirms PP7/PPK/watch fixed; AK47 + NPC-held world-model weapons are a distinct, still-open white-texture residual Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG * docs(D217/D228): M-97 human playtest - green FIXED, AK47 white patches split out as D228 Second independent playtest (after M-92) of PR #47 on FACILITY, this time with the branch merged up to date with main. Confirms the same split the M-90 write-up predicted: PP7/PPK grip+hammer, watch face and world-model green are all fixed by the texpool alignment; NPC-held AK47 white missing-texture patches are untouched. White is a different signature from wrong-colour -- a wrong palette binds a real TLUT with wrong contents, white means the texture was never decoded/uploaded at all -- so this is a separate mechanism, not an incomplete fix. Split out as D228 so D217 can close on the green mechanism it actually describes instead of carrying an unrelated open failure mode indefinitely. |
||
|
|
9d45c6aae6 | chore(D221): regen findings-index.csv (CI validate) | ||
|
|
97946633dd | chore(D77): regen findings index | ||
|
|
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 |
||
|
|
1351e9db34 |
docs(D194): user confirms aim-mode over-sensitivity, suggests unifying sensitivity base
User independently confirms D194 part (a) and suggests the fix: aim sensitivity should scale off the same base per-pixel sensitivity as normal hipfire-turn mouse movement, not its own disconnected AIM_GAIN/ AimBand floor+clamp curve. Noted this is orthogonal to the SOLITAIRE scheme change (aim mode is a separate insightaimmode-gated codepath, untouched by natural-pitch/turn) - needs its own fix either way. Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG |
||
|
|
c729fe4750 |
docs(D194/D166/D223): SOLITAIRE control scheme gives free continuous mouselook
User correction (they were right): the digital-C-pitch-vs-analog-yaw
asymmetry only applies to the 1.1 HONEY scheme the port currently
forces. User recalled that 1.2 SOLITAIRE uses C-buttons for movement
(fwd/back/strafe) and gives the analog stick full continuous pitch+yaw
('natural pitch/turn') - verified in code (bondview2.c:5195-5253 digital
step movement; :5950/6008 canNaturalPitch/Turn drive
analogPitch//70.0f/analogTurn/70.0f directly, gated only by
!insightaimmode, no threshold/clamp; :4881-4882 these default to the
primary stick's raw X/Y for every 1.x scheme).
This is architecturally identical to modern FPS mouselook. Recommended
fix: force controldef=SOLITAIRE instead of HONEY, WASD -> digital step
buttons, mouse X/Y -> stick X/Y directly and continuously. Resolves the
vertical/horizontal asymmetry with zero digital-pulse emulation (D166's
hack becomes largely moot) and likely much of 'feels unresponsive' too.
Bonus: SOLITAIRE keeps the same shootButtons/invButtons roles as the
current default (not grouped with KISSY/GOODNIGHT's different button
roles), so D223's wheel-cycle plan needs no changes if SOLITAIRE is the
target. GOODNIGHT also gets natural pitch/turn but would need D223 rederived.
Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG
|
||
|
|
bc287cd97d |
docs(D224): deprioritized — gamepad-only, this release targets mouse/keyboard
Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
8f615bde14 |
docs(D223): new — directional mouse-wheel weapon cycling, root-caused
QoL ask: wheel up = next weapon, wheel down = previous, instead of both directions cycling forward. Root-caused: GE already computes separate weaponForwardOffset/weaponBackOffset signals from invButtons(A)+shootButtons(Z) held/edge combos (bondview2.c:5338-5347, the N64 'hold A tap Z' backward-cycle trick); triggerOn is suppressed while invButtons is held so this never accidentally fires. Port's inputPostWheel() discards wheel direction and only ever synthesizes the forward (bare A pulse) case. Fix is port-only (input.c), synthesize the A-held+G-edge combo for wheel-down instead - no game-logic change. Open question: which N64 controldef preset is actually active in port saves, since invButtons/shootButtons differ per preset. Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG |
||
|
|
9016cf183c |
docs(D222): new — high FovScale breaks culling, NPCs/objects vanish near screen edges
Split out of D211's 'cosmetic edge-cull-early' caveat - user confirms this is a real functional bug, not cosmetic: NPCs and some world objects stop rendering near screen edges at high FovScale. Root cause: frustum cull / visibility math still runs at nominal unscaled fovy while the render projection is portFovScale-widened. Fix direction + a real risk flagged: some of these cull call sites may double as AI awareness/visibility checks, not just render culling - must audit before changing so portFovScale never leaks into NPC behavior (non-negotiable #2, game logic unmodified). Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG |
||
|
|
60a31cbc2e |
docs: M-87 playtest results — D178/D149 fixed, D182 still broken, D77 rescoped, D221 new
D178 (briefing objectives blank) and D149 (front-end 3D models garbled) - user-verified fixed, closed. D182 (file-select round-trip degradation) - still broken, confirmed. D77 (no music) - rescoped: music plays on intro/cast-roll/main menu, only missing in-level. Points at the level-start music cue, not the mixer/playback layer. D221 (new): SELECT FILE mouse hit-box vertically offset from the visible box (click below a file to select it) under the shipped default MouseCaptureMode=1 - distinct from D192's clamp-range bug under legacy mode 0, likely same front-end grid-coordinate family. Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG |
||
|
|
c82430abcf |
docs: M-87 playtest notes — D176/D173/D148 still current
D176: skybox still glitchy broadly (not just Surface); Cradle named as still showing solid black where sky/backdrop should be. Confirms (a) never landed a verified fix. D173: Bond hover/float cutscene glitch still present. D148/D160: new specific instance - Bond spins in circles in some cutscenes (end-of-Cradle Trevelyan-falls cutscene named) - likely same actor-position/anim-root cause as D173, manifesting as bad rotation. Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG |
||
|
|
ad82c0cac6 |
docs: M-87 playtest notes — D211 good, D217 matrix, D219/D220 logged
D211: user-verified good at big FovScale. D217: playtest matrix across weapons/levels (PPK/AK47/M16, Dam/Facility/ Jungle) - green, blue, white (missing), orange, all consistent with the per-weapon-material upstream theory, not one fixed offset. Watch still green. D219 (new): PPK explosions render purple on Dam - likely D172 particle- color family, different DL. D220 (new): 1P muzzle flash shifts upward at higher FovScale (Jungle M16) - likely same unscaled-viewmodel-fovy root as D211's residual caveat. Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG |
||
|
|
6b66c9ddc9 |
docs(D217/D211): M-86 — D217 root cause characterised; D211 aim-zoom verified
D217: two defects found. (1) The visible green grip/hand is upstream of fast3d — the grip's count=1 model G_LOADTLUT is fed different source bytes per level (Facility 1a 00 -> 0x1A00 green; Bunker 00 01 -> black); every larger model TLUT carries a 2-entry leading zero pad, so the port's model-TLUT source pointer is likely 4 bytes low (or a d43_emit.py blob offset). fast3d byteswap is correct (proven by Bunker). (2) A real CI texture-cache collision (no palette-content key) — fix landed on branch fix/d217-model-texture-palette f6fc0399 (palette FNV-1a hash in the CI key); owes a 21-level sweep; does not fix (1). porting-notes + graphics- backlog updated. D211: aim-zoom compose verified correct by construction (single post-lerp multiply at the fr.c chokepoint; zoom ratio preserved). Only a low-risk cosmetic eyeball remains (viewmodel/frustum-cull use unscaled fovy). Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG |
||
|
|
717a5210d3 |
docs(D217): repro pinned to FACILITY post-cutscene
BUNKER1 is clean in any config; -level_34 FACILITY reproduces at ~frame 300 (just after the vent-drop cutscene). PP7 grip = solid green block, green ring at the barrel joint, hand has a green cast; slide/barrel upper is correct black. GE_TEXI confirms the weapon CI tiles import with a loaded TLUT (fmt=2 palfmt=0x8000 palidx=0) -> gDPLoadTLUT does reach gfx_pc.cpp for model DLs. Green-tinted hand => wrong TLUT bound, not corrupt index data. Working theory: FACILITY pre-level cutscene leaves fast3d palette/tile state (or a TextureCacheKey collision on rdp.palette_addrs[] at gfx_pc.cpp:990) that the cutsceneless BUNKER1 never hits. Next steps + suggested subagent scope in the finding. Claude-Session: https://claude.ai/code/session_01U7gyYC3pcatXHhD1kPCgsR |
||
|
|
f9ff24101a |
docs(D217/D211): M-85 triage results
D217: did not reproduce headlessly on main @
|
||
|
|
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 |
||
|
|
9c623858c1 | regen findings-index (validate gate); M-84 verdict tokens on D211/D214/D217 status cells | ||
|
|
6e5a4e4e7f |
Merge main into fix/d215-viewmodel-renderdata-flags (M-84 findings rows)
# Conflicts: # docs/dev/findings-index.csv # docs/dev/findings.md |
||
|
|
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. |
||
|
|
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. |
||
|
|
f602b1738f |
fix(D215): 1P weapon viewmodel never drew in-level — renderdata.flags read 0
gunRenderFirstPersonGunModels built its ModelRenderData with
renderdata = *(ModelRenderData *)&D_80035CC0;
a reinterpret that reads the lone `u32 D_80035CC0 = 0` and continues into the
next declared global `u32 D_80035CC4[] = {1, 3, 0, ...}` (gun.c). On N64 that
yields { basemtx=0, zbufferenabled=1, flags=3, rest=0 } because the linker
lays the two globals out contiguously in declaration order and pointers are
4 bytes. On x86-64 it breaks twice: ModelRenderData's pointer members widen
4->8 B (so `flags` is no longer at struct offset 8) and C guarantees nothing
about the two globals' placement. Measured (GE_DVM=1, BUNKER1): flags == 0,
which gates every weapon-model DL node off in modelRenderNodeGundl
((renderdata->flags & 1)) -> subdraw emits 1 gfx command instead of ~150 ->
no gun visible while playing.
All three VIEWMODEL-RESEARCH.md ranked suspects were ruled out first by the
same probe: field_87F==1, RootNode valid, numRecords 22 < pool 192, node walk
visits 24+ valid DL/SWITCH/BSP nodes.
Fix: #ifdef PORT -> set the fields explicitly (zbufferenabled=TRUE; flags=3);
N64 line verbatim under #else. ABI/layout only (D3x class), no logic change.
Verified (headless): GE_DVM=1 post-fix shows flags=0x3 and the weapon subdraw
now emits 153 gfx commands (was 1); bunker1 framediff worst_cell 12.2 -> 28.5
(gun renders in-frame); Dam/Facility/Bunker1/Archives/Cradle/Aztec sweep
crash-free. Owed: one BUNKER1 eyeball; the bunker1 golden needs re-baselining.
GE_DVM gate+render probes left in, env-gated (cf. GE_D193*).
findings.md D215 + index; GRAPHICS-BACKLOG + VIEWMODEL-RESEARCH marked resolved.
|
||
|
|
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. |
||
|
|
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. |
||
|
|
9b649b025d | docs(D172): ROOT CAUSE FOUND + FIXED — gfx_lod_tile_offset collapse; findings D172 row + index | ||
|
|
b47eea65e3 |
fix(D210): chrToPatrol leaves act_patrol.lastvisible60 uninitialised on PC
Sibling of D209 (A1 raw-byte union-alias class). chrToPatrol writes act_init.padding[0x13] (union byte 0x4c) = -1, which is act_patrol.lastvisible60 on N64. act_patrol starts with a patrol_path* that widens 4->8 on x86-64, so 0x4c now lands in waydata and lastvisible60 (@0x50) stays uninitialised. The patrol 'haven't seen the player recently' gate (chraction.c:~9347) then reads garbage on the first ticks after entering PATROL. Fix: write the named field under #ifdef PORT; N64 line verbatim under #else. ABI/pointer-width only, no game-logic change. Found by the post-D209 A1 sweep; the only new instance (act_anim/act_dead padding aliases are pointer-free -> layout-stable). Build clean; bunker1 + facility verify PASS (both patrol-heavy), no crash. findings D210 + porting-notes A1 + findings-index regen. |
||
|
|
b0b5f948f1 |
chore(D209): regen findings index
gen_findings_index.py --check gate was red on the new D209 entry. |
||
|
|
cf477c20cf |
docs(D191): findings + porting-notes + regen findings-index
Playtest-verified (Bunker ii + Statue). 21/21 solo levels crash-free. Regenerated findings-index.csv (the validate CI check requires it current). |
||
|
|
15c374813e |
docs(audio): D208 FIXED + playtest-verified (M-78d)
Claude-Session: https://claude.ai/code/session_01U7gyYC3pcatXHhD1kPCgsR |
||
|
|
76910cbf19 |
fix(audio): D208 — player fire sound goes permanently silent (field_A48 ptr width)
Root cause of the "doesn't recover after the alarm stops" half of the user's D207 report (distinct bug from D207's voice-pool question). struct field bondview.h:200 `s32 field_A48` is used as a SECOND ALSoundState* sound-handle slot for the player's double-buffered per-hand fire sound (gunfire.c:3186-3200). sndPlaySfx stores the new state through `&field_A48` as an 8-byte `pendingState->link.next = nextState`; as s32 that write tears into the adjacent field_A4C (the auto-fire cadence timer, rewritten every frame). After enough double-buffered fire cycles field_A48 holds a value that is neither 0 nor a valid pointer, so the `else if (field_A48 == 0)` re-arm gate never fires again and no further player gunshot is issued. Evidence: user Bunker audiotrace.log — silenced-PP7 idx-46 sndPlaySfx requests stop dead at log line 18930/25086 while all other SFX continue; the player-gun slot 0x70076630 is then caught holding a torn value 0x000001db00000000 (field_A48=0 + field_A4C=0x1db read as one word). Fix (D3x ABI/layout, #ifdef PORT): declare field_A48 as ALSoundState* under PORT, s32 verbatim under #else. hands[] is runtime-only state (g_CurrentPlayer), never ROM-serialized, and all downstream fields are accessed by member name — widening 4 bytes is layout-safe. The (struct ALSoundState *) casts at the use sites become no-ops and stay for a minimal diff. Build clean (gunfire.c + full link). Runtime verification owed: replay the Bunker firefight, confirm idx-46 requests continue for its full duration. Claude-Session: https://claude.ai/code/session_01U7gyYC3pcatXHhD1kPCgsR |
||
|
|
2f503f007d |
diag(audio): D207 — re-arm [D207-DROP] counter; close D205
D205: CLOSED (M-78). User re-tested explosion->scream on Bunker after the PR #28 merge — the explosion SFX is correct by ear now. All four D205 symptoms were D206 (the +1 bank-slot alias); the "scream" was a coincident guard death-yelp (faithful N64 behaviour). M-74's pan audit already ruled out a presentation-layer bug. D207: re-add ONLY the [D207-DROP] trace line (not the reverted last-resort LOOPED-preemption pass) at both sndDisposeSound no-voice sites in snd.c, gated on GE_AUDIOTRACE, logging state/prio/flags/count=N/max — so a user capture on the real failing level is decisive between (a) heavy-level 8/8 starvation, (b) a leak on an alarm-stop path, (c) perceptual. Static re-check noted in findings D207: ALARM3 slot 162 sampleVolume=80 (below gunfire), keyMax=8 => not RETRIGGER => continuous-loop by design on N64 too. findings-index.csv regenerated. Claude-Session: https://claude.ai/code/session_01U7gyYC3pcatXHhD1kPCgsR |
||
|
|
c855625643 |
docs(dev): D202 closed, D206 by-ear PASS, D207 monitoring (M-77c)
User by-ear A/B vs N64 confirms D206: armour pickup, unarmed melee whiff and silenced PP7 all correct. D202's silenced-PPK 'slap' was D206 (SFX bank index +1) — CLOSED; M-65/M-66b ownerless-loop mitigations stay. D205 narrows to symptom (2) explosion->scream only. D207 (alarm klaxon) acceptable in play per user; voice-starvation hypothesis didn't hold up (M-77b), no fix shipped, GE_FORCEALARM probe kept for follow-up. Regenerated findings-index.csv. |
||
|
|
efe477033f |
docs: D202 M-63/M-65/M-66/M-66b + D204 writeups; audio status in README/AGENTS
- findings.md: D202 full session history (M-63 live repro, M-65 voice-leak guard + sndCreatePostEvent unstub, M-66 root cause — sound 203 infinite ADPCM loop played fire-and-forget = faithful N64 quirk; M-66b option-C expiration implemented + measured) and D204 (AI-queue starvation, fixed + measured). Index row updated; findings-index.csv regenerated. - GE-ENV-PROBES.md: D202 probe set documented ([EXPIRE] marked permanent). - GRAPHICS-BACKLOG.md: D172 / D176 / 1P-viewmodel rows point at the new research docs. - README + AGENTS.md: audio status corrected (mixer landed post-v0.1.0; Phase 3 in progress, not 'no audio'). |
||
|
|
c0ae2cf295 |
chore(ci): regenerate findings-index.csv for D195/D197-201
tools_pc/gen_findings_index.py output after the audio-findings commit. |
||
|
|
a5e6cd443a |
fix(ci): regenerate findings-index.csv, document GE_DETERM_TRACE
The D117 findings.md addenda in the prior commit changed D117's status text without regenerating findings-index.csv, and the new GE_DETERM_TRACE env var wasn't yet mentioned in GE-ENV-PROBES.md -- both fail their --check gate in CI's validate job. Caught by a real CI run, not predicted. Claude-Session: https://claude.ai/code/session_01P3WrUHAFQZUuhrHhiJgTUr |
||
|
|
dbea5a30fe |
chore(agentic): speed-ups plan Steps 1, 4 (N2/R8/R9), 6
Static/tooling slice of docs/dev/notes/AGENTIC-SPEEDUPS-PLAN.md — no build or
runtime environment needed to verify any of it.
- Step 6 / N7: add a VERIFY: line to the investigation-brief template
(docs/dev-process.md) and the CLAUDE.md dispatch-preflight checklist — the
brief must now name the exact probe/verify invocation that proves the task
done, or state that runtime verification is impossible here.
- Step 4 / N2: tools_pc/README.md — indexes the ~60 scripts with a
living vs artifact marker so closed-finding artifacts can be archived.
- Step 4 / R8: tools_pc/gen_findings_index.py -> docs/dev/findings-index.csv
(label, bucket, one_liner, status_raw) — grep-before-you-read aid for the
~200 KB finding log. Heuristic parser of the §F/§H tables.
- Step 4 / R9: tools_pc/gen_env_probes.py — re-greps live getenv("GE_*")
sites and reports NEW/GONE drift vs docs/dev/GE-ENV-PROBES.md (keeps the
curated prose, only checks the site map). Doc reconciled: 7 new probes
added, GE_D54 kept as a tombstone.
- CI: validate job now runs both generators with --check.
Step 1 / N1 (HANDOFF preflight) applied in the local-only docs/HANDOFF.md.
Step 2 (verify.sh) and the HANDOFF/HANDOFF-ARCHIVE trim are left for a
session with a build environment.
|