mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-24 00:02:45 +02:00
70bc9d65b5e43249bb96c64b3de3cec9e3bf9ba4
609
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
70bc9d65b5 |
D250: getenv() caching fix -- root cause of the Dam fps drop (plus sysSleep unit fix, GE_D250 probe, perf.ps1) (#75)
* D250: fix sysSleep unit-conversion bug in frame-pacing; add GE_D250 lvlRender probe port/fast3d/gfx_sdl2.cpp sync_framerate_with_timer() computed the remaining frame budget in 100ns units (qpc_to_100ns) and passed it straight into sysSleep(), which takes microseconds -- a real 10x oversleep whenever that branch fires. Genuine bug, but only active when target_fps is nonzero (tied to Input/Video FpsCap); confirmed via live testing that it is NOT the active cause of the user's reported ~45-50fps with FpsCap=0/VSync=0, where this pacing path never runs. Added GE_D250 (src/boss.c, #ifdef PORT) to isolate lvlRender()'s DL-building cost (culling/animation/scene traversal) from gfx_run's already-measured software-RSP interpretation cost. Live test on Dam shows lvlRender averaging ~0.5-1.5ms -- also not the bottleneck. Documented in docs/dev/GE-ENV-PROBES.md (GE_D250 row). findings.md D250 update to follow after rebase. Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog * docs(D250): live A/B session -- MSAA/aniso/FOV/resolution/vsync all ruled out Live session with the user driving the game directly, confirmed the sim-healthy/render-sagging pattern from earlier idle testing reproduces under real play on Dam. One-at-a-time A/B via the F10 overlay ruled out MSAA, anisotropy, FOV, resolution (1280x960 fullscreen vs 640x480 windowed), and VSync (render never exceeds ~60 even uncapped) as the cause -- the cost is resolution/GPU-setting-independent. gfx_run (~6-10ms) + lvlRender (~0.5-1.5ms, via the new GE_D250 probe) together don't account for the real ~20-22ms/frame. Leading remaining suspect: the emulated N64 thread-handoff/scheduler round-trip (gesched.c/sched.c) -- real OS context-switch/wake latency standing in for what was free hardware handoff on N64. Needs a real profiler (Visual Studio / WPR) attached during a live bad-fps moment to confirm; ad-hoc per-call timers have now ruled out the two most obvious CPU call sites without finding the cost. Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog * D250: fix uncached getenv() in fast3d hot paths -- root cause of the fps drop A live WPR/xperf CPU trace (tools_pc/perf.ps1, new sister script to debug.ps1) with Microsoft public symbols resolved found the dominant render thread spending only 5.2% of its CPU time in the game's own code -- 43% was in msvcrt.dll, and getenv()/_getenv_helper_nolock alone accounted for ~50% of the thread's total CPU time. Root cause: port/fast3d/gfx_pc.cpp's import_texture() (runs on every texture bind) and several per-triangle/per-G_SETCOMBINE debug-probe checks (GE_DTEX, GE_TEXDUMP, GE_TEXRAW, GE_D229 x2, GE_D172 x2, plus GE_TEXDUMP/GE_PCDUMP in gfx_opengl.cpp) called getenv() uncached on every call, unlike the safe static-cached pattern already used elsewhere in this codebase. On a texture-heavy scene that's tens of thousands of locked, locale-aware Windows CRT environment lookups per second -- invisible to both of the timers added earlier this session (gfx_run's own cost measurement and the new GE_D250 lvlRender probe), since neither isolates CRT/library time from the game's own code. Fix: cache every getenv() site in gfx_pc.cpp/gfx_opengl.cpp with the established static-int pattern. Narrow, mechanical, no logic change. Verified: the exact idle-Dam test that was pinned at 45-50fps before the fix now holds a rock-steady 60-61fps, zero dips. Also explains why the earlier MSAA/aniso/FOV/resolution/vsync A/B pass all showed "no change" -- the getenv() cost was a large per-call constant that swamped any real, smaller resolution-dependent GPU cost at every setting tried. Residual, separate issue found once this was fixed: still runs badly at 1440p+ (640x480 now holds a clean 60fps) -- likely the real GPU fill-rate cost the getenv() bug was masking, now exposed on its own. Not yet investigated. docs/dev/findings.md D250 updated with the full root-cause writeup. docs/porting-notes.md gets a new lesson under section E (cache getenv() in any hot-path probe). tools_pc/perf.ps1 automates the trace-and-analyze workflow for next time. Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog * docs(D250): correct the 1440p+ follow-up -- it was a stale-build false alarm User re-tested 1440p/4K on the actual getenv-fix build and confirmed it holds up fine too. The "still runs badly at 1440p+" report was from before the fix was live, same stale-build class as the DLL-load false alarm earlier in the session. D250 is fully resolved across all resolutions tested, not just 640x480. Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog * docs: add missing GE_D219RAW row to GE-ENV-PROBES.md (CI validate fix) Pre-existing gap unrelated to this branch's own changes -- gen_env_probes.py --check caught it now since it runs on every PR. GE_D219RAW has had live getenv() sites in src/game/image.c since the D219 investigation but was never added to the probe doc. |
||
|
|
ef0c0900b4 |
docs(D250): rule out build flags + idle-load as the fps bottleneck (#74)
Build flags confirmed as -O2 RelWithDebInfo by default, not the cause. GE_D193 idle-load probe on bunker1 and streets both hold a healthy 55-60fps in this dev box, well under the 16.7ms/60fps budget -- does not reproduce the user's reported ~45fps at 720p on a high-end PC, so the real cost is likely gameplay-load-dependent (AI/combat/particles) or resolution/GPU-fill-rate-bound in a way idle testing doesn't exercise. This session had no interactive input tooling to drive real gameplay headlessly. Ruled out in passing: the D51 vi-post debug log is throttled to ~once/sec, not a per-frame cost. Next step needs a human: capture GE_D193=1 output during a live session at the moment fps visibly tanks, to tell sim-side cost from render/GL-side cost. Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog |
||
|
|
4ed9cf88cd |
docs(D230/D243): log dispatch results — one hypothesis ruled out, one build-blocked (#73)
M-120: dispatched a local-Qwen triage pass on D230 (music quality) and a
Claude subagent on D243 (Dam cutscene race), per the roadmap triage.
D230: the dispatched hypothesis (afFixupBook's BE->LE book-coefficient
conversion in port/src/romdata.c is backwards) does not survive review —
it's the same conversion pattern used for every other field in the same
function, and if it were wrong every ADPCM sound would be corrupted, not
one bass instrument on three tracks. Ruled out; the standing M-108 leads
(predictor-count truncation / order mismatch in port/src/mixer.c) remain
the live hypothesis.
D243: the subagent wrote the planned GE_D243 env-gated MENU_* transition
trace (src/game/front.c only, statically reviewed sound) but could not
build-verify it — hit a reproducible CMake/ninja-vs-bare-gcc environment
failure ("Cannot create temporary file in C:\Windows\"). Independently
reproduced the same failure in a fresh worktree this session. Diagnostic
is pushed on investigate/d243-cutscene-race (2a47f110), not merged —
needs a session with a working toolchain to build, run the -level_33
repro, and correlate against the D146 burst.
Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog
|
||
|
|
3470b68ae9 |
docs: regenerate findings-index.csv, fix stale statuses; file D249/D250 (#72)
Findings-index regen (tools_pc/gen_findings_index.py) — only D194's row had actually drifted (fix direction implemented since, on PR #62, not "not implemented" as the cell said). GRAPHICS-BACKLOG.md's D233/D234 rows were also stale ("not investigated") despite both being fixed and verified since M-107/M-108 per findings.md. New findings from user QA (M-119): - D249: LOD/culling breaks on far geometry on big open levels (Streets, Egyptian) at default FOV; distinct from D222/D218. Also a QoL ask for a togglable LOD improvement, N64-accurate LOD kept selectable. - D250: FPS now drops to 20-40 intermittently on many levels, a likely direct consequence of D248 (M-118) exposing real per-frame cost that was invisible while the port silently ran at half rate. Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog |
||
|
|
52477b44af |
D248: fix port-wide 30fps halving -- pointer-width scheduler ABI bug
osScAddClient() stashes a per-client "every retrace vs every other retrace" flag in the next array slot's .next field (c[1].next = next); __scHandleRetrace read it back via a hardcoded 8-byte offset that only lands on the right memory when OSScClient is 8 bytes (32-bit pointers). On this 64-bit port the struct is 16 bytes, so the read landed inside the gfx client's own msgQ pointer (always non-NULL) instead, silently collapsing the gfx client onto the audio client's 30Hz path even though the VI-retrace pacemaker itself was always correctly ticking at 60Hz. Fix indexes the struct (client[1].next) instead of hand-rolling the byte offset, so it scales with pointer width on either platform -- same class as D122/D126/D132/D209, just in the scheduler instead of a ROM-serialized struct. Verified via the Video.DisplayFPS overlay: 30 FPS pre-fix, 60 FPS post-fix, same build/scene (-level_09). -level_09/-level_20 both crash-free post-fix. |
||
|
|
dac4ae7c0c |
D228: fix white NPC-weapon-texture patches -- IA16 palette intensity/alpha byte-order bug
GE_TEXDUMP on Facility (-level_34) found ~14% of LOD-textured CI uploads whose palette is IA16 (intensity-alpha, not RGBA16) rendering solid near-white. A GE_D228 correlation probe ruled out the texpool/RC2-clip hypothesis this row started with -- decoded size exactly matched the base tile every time, so the data reaches import_texture fine. The real bug: palette_to_rgba32's G_TT_IA16 branch read intensity = palentry & 0xff (low byte), alpha = palentry >> 8 (high byte) -- backwards relative to the already-correct direct-IA16 decode (import_texture_ia16) a few lines above it in the same file, which reads intensity from the first byte (high, after gfx_dp_load_tlut's PD_BE16 swap) and alpha from the second. A dim, fully-opaque palette entry (e.g. intensity=0x58, alpha=0xff) decoded backwards as bright and mostly-transparent -- the white-patch symptom. Fix: swap the two lines to match the working convention. User-verified live: AK47 and other metallic guns render correctly. Headless -level_34 run crash-free pre-verification; a full sweep is still owed (the user's own game instance was running concurrently this session, so verify.sh/taskkill were avoided). Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog |
||
|
|
53c9a879f3 |
D245: reopen -- live playtest shows the allowShift=TRUE fix did not resolve the seam
User report on Frigate post-fix: water still glitches between two patterns and still changes with view angle, unlike the sky (which uses the same skyPortBeginFan/skyPortEmitTileShift mechanism post-D227 and no longer has this problem). The mechanistic writeup and GE_D227V trace from the same session looked correct but were evidently not sufficient evidence -- a single static frame/angle capture never exercised whatever actually causes the live symptom. Reopening honestly rather than leaving a stale FIXED label; the superseded analysis is kept in the row for reference but flagged as unconfirmed. No code change in this commit (docs only). Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog |
||
|
|
a088bb51e7 |
D195: restore G_TEXTURE_GEN envmap UV generation -- fixes black reflective materials game-wide
GE uses real N64 RSP environment-mapping (G_TEXTURE_GEN) for every shiny/reflective material: gold/silver weapon skins, chrome trim, and Control's reflective console glass. An earlier session (D72.1) removed the PD-inherited envmap UV-generation block wholesale from gfx_sp_vertex to fix the Rareware logo, reasoning "GE never generates texture coordinates from vertex normals" -- true for the logo (lit, but doesn't set G_TEXTURE_GEN) but false in general. Every envmap draw was left sampling whatever raw tc[] happened to be authored (near 0), landing on a fixed corner texel of the reflection texture regardless of view angle -- reading as solid black for a typically dark-edged reflection map. Restored the envmap UV generation (ported from the reference Perfect Dark PC port's already-correct implementation), gated specifically on rsp.geometry_mode & G_TEXTURE_GEN so D72.1's actual fix is untouched (the logo never sets this bit) while every other envmap draw gets real reflection coordinates back. User-verified live: console glass and gold/silver guns are now visibly reflective instead of flat black. The remaining "not translucent" complaint on the glass is confirmed NOT a bug -- it was never see-through on the original N64 either (reflective by design, not transparent). Full 21-level sweep crash-free post-fix. Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog |
||
|
|
1f7f804733 |
D247: close as NOT A BUG -- confirmed faithful N64 TV-safe letterbox; D246 narrowed
Display pipeline pass on the two remaining M-116 QA reports. D247 (black bars top/bottom): traced to bondviewGetCurrentPlayerViewportHeight() returning VIEWPORT_HEIGHT_DEFAULT (220 of 240 NTSC scanlines, fr.h) for the decomp's own default SCREEN_SIZE_FULLSCREEN option -- unmodified ground-truth code, no #ifdef PORT anywhere near it. Verified against both the existing save and a fresh EEPROM (rules out a stale Screen=Wide/Cinema setting from earlier sessions) -- same inset either way. This is GoldenEye's real TV-safe-area letterbox, present on original hardware. No code change. D246 (left/right edge strips): GE_PCDUMP captures of the native render show clean edge columns, ruling out an RDP/game-DL scissor bug and the D227/D245 tc-overflow class -- the defect must be introduced later, in the SDL window presentation path GE_PCDUMP bypasses (MSAA resolve blit or a live-window scale remainder). Still needs the user's window size/MSAA setting to chase further; documented as a narrowed OPEN with concrete next steps. New porting-notes.md entry: check the decomp's own constants before assuming a rendering "defect" is a port bug. Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog |
||
|
|
4806799867 |
D245: fix IsWater view-tracking seam -- allow the D227 tc-shift safety valve on the water quad
The water quad's fan was the one skyPortBeginFan() caller passing allowShift=FALSE, on the theory that a matched tc-shift on both texunits would flatten the sinf()-driven cross-fade the effect depends on. That theory was wrong: skyPortEmitTileShift() replays both tiles' SETTILE commands with the identical k, rescaling both texunits' decoded S/T by the same factor before each tile's own (unshifted) uls/ult offset is applied by the RDP -- the offset between tile 0 and tile 1's sample points is preserved, not flattened. Confirmed with the GE_D227V probe on Frigate (-level_26): a water fan vertex spans S=-166..299317, T=-6451..288410 in S10.5 units (same order of magnitude as D227's pre-fix sky overflow), now correctly getting kS=4 kT=4 instead of being forced to 0. verify.sh frigate and "surface 2" (the two IsWater levels) both PASS crash-free. Live playtest of the mouse-tracking seam itself still owed. Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog |
||
|
|
9b0acccd1d |
D229: fix green/pulsating IsWater water -- fast3d wasn't expanding CI8 indices
texSelect loads the water image as ZLIB+CI8 (raw 8-bit palette indices); GE's custom RSP ucode expands them to RGBA16 in TMEM at load time, so a later RGBA/16b tile re-declaration over the same slot samples real blue pixels. fast3d has no such expansion and read the raw index byte-pairs as 16-bit texels (g dominates -> green mottle); the "pulsing" was always the pre-existing PRIM_LOD_FRAC sinf() cross-fade between two mis-decoded tiles. Track each TMEM load's source G_IM_FMT in LoadedTexture.src_fmt; in import_texture, an RGBA/16b tile over a CI-sourced slot now routes through the CI8 palette import path instead. Fires only on the genuine format disagreement, so real RGBA16 textures are untouched. Verified live on Frigate (-level_26): water renders blue, user confirms "looks better". Sweep + Surface 2 by-eye still owed. Also files D245 (water seam), D246 (edge strips), D247 (black bars) from the same playtest session. Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog |
||
|
|
9137f8f44b | docs(D219): flag the unverified 16-bit texnums in porting-notes for future triage | ||
|
|
b1cf0eac4c |
D219: scope the fire-particle byte-order fix by pixel format; resolved
M-115: re-apply M-113's store-time bswap scoped to the 16-bit GBI-size family only (PORT_PIXEL16 = bswap16 under #ifdef PORT; PORT_PIXEL32 stays identity). The fast3d importers disagree on pool byte order: import_texture_rgba16/_ia16 read manually big-endian bytes, while import_texture_rgba32 does PD_BE32() on a native load -- M-113's blanket swap hit both and regressed muzzle flash/ammo HUD (M-114). Format is the structural discriminator, not compmethod (fire frames span LOOKUP/RLE/HUFFMANBLUR/RLELOOKUP) or texnum range. Offline census of all 2698 ROM images (scratch/d219_headers.py / d219_census.txt): 94 non-zlib wide-pixel; the 16-bit family is IMAGE_FIRE_0..14 + texnum 1198-1201/2430/ 2510-2523, every ammo/flare/crosshair/muzzle-flash image is RGBA32. Runtime-verified on Dam (scripted fire): all 15 frames decode format=1, pool dumps warm R-dominant (mean opaque RGB 255,238,139). RGBA32 pool bytes bit-identical to the M-114-revert build. User playtest confirmed; D219 closed. GE_D219RAW probes generalized (no texnum filter), inert. |
||
|
|
f55a89515f |
D219: root cause found -- fire particle texture data itself is R/B-swapped, not a rendering bug (#71)
Live-tested against 4 real user playtests (not scripted) tonight, each confirming purple/blue explosions still reproduce on current main: 1. G_LIGHTING hypothesis (previous session's lead) RULED OUT: extended the D172 probe to flag lit particle draws uncapped (the original 40-hit cap, sized for a short scripted repro, silently went dark after ~40 ordinary bullet-impact particles well before any real explosion -- masking the result, not confirming it). Fixed with an always-on periodic counter. Result: 5000+ particle draws logged across a real playtest with explosions triggered, 100% LIGHTING=off, 0 suspect hits. 2. Texture-cache-collision hypothesis RULED OUT: extended the same probe to log the actual bound TEXEL1 address/GL-texture-id/byte-count. 7 distinct, internally-consistent 448-byte (16x14 RGBA16) textures observed -- no aliasing, no stale-slot signature. 3. ROOT CAUSE: used the existing GE_TEXDUMP feature (extended to exempt 16x14 RGBA images from its own 400-image cap, same masking problem as above) to dump all 15 IMAGE_FIRE_0..14 particle textures as PPM during a live playtest with confirmed-purple explosions. Every single one decodes blue/purple-dominant; swapping the decoded R and B channels turns all 15 into a plausible warm fire palette, consistently. This is NOT a bug in the shared RGBA16 decoder (port/fast3d/gfx_pc.cpp import_texture_rgba16) -- that function is proven correct by every other RGBA16 texture in the game rendering fine, and the general C-array byte-order normalization already runs upstream of it. The wrongness is in the decoded bytes themselves: either the Globalimagetable entries for these 15 image ids resolve to the wrong ROM offset, or these assets use a sub-format the shared decoder doesn't special-case. Do NOT blanket-swap R/B in the shared decoder -- it would break every correctly-rendering RGBA16 texture in the game. Also fixes a mistake made mid-session: a follow-up raw-byte-dump probe (GE_TEXRAW) initially keyed its own cap-exemption on the wrong field (gsDPSetTileSize's logical 56x56 wrap size instead of the real 16x14 pixel content), so it never matched and burned a live-playtest cycle for nothing. Fixed to key on the real signature: a 448-byte load (16*14*2, RGBA16). Diagnostic infra left in tree, env-gated and inert by default: GE_D172=1 (uncapped lit-case + periodic counter in gfx_sp_tri1), GE_TEXDUMP=1 (16x14-exempt-from-cap fire dumper in gfx_opengl_upload_texture), GE_TEXRAW=1 (fixed 448-byte-exempt raw dump in the texture import path). |
||
|
|
85393fec3f |
ci(windows-build): default to the self-hosted runner, windows-latest only for releases/fork PRs (#70)
Previously: self-hosted for push/workflow_dispatch, windows-latest for every pull_request (including the user's own same-repo branches). Per standing preference (2026-09-12): route all Windows builds to the self-hosted box for now, except when cutting a release (tag push) -- that stays on a clean GitHub-hosted runner for the artifact that actually ships. Fork PRs also stay on windows-latest regardless (untrusted code must never reach a box that will later hold a ROM, docs/dev/notes/SELFHOSTED-RUNNER-PLAN.md §G2). Adds a `windows_selfhosted` output on the `changes` job (computed once) and threads it through windows-build's runs-on, the msys2/setup-msys2 release/update/install flags, and the self-hosted dependency check -- replacing every prior `github.event_name == 'pull_request'` check. |
||
|
|
81054e4c98 |
D219: instrument a G_LIGHTING probe on explosion particle draws (#68)
* docs+probe(D219): new lead -- explosion.c never clears G_LIGHTING before particle draws explosion.c's 15 particle DLs (assets/oddtextures.c globalDL_0x078..0x9a8, all G_CC_INTERFERENCE+G_CC_MODULATEIA2) never get G_LIGHTING cleared by any of explosion.c's own gSPClearGeometryMode calls. If lighting is left on from a prior draw (e.g. a nearby lit character model), the port's vertex path (gfx_pc.cpp:1301-1346) reinterprets the particle's authored RGBA tint as a normal vector, turning SHADE into a scene-light color instead of red/orange -- state-dependent, would explain "game-wide, live-play-only" exactly as reported, and why the M-90 scripted repro (no nearby lit models) came back clean. Extends the existing D172 multitex-tri probe (GE_D172=1, already proven reliable and uniquely keyed to this particle family) to also flag geometry_mode & G_LIGHTING. Sanity-checked against the known-clean M-90 repro: still fires 40/40, all LIGHTING=off, matching the prior clean result -- confirms the probe works without false-positiving. Not a fix -- needs one live GE_D172=1 capture during a real explosion to confirm or rule out before touching any rendering logic. * fix: regenerate findings-index.csv via tools_pc/gen_findings_index.py Hand-edited the CSV directly in the previous commit instead of running the generator -- CI's "validate" job caught the drift. |
||
|
|
6e541483a4 |
fix: regenerate findings-index.csv (stale since PR #66/#67 hand-edits) (#69)
Those two PRs edited the CSV by hand instead of running tools_pc/gen_findings_index.py. Both were docs-only, so CI's "validate" job (which only runs when non-doc files also change) never caught the drift. |
||
|
|
8e2213c28f |
docs(D219/D234): user confirms D219 game-wide + wants it fixed; D234 already closed (#67)
D219 (purple/blue explosions): user confirmed 2026-09-12 this is not Dam-exclusive -- game-wide -- and wants an active fix, not further triage. Static/scripted analysis (M-89/M-90) is exhausted; next step is a live GE_PCDUMP capture during a real explosion. D234 (Dam guard towers): already marked VISUALLY VERIFIED FIXED in both the findings.md row and findings-index.csv -- no change needed, just confirming consistency per the user's note that this is resolved. |
||
|
|
df7186617b |
docs(D230/D235/D243): M-108/M-109 static-analysis follow-ups from 3 parallel subagent investigations (#66)
D230 (bass-instrument audio corruption): narrowed suspect to port/src/mixer.c's hardcoded order=2/npredictors<=8 ADPCM decoder assumptions; not yet confirmed against real ROM book data. D235 (wrong textures after death/restart): the leading "CI-cache survives restart" theory is likely refuted -- lvlStageLoad (the only call site for both set_mt_tex_alloc and proplvreset2) runs on every stage transition including restarts, so the cache reset isn't actually skipped. Real mechanism still unknown. D243 (Dam end-cutscene D146 corruption burst): corrects the prior write-up's timing claim by re-reading the same session's raw logs at the frame-counter level -- the corruption fires ~1600-3900 frames after the camera-cut, not during it, in both runs (not just one). This also weakens the D134/pendingGfx race citation, since osSpTaskStartGo runs gfx_run() synchronously. New lead: the mission-complete/briefing screen chain, not the cutscene render itself. Claude-Session: https://claude.ai/code/session_01CUFSnpgrAyQSDVHoQQ6Sx3 |
||
|
|
f26fb8f0a3 |
docs(D160/D219/D233/D234/D243): M-108 live-playtest findings (#64)
Live GE_D160=1 traces on Dam plus a fresh-save playthrough during this session's interactive playtest: - D160: corrects its old "cutscene never plays" premise (refuted by a live trace showing the AI trigger fires and all 3 camera_switch calls execute). Real defect split out to new D243: a stale/freed display-list-memory read (D146 guard burst, freed-memory byte pattern) during the cutscene's camera-angle cuts, evidenced by non-deterministic call ordering between two runs. User confirmed the glitch reproduces on real fresh-save play, not just the debug-load harness. - D233: visually confirmed fixed (Frigate -level_26, corrected level id). - D234: visually confirmed fixed; mechanism still unexplained (the M-107 investigation argued D233's fix shouldn't cover this symptom). - D219: REOPENED. Live Dam PPK firefight still shows purple/blue explosions, contradicting M-90's clean scripted-repro verification — the scripted GE_INPUTSCRIPT repro likely didn't exercise the same explosion DL live play hits. User's impression is this is game-wide, not Dam-specific; needs a second-level confirmation. No src/ or port/ changes. findings-index.csv regenerated via tools_pc/gen_findings_index.py. Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG |
||
|
|
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
|
||
|
|
7e99344741 |
docs(D148/D160/D173): re-confirm propDef stride ruled-out; rule out PadRecord.stan and act_anim aliasing for puppet-position bug (#61)
Investigation pass on the cutscene-reliability cluster (D148/D160/D173), part of the 1.0 acceptance-bar #4 push. No build/runtime toolchain was available in this worktree (no gcc/cmake on PATH, MSYS2 MINGW64 not active), so this is a static-analysis-only pass; no code changes. D160 (Dam end-cutscene / D148 label): independently re-derived (not just re-cited) that hypothesis (a), a propDef sizepropdef() stride desync, is ruled out — CutsceneRecord (propDef type 46) is all-scalar, 28B, zero pointer/union fields, so the D122/D126/D132/D209 pointer- width-growth class cannot apply. Confirms the existing M-31 audit recorded in porting-notes.md. Hypotheses (b)/(c) (AI trigger flow / invisible puppet render) remain unconfirmed — still need the shipped GE_D160=1 trace driven live. D173 (third-person Bond puppet floats / spins in cutscenes): checked the two most D209-shaped candidates for a puppet Y-position aliasing bug and ruled out both. (1) PadRecord.stan is a ROM-serialized pointer field zeroed by tools_pc/d88_emit.py's emit_pad() with a "dead-on-load" comment; verified against the N64 source asset tables (UsetupdamZ.c) that every PadRecord literal initializes stan to 0 on N64 too, so the PC zeroing is byte-identical to ground truth, not a converter bug. (2) The anim-root-motion Y-accumulation chain was already exhaustively audited during D193/D209 (M-80/M-81) and the padding[]-union sweep that found D209/D210 explicitly cleared act_anim/act_dead as layout-stable. Root cause not found; the D209-style struct-aliasing pattern does not appear to be the shared explanation here, unlike the hope stated in the dispatch brief. Flagged next steps: a live Cradle capture, and locating the actual two-actor Cuba end-credits code path (not found this session). Added a generalizable porting-notes.md quirk: before assuming a zeroed ROM-serialized pointer-width tail slot is a converter miss, check the N64 source literal — some fields are legitimately always NULL on both platforms. Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG |
||
|
|
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.
|
||
|
|
8f0abae18f |
QoL: Game.NoHitFlash toggle (D232) (#58)
* QoL: Game.NoHitFlash toggle (D232, route-(b) in currentPlayerSetFadeColour) The community 'no damage flash' option. Every fade routes through currentPlayerSetFadeColour; the only non-black ones are the two damage paths (white g_DamageTypes envelope + red 150,0,0 death overlay), so zeroing the RGB there kills exactly the hit flash and leaves blackouts, the colour screen and the death fade untouched. Default 0 = original. Frame A/B verification in progress; finding entry to follow. * D232: add GE_NOHITFLASH_TRACE diagnostic to currentPlayerSetFadeColour Env-gated osSyncPrintf (pattern per GE_D160) logging r/g/b/frac/caller whenever a non-black fade reaches the gate. Used to resolve the frame-A/B anomaly from the previous session: zero trace fires during a headless no-input BUNKER1 run confirms the gate is inert absent real damage, and a controlled ini-toggled A/B (framediff.py, exact + tolerant modes) shows the gate's visual effect (99/100 frames match) is far smaller than the port's own run-to-run noise floor (0/100 match between two runs of the identical build) — the anomaly was D117 nondeterminism, not a real bug. Full writeup in docs/HANDOFF.md (local, gitignored). Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG * D232 fix: zero frac too, not just RGB, in the NoHitFlash gate User playtest found the white hit-flash was gone but a black fade-to-black still played on every hit -- the gate zeroed r/g/b but left frac (the overlay's alpha/opacity) untouched, so a fully-opaque BLACK rectangle drew at the same envelope the white one would have. Same visual intrusion, different colour, not what the toggle promises. Fix: zero frac too when the gate fires, so the overlay doesn't draw at all. This also drops the GE_NOHITFLASH_TRACE diagnostic added earlier this session to hunt the original (unrelated, since-resolved) headless frame-diff anomaly -- no longer needed now that the real bug is fixed and user-verified in a live playtest. User-verified: hit flash fully gone, no residual black flash, blackouts/ colour-screen/death-fade untouched. |
||
|
|
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 (
|
||
|
|
1a97895a37 |
docs(issue #6): resolve 3 of the drop-in-ROM plan's open questions (#57)
Research pass (local Qwen delegate, repo-mapper role, findings spot-checked
against the actual files):
- filelist.u.csv hard-coding in d43_emit.py/d69_emit.py is confirmed real
(converts PAL/JP ROMs with NTSC byte offsets); per-region filelist.e.csv/
.j.csv already exist and have the same shape, so the fix is narrow. Real
remaining unknown: whether modelFileHeader.inc.c's NS/NT differ cross-
region (needs an actual PAL/JP ROM to check).
- Native converter output path for Part B nailed down exactly:
data/pcmodels-<region>/{pcmodels.bin,manifest.csv} and
data/pccg-<region>/{pccg.bin,manifest.csv}, via pcmodels.c/pccg.c's
existing $S//$E//./ + country-byte region resolution.
- Part B's CMake codegen scope narrowed to one real artifact: a 512-entry
model-symbol -> (NS,NT) table from modelFileHeader.inc.c. Everything else
the emitters read is either runtime-readable from disk or a small
hand-transcribable constant.
|
||
|
|
4bfb64093e |
CLAUDE.md: dispatch preflight rule -- branch delegate output off main explicitly (#59)
This session's docs/issue-6-dropin-rom-plan PR (#57) got 'git branch <name> <sha>' where <sha> happened to be on top of an unrelated in-flight feature branch instead of main -- the resulting PR silently carried that branch's whole history (including code since reverted) into the diff. It only surfaced as a CI failure (a stale env-probe-index check), not from inspecting the PR diff, meaning it could easily have gone unnoticed. Adds an explicit rule to the Local Qwen dispatch section: branch off main/origin/main HEAD explicitly when landing delegate output, never off whatever the session happens to have checked out, and sanity-check with 'git log --oneline main..<branch>' before pushing. |
||
|
|
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. |
||
|
|
f800275247 |
fast3d/D217: key CI texture cache on palette content, not just TLUT address (#49)
GE assembles weapon and character model display lists in scratch arena RAM
and reissues gDPLoadTLUT from a repeated source address with different
palette *content* between materials and frames. The CI TextureCacheKey
keyed only on {texel addr, palette_addrs[0/1], palette_index, size_bytes}
with no content check, so gfx_texture_cache_lookup() (a plain map.find)
could return a stale HIT whose GL texture was decoded against a different
palette. Proven with an env-gated probe on -level_34: the same
{addr, paladdr} pair re-imported many times per frame with materially
different rdp.palette content, all taking HITs.
Fix: FNV-1a hash of the 512-byte rdp.palette, recomputed only in
gfx_dp_load_tlut() (rare vs draws -> no per-texel or per-draw cost), added
as a trailing field of the CI cache key. Non-CI keys carry 0. Cannot
produce a worse decode than the stale HIT it replaces; worst case is a few
extra texture uploads per frame (~2 measured on -level_34).
Partial for D217: this removes a real stale-palette-HIT class (the D217
finding's own "test a palette-content hash" next-step) but does NOT resolve
the visible green PP7 grip. That has an upstream cause - the grip's count=1
model TLUT is fed different source bytes per level (Bunker 0x0001 -> black,
Facility 0x1A00 -> green) for the same tile; fast3d faithfully stores what
it is given. See scratchpad D217-report.md. Full 21-level world-CI
regression sweep still owed before merge.
Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG
|
||
|
|
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. |
||
|
|
6c49c25083 |
Merge pull request #50 from jkdansereau/fix/d221-fileselect-hitbox
front/D221: file-select mouse hit-box — pass explicit {min,max} pairs (stack-layout dependency)
|
||
|
|
9d45c6aae6 | chore(D221): regen findings-index.csv (CI validate) | ||
|
|
4dd596a570 |
docs(D221): record human mouse-playtest verification (M-91)
Clicking directly over each visible file wallet selects it immediately across all slots and the Copy/Erase icons; no crash. Closes the last open item on PR #50. |
||
|
|
cc332d7287 |
merge main into fix/d221-fileselect-hitbox
Resolves the D219/D220 findings.md conflict: main had the M-90 D219 runtime-recheck update (from merged PR #48), this branch had the M-91 D220 analysis (its own second commit) -- kept both. |
||
|
|
972b7e69bf |
docs: README update - trim repeated framing, fix stale status claims, drop going-stale badges (#52)
* docs: step 2 of the docs/site cleanup - README rewrite + agentic-development.md numbers refresh README: trim the repeated AI-collaboration pitch to one mention with a link (was stated near-verbatim 3x in this file alone); drop the status/ tested_on/built_with badges (CI + license only - the others just go stale); cut the duplicate 'by the numbers' table and mermaid diagram (both live, and are current, in docs/dev/agentic-development.md); tighten the comparison-table Why-it-exists/Status cells; update the Documentation table for the site-config change in the prior PR. Fixes the contradictory status claims: audio (in-level music does not play yet, D77 -- was claimed both silent and working in different sections) and Linux (not actively tested since v0.1.0 -- was claimed both untested and level-swept in different sections). docs/dev/agentic-development.md: refreshed the 'By the numbers' table with git-verified figures (calendar range, commit count, bug count, ABI-edit file/line counts) -- the old numbers were from an earlier session and had drifted well behind main. Trimmed the italic pitch block under the H1 to one plain paragraph; left the timeline, milestone table, and effort-split estimate untouched (maintainer's own record, not independently re-derivable from git). Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG * docs: README fixup pass - trim bold/em-dash density, drop redundant WARNING callout Benchmarked against comparable projects (Perfect Dark PC port, the GoldenEye-Recomp competitor, ripgrep as a general engineering-README gold standard): this file was still running ~42 bold spans and 48 em dashes across ~2,600 words, well above what any of those use, and the alpha-status callout box duplicated the Status section's own opening paragraph. - Bold audit for real this time: kept proper nouns on first mention, table row labels, and one bullet-lead-phrase convention used consistently in three lists; cut everything that was just mid-sentence emphasis or a second bold of something already established (the ROM phrase, Windows/Linux, v0.1.0, a bolded URL). 42 spans -> 33. - Un-bolded and merged the duplicate audio/music caveat (was bolded once in Working and again in Not-yet-working for the same fact). - Dropped the alpha WARNING callout; folded its one load-bearing sentence into the Status section's existing opening paragraph instead of stating it twice in two different box styles. Kept the ROM-supply IMPORTANT callout - that one's legally load-bearing and not duplicated elsewhere. - Varied em-dash-as-everything-connector into periods/colons/semicolons/ parens where a flowing sentence called for it (not touched: the term-dash-description bulleted lists in Credits/How-it-works/Not-yet- working, which are a legitimate, consistent convention, not filler). 48 -> 33, and the worst offender (a double-em-dash-wrapped parenthetical in Background) is gone. - Reworded the intro's research-angle sentence away from "doubles as a research project" to plain prose. Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG |
||
|
|
4d45e78176 |
docs: Pages site cleanup step 1 - curated publish set + cross-link fixes (#51)
* docs: step 1 of the docs/site cleanup - whitelist Pages site config, fix cross-links to excluded docs, drop game-behavior-reference index bullet Publish only the curated set (index/building/internals/porting-notes/ dev-process/agentic-development); everything else under docs/ stays in the repo but out of the Pages build and sitemap. Cross-links from the published pages to excluded docs now point at GitHub blob URLs so they still resolve. No content rewrite yet -- steps 2-5 (README update, per- page descriptions, light copy passes, repo hygiene) remain. Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG * docs(dev-process): add §6 -- keep PR/git-history noise low Squash-merge single-purpose/docs PRs, batch same-session docs updates into one branch instead of one-PR-per-finding, keep unrelated housekeeping off active branches, declare stacked-PR dependencies, and triage the open-PR list every session -- close stale PRs (superseded by newer main content) rather than force-merging or endlessly rebasing them. Prompted by this session's PR #26: real conflicts against main because D191/D193/D149 were all fixed/closed since it was opened; closed rather than rebased. |
||
|
|
b577008543 |
Merge pull request #48 from jkdansereau/docs/d219-runtime-recheck
docs(D219): M-90 runtime re-check — D172's fix covers Dam's particle DLs |
||
|
|
fd0f6eb749 |
docs/D220: analyze muzzle-flash FovScale drift -- correct perspective behavior, not an unscaled-viewmodel bug
The flash nodes share the gun DL and the (scaled) per-frame projection, so flash-to-muzzle attachment is geometrically preserved; the whole camera- anchored viewmodel migrates toward screen center as FOV widens. Disposition: human call -- QoL viewmodel re-anchor if it bothers, not a fidelity bug. |
||
|
|
257e9c3fc1 |
front/D221: pass explicit {min,max} coord2d pairs to projectRectCornersTo2D in file select
interface_menu05_fileselect passed four separate f32 locals (&xmin, &ymin)
to a function that takes {min,max} coord2d pairs, relying on the N64
compiler laying each max adjacent to its min. The PC toolchain lays the
locals out in declaration order, so arg1->f[1] read ymax (as an X coord)
and arg2->f[1] read an unrelated stack float (sometimes NaN) -- the file-
select hit band collapsed to a ~14px strip at each wallet's bottom edge:
'click below the file to select it' (D221, M-87 user QA).
Pass explicit pairs under #ifdef PORT; identical values to what the N64
binary used, N64 call untouched. ABI/layout exception per AGENTS.md rule 2.
Verified headless: same cursor position + A press -- pre-fix never
selected folder 0, post-fix selects and advances (findings.md §H D221).
|
||
|
|
0cb38c6bdc |
docs(D219): M-90 runtime re-check — D172's fix demonstrably covers Dam's particle DLs
Scripted-fire repro (GE_INPUTSCRIPT, 64 Z-pulses) on -level_33 with GE_D172=1: 40/40 particle multitex tris sample SAMPLED1=tile1 (tmem=392), the correct RGBA fire tile (pre-fix behaviour was tile0/smoke); zero BUG/unknown-GBI/D146-abort lines; 0 purple/magenta pixels across 28 captured frames. Row can close on a human by-eye pass (folds into the D217 PR #47 playtest). |
||
|
|
f0b87ae0f5 |
Merge pull request #46 from jkdansereau/docs/d219-triage-note
docs(D219): triage note — likely already covered by D172's fix |
||
|
|
2ccc1af51e |
Merge pull request #45 from jkdansereau/probe/d217-texload-invariance
probe(D217): decode is proven correct — parked defect ② fix does NOT fix the visible symptom |
||
|
|
a6b26f430f |
docs(D219): M-89 local-Qwen triage note - likely already covered by D172's fix, needs a runtime re-check
Static analysis found no separate/unfixed code path -- the explosion DLs use the identical G_CC_INTERFERENCE/G_CC_MODULATEIA2 two-tile mechanism D172 already fixed in gfx_lod_tile_offset. Not yet runtime- confirmed (needs a scripted explosion repro on Dam with GE_D172=1). Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG |
||
|
|
3d3044a469 | docs(GE-ENV-PROBES): add GE_D217TEX row (validate CI gate) | ||
|
|
78f002e5cf |
probe+docs(D217): M-89 cont. - decode proven correct+identical; parked defect 2 fix does NOT fix this; new stale-arena-pointer theory
Added a GE_D217TEX env-gated probe (src/game/image.c, texInflateZlib) logging texnum/numcolours/palette[0..1] straight off the decode bitstream -- the most direct check of whether the SAME texture number decodes to the SAME bytes across levels. Result: texnum=1608 (the PP7 grip, confirmed by its pal0=0001 matching the previously-documented BUNKER1-correct value) decodes IDENTICALLY on both BUNKER1 and FACILITY. This disproves the 'different ROM bytes per level' theory and confirms the bug is strictly downstream of a provably correct decode. Then built and tested the parked fix/d217-model-texture-palette (f6fc0399, the CI-texture-cache content-hash fix, defect (2)) directly against the FACILITY repro -- still renders green. That fix does not close this row; defect (1) and (2) are confirmed separate bugs. New leading theory: a stale/dangling tex->data pointer into the texpool arena, re-resolved once and not re-checked at draw time, so a later texture decode reusing the same arena address on a texture-heavy level silently overwrites the grip's palette bytes. Documented as the concrete next probe for whoever picks this up. Probe left in tree, env-gated (GE_D217TEX), inert by default. tools_pc/verify.sh bunker1: PASS, no regression. Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG |
||
|
|
14bf7e59f2 | TEMP probe(D217): log decode-time palette[0] keyed by texnum (GE_D217TEX) | ||
|
|
80a3ccea5e |
Merge pull request #44 from jkdansereau/docs/d217-m89-pitch-hypothesis-refuted
docs(D217): M-89 — refute the gfx_dp_load_tlut pitch/width=1 hypothesis |