Commit Graph
83 Commits
Author SHA1 Message Date
James Dansereau 1d15e5695a v0.5.0 final pass: AI and Deck fixes (D565-D567), docs and site refresh
Fixes:
- D565: AI awareness no longer follows two player render settings. Fog
  distance is floored at 100 (below it the shortened clip hid guards from
  AI on-screen checks); the D466 gameplay fog cull is rescaled to the game's
  own FOV (identity at defaults).
- D566: Steam Deck Game Mode no longer shows the on-screen keyboard at
  launch (SDL text input stopped; screen-keyboard hint off).
- D567: opening PC Options from the front end with a pad no longer drags
  the game's crosshair to the top-left corner.

Docs and site:
- README News/Status/Features/controls brought up to date; the unpublished
  v0.4.1 work is folded into v0.5.0 everywhere; stats to v0.5.0.
- Release notes reordered: player-facing highlights first, finding labels
  and verification at the bottom.
- Site status rewritten; favicon is now the exe icon (make_icon.py writes
  docs/favicon.ico).
- ROADMAP: AI review results, update-check follow-up, stale rows closed.

Verified: Windows + Linux builds; Windows pixel gate Surface 1 PASS;
Deck build deployed for the maintainer's Game Mode checks.
2026-10-06 21:06:48 -04:00
James Dansereau 4b710728bf v0.5.0 playtest fold-in: fog and near-plane clip, crash fixes, polish (D540-D564)
Deck/Windows playtest fixes: fog rework (D540) and RSP-style CPU near-plane
clip with per-vertex N64 fog (D543), with fog recomputed on near-plane-clipped vertices as the RSP does,
so the haze around the player stays steady (D553); colour-combiner clamp for banded walls
(D548); rocket and thrown-item stack overflows (D545, D549); menu
double-trigger mitigation (D541); Dam ending first-shot camera swivel
restored by removing an old port look-at workaround (D552); settings standardisation (D546), live
All unlocked (D547), F10 mouse (D544); static window title and the project
mark (the in-game crosshair shape, site gold) as window/taskbar icon and
favicon (D550). Reference frames re-based
on Windows, Linux and Steam Deck (A/B-confirmed); capture_p7.sh runs on
Windows. Opt-in update check, off by default (D551: one HTTPS request to
GitHub per launch when enabled; winhttp.dll loaded only then; the tag CI
job checks the tag against GE007_VERSION). Release notes, ROADMAP,
internals and security docs updated; tree-hygiene pass.

Refs: D540 D541 D543 D544 D545 D546 D547 D548 D549 D550 D551 D552 D553 D554 D555 D556 D557 D558 D559 D560 D561 D562 D563 D564
2026-10-06 18:35:44 -04:00
James Dansereau d7b9371afb Docs: public copy, site presentation, release-gate review (D533, D534)
Public-copy language pass, homepage restructure and Documentation rename,
404 page, dead-anchor and permalink fixes, release-gate sign-off, and the
pre-release review fixes of the 2026-10-05 batch (probe-doc drift,
build-path hygiene, verify.sh and capture safety).

Refs: D533 D534
2026-10-06 18:35:44 -04:00
James Dansereau 452fa1025a Golden gate: Linux baselines, canonical save, gate hardening (D521-D532)
Linux reference-frame set for all 21 levels and the cross-platform golden
rule (D521); golden tooling made non-destructive (D524); the gate pins a
canonical in-tree save (D529) and the PRNG seed (D532); the capture tool's
per-level re-pin bug fixed and the Linux set re-captured under the
canonical save (D530, D531); no-arg verify.sh exits non-zero. README and
docs visual/consolidation passes; the 15 MB preview gif replaced by stills.

Refs: D521 D523 D524 D525 D526 D527 D528 D529 D530 D531 D532
2026-10-06 18:35:43 -04:00
James Dansereau aa5a1b81fe v0.5.0: settings UI, control styles, save import, documentation
PD-style F10 menu with six groups, checkbox/slider/dropdown controls (D504),
wording pass and real-unit sliders (D505-D509), overlay/FPS-counter window
scaling (D510, D512), small settings (D511), control scheme Ext/Original
(D513) with the game's own N64 styles (D516-D518), emulator-format save
import on first launch (D514), build arch tag (D515), converter argv quoting
and sidecar size check, one PC options UI (D519) with by-eye follow-ups,
antialiased overlay sliders (D520), README settings paths, ROADMAP trim,
D441 tier-1 all-level sweep (21/21 pass).

Refs: D503-D520 D349 D441
2026-10-06 18:35:43 -04:00
James Dansereau daa2522511 D489-D502: settings persistence, collision, A/B harnesses, D441 census
F10 pad slider persistence (D489), glass collision hull (D490), A/B input
harnesses (1964 virtual pad, GE_INPUTSCRIPT, live one-pad launcher), adjacent
global struct reads (D491), vibration percent, D441 complete pointer-width
census with high-arena tier-1 build, fix batches 1-5 and an ABI warning
ratchet, playtest verification of D493-D502.

Refs: D489 D490 D491 D492-D502 D441
2026-10-06 18:35:42 -04:00
James Dansereau 4b38c2566e D478-D490: crash log, session start, performance, fidelity, regions
Crash-log snprintf (D478), start-mp folder (D479), shader pre-warm (D480),
render worker thread (#92 low-end 45 to 60 fps, D481), lighter first-launch
defaults (D482), sniper crouch/zoom fixes (D483-D485, #136), Rareware text
TMEM swap (D75), D173 residual, turret/explosion fidelity (D486, #126),
21-level Windows golden rebase with a two-tier gate, PAL and JP convert/build/
boot (D258) with PAL frame height (D487) and osTvType (D488).

Refs: D478 D479 D480 D481 D482 D483 D484 D485 D486 D75 D173 D258 D487 D488 #136 #126
2026-10-06 18:35:42 -04:00
James Dansereau 00fdd94c8d 2-4 player split-screen (#99): D416-D419, D422, D428, D448, D450
Virtual seats, per-seat camera and input routing, aim modes, hot-plugged
controllers reopening via a coalesced rescan (D450, dolent's PR #122,
merged as the original commit). Rule-2
sign-off for D417.

Refs: #99 D416 D417 D418 D419 D422 D428 D448 D450 #122
2026-10-06 18:35:40 -04:00
James Dansereau 41d690f7ca 32-to-64-bit pointer-width fixes D451-D462; scaffolding removal batch 1
Tick-based auto-fire gate (D451), knife keyframe pointers (D454), font/text
slots (D456), memp arena start (D453), exe-address truncation census (D455),
global bank/RDRAM and background slots (D457 a/b), item-slot allocation
stride (D461), vtxstore table stride (D462). Mechanical, semantics-preserving
ABI/layout edits only. Diagnostic probes removed (batch 1).

Refs: D451 D453 D454 D455 D456 D457 D461 D462 D408 D294
2026-10-06 18:35:39 -04:00
James Dansereau ad3856892a v0.4.1 sync: build and CI fixes, settings presets D440-D447, outside PRs
build-pc.sh Windows temp-dir re-exec fixes; CI pin actions and no-cache ROM
handling; original-N64 and Deck presets; D440 preset labelling; D442 All
unlocked as a RAM/query-time override; D443 settings values (MSAA 16x,
Restart game); D444 front-end Previous/Next paging; D447 Original-aspect
pillarbox/letterbox. Outside contributions by dolent: PRs #120 and #121 (relabelled D445,
D446), merged as dolent's original commits.

Refs: D440 D441 D442 D443 D444 D445 D446 D447 D387 D283 #120 #121
2026-10-06 18:35:39 -04:00
James Dansereau 811e9a62cf Bug-fix pass for reported issues #114-#119 (D424-D434)
Recoil byte order, crouch-tank interactions, auto-fire cadence in ticks,
hipfire turret tracking, light-fixture Gfx index parsing, hit-type byte
reads. Rule-2 sign-offs recorded for D424 and D427. Documentation re-triaged
onto a single tracker (docs/ROADMAP.md).

Refs: D424 D425 D426 D427 D428 D430 D431 D432 D434 #114 #115 #116 #117 #118 #119
2026-10-06 18:35:38 -04:00
James Dansereau d267d2311e docs: reconcile GE-ENV-PROBES.md for v0.4.0 (31 new rows); fix env-drift checker to see envflag.h wrapper reads; drop README hero row 2026-09-28 10:25:31 -04:00
James Dansereau 4b7cae63f9 v0.4.0 final review: D404 pad-centred aim (N64-parity quadratic curve) + full campaign-playtest sign-off bookkeeping
- input.c: padDirectCompute() — pad twin of the mouse Centred aim; the natural
  pitch path routes AIMMODE_CENTRED through it, AIMMODE_N64 keeps classic
  stick-deflection behavior. Parity pass uses bondview2's verbatim N64
  quadratic curve (sign(v)*clamp(defl/70,+-1)^2 * fov/60 * 3.5 deg/poll), so
  full-stick Centred matches N64's top rate exactly.
- optionsoverlay: aim rows device-neutral (Aim style / Aim range).
- findings.md: D404 RESOLVED (user-validated on Steam Deck, both passes);
  21 sign-off status updates from the user's full campaign playtest
  (D175/D224/D235/D236/D240/D241/D245/D249/D281/D286/D287/D289/D291/D294/
  D303/D306/D307/D308/D311/D320/D322/D75/D387/D395/D396/D399/D400); stale
  options-wave rows closed (D346-D391 batch); D383/D394 kept PARTIAL with
  v0.5.0 scopes; D281 root cause -> v0.5.0; D258 -> NTSC-only for v0.4.0.
- README: Known issues = the true residual set (D318, D75 Rareware logo,
  AllUnlocked EXPERIMENTAL, NTSC-only); stale v0.3.0 clause removed.
- AGENTS.md: native-reexec guard status re-confirmed for 2026-09-28.
2026-09-28 01:02:09 -04:00
James Dansereau 461ea871c2 D344: orderly quit so exit never lands inside the GL driver; GE_QUITFRAME
Quit (quit event, window close, Alt+F4, from either SDL pump) used to
exit(0) immediately while the render thread could be mid-frame in the
driver: the likely trigger of the host's 0x119 bugchecks. Now quits are
requested; the render thread glFinish()es, unbinds the context and parks
at a frame boundary, and the host exits after (max 2 s). GE_QUITFRAME=<n>
gives the harness a clean self-quit; golden_gate.sh uses it.
2026-09-27 10:38:16 -04:00
James Dansereau d061ca3d12 D303: model converter copied 1/4 of each star muzzle flash's vertices
The op-22 DisplayListPrimaryRecord count is the number of star-flash
arms; dorottex (and PD's modelRenderNodeStarGunfire) read count*4
vertices. d43_emit/d43_convert sized the array as count vertices, so
3/4 of every muzzle star came from neighbouring sidecar bytes and drew
as long spikes (M16 "shooting up"). Size it 16*4*count. Emitter
round-trip checks all pass; M16 renders a clean star headless.

Also TEMP GE_STARTWEAPON (bondview_r.c) for weapon repros. Re-run
tools_pc/d43_emit.py to regenerate data/pcmodels-*.
2026-09-26 18:15:17 -04:00
James Dansereau 2a20a93318 D322: periodic 'garbled' bursts are the host audio device, not the port
A no-game SDL drain monitor (new tools_pc/sdl_drain_monitor.c, opens SDL
exactly like port/src/audio.c) shows the maintainer's Focusrite USB interface
draining at 88-95% for ~15 s every ~312 s, matching the in-game overflow
bursts' period. Level loads, combat, the SDL pull pattern and a
pacemaker-vs-vsync beat were ruled out. Remaining D322 items: the 8-voice SFX
cap (D207/D241) and #87's progressive decay. porting-notes: measure the host
device before chasing periodic audio glitches.
2026-09-25 19:15:22 -04:00
James Dansereau 7baf490020 tools_pc: golden_gate.sh — one-command per-patch golden gate
Pins data/ge007.ini to a defaults-only 640x480 window (restored on any exit),
captures Bunker 1 at 640-1120:240 and runs framediff.py against the golden
baseline. --level/--dump give a capture-only smoke run (exit 0 = frames
captured and no crash). Full framediff output goes to scratch/.
2026-09-25 16:46:03 -04:00
James Dansereau c728c42e81 golden: move bunker1 window to 640-1120:240 (frame 400 sat on the nondeterministic intro camera; 3 captures agree) 2026-09-23 13:32:20 -04:00
James Dansereau f89311e9e3 golden: re-baseline bunker1 at 400-880:240 (post-D248 60 fps); defaults-only ini pin
The 2026-09-09 set was captured at 30 fps (2 sim ticks/frame). Since D248
the port renders at 60 fps (1 tick/frame), so frames 200/320/440 landed in
the intro cutscene (nondeterministic camera, D117) and the golden failed on
every build, with or without D329. Moved the window to 400-880:240 (the same
sim ticks the original set sampled, now in settled gameplay); two independent
native-Windows captures agree 3/3.

verify.sh's ini pin rewrote only Width/Height, which kept personal
FovScale/DrawDistance/MSAA settings and failed the gate. It now writes a
defaults-only 640x480 ini (and pins/removes one when none exists).
2026-09-23 11:03:21 -04:00
James Dansereau 97c7e38845 D320: resolve sweep via forced repro — ai_19/ai_9 pins confirmed, ai_12 clean; watchdog generalized (table-driven + uncapped repeat rescue); D318 N64-manifestation corrected (unverified; 1964 deliberate-timing A/B armed)
Also commits the prior session's carried-uncommitted set: D294 room-pool
fix (sqrt + 2x cap, user-verified state), D324 stdlib-shim prototypes,
D323 explicit-LOD census + tools_pc tooling, GRAPHICS-BACKLOG row.
texdump*/ probe artifacts gitignored.
2026-09-22 22:30:32 -04:00
James Dansereau 425237f93f D236 (M-198): fix Surface 1/2 tree cards; root cause was a DETAIL tile binding
GE's texLoadFromGdl picks a handler per G_NOOP texture reference from w0 & 7.
TEXTURETYPE_LOD/MIPMAP (texHandleType0/Type2) issue ONE load block at TMEM 0,
so tiles 0..n are LOD levels of that one image. TEXTURETYPE_DETAIL
(texHandleType1, src/game/tex.c) issues TWO: the detail texture at TMEM 0 and
the real base image at TMEM texGetSizeInBytes(detail,0) on tiles 1+.

gfx_lod_tile_offset folded every G_TL_LOD draw to tile 0 (D107 -- correct for
the first two, wrong for DETAIL), so the port drew the detail texture and never
imported the base image at all. All four Surface 1 treeline cards
(tex1198-tex1201, RGBA5551 64x17 cut-outs) are DETAIL bindings sharing one
detail texture, tex2465: an opaque 32x32 IA8 noise tile. That is the same
texture D267/D276 measured at 1023/1024 texels opaque and called the mystery
"noise painter" -- hence 20+ passes chasing decode, alpha, mip and geometry
theories for what was a binding bug.

Fix (port layer only): under G_TL_LOD, if tile fi+1 differs from tile fi in
BOTH TMEM address and fmt/siz, sample tile 1. Asymmetric on purpose -- LOD
levels of one image always share a format, so a mip chain can never trip it,
and a detail pair that happened to share a format just keeps the old
behaviour. Config Video.DetailBaseTile, default on.

Verified: Surface 1 idle spawn goes from an opaque tiled noise wall to discrete
snow-covered pines with ground/snow unchanged; Surface 2 shows a discrete night
treeline; Depot renders clean; bunker1 golden framediff is bit-identical with
the fix on and off. Offline scan of every level's bg: DETAIL bindings exist
only in sevx (84), depo (39), sevb (4), sev (4), so every other level is
provably untouched. User play-test confirmed.

Also refuted and removed pass 25's fix direction: cut-out mip suppression
changed nothing, and its alpha census showed the wall is painted by a fully
opaque texture -- retiring the mip / 1-bit-alpha / XLU-smear branch. The
GE_D236P25 probe is gone with it.

tools_pc/gen_env_probes.py: stop counting a "GE_FOO[" log-line TAG in the doc
as a documented env var. GE_TEXDUMP_ADDR prints "GE_TEXA[...]", which is not
the name of any getenv(), so the check reported a permanent phantom GONE.

Generalised as docs/porting-notes.md D12; full trail in findings.md D236 pass
26. Carries the pending D75/M-197 sidecar-converter fix and the env-gated
D236/D75 probes, which the doc gates require to land alongside their rows.
2026-09-21 22:48:10 -04:00
James Dansereau b3b0ff74db v0.3.0 release review: watchdog re-arm, rule-2 sign-offs, GPL source removal, repo hygiene
Per the 2026-09-19 adversarial pre-ship review (findings.md M-192):

- d318watchdog.c: one-shot per OCCURRENCE, not per process -- re-arm after
  600 ticks of signature absence so a Facility replay in the same session
  (AllUnlocked) is still covered.
- model.c: RULE-2-SIGNOFF records for the retained M-183/M-185 and D311
  defensive clamps (user-approved retention as the stability-maximizing
  option; stale 'Threshold: 50.0' comment corrected to 10.0f; both marked
  post-release removal candidates pending full cutscene re-verification).
- reference/mouse-injector/: removed from the public repo (GPLv2 source +
  prebuilt discord-rpc.lib in an MIT project; never compiled here).
  Provenance stub README remains; path gitignored for local re-vendoring.
  AGENTS.md / GEPD-INPUT-PLAN.md / optionsoverlay.c refs updated.
- scratch/: untracked + gitignored (19 session logs/probe sources were
  committed in the release window).
- findings-index.csv: D317 (and 5 other stale rows) now CLOSED -- added
  CLOSED to gen_findings_index.py's verdict vocabulary.
- docs: D310 perf gauge (no change warranted), GE_DEBUG_UNLOCKALL noted in
  security-and-fidelity-status.md, release notes gain the missing D318
  watchdog bullet + FOV-clamp clarification.

Build verified: ./build-pc.sh ntsc-final clean; gen_findings_index.py
--check and gen_env_probes.py --check pass.
2026-09-20 10:51:12 -04:00
James Dansereau f1e2c7cbb8 D297 M-150: eeprom-shim migration of pre-D284 save CRCs
v0.3.0 silently wiped all v0.2.x saves on first launch: stored slot CRCs
are fingerprints of the PRNG that wrote them, and D284 changed the PRNG.
One-time migration in the eeprom shim's read path (block 4 only): a slot
that fails the current-CRC check but validates under the legacy PRNG is
re-stamped with current-PRNG checksums before fileValidateSaves sees it,
then persisted once. Runs before geEepromPatchAllCheats so cheat patches
never reach the file. Corrupt/fresh slots untouched (game's own wipe path).

- port/src/legacycrc.c: isolated file carrying the deliberately-wrong
  pre-D284 PRNG (verbatim from 0de99b4d^, reviewer-verified bit-exact) +
  lockstep copy of the crc.c loop; header says when it may be deleted.
- tools_pc/d295_crc.py: reference CRC model, bit-exact vs compiled C for
  both PRNGs (used to verify migrated files).

Verified: user's #87 artifact migrates 5/5 slots with progress intact and
is idempotent on relaunch; native post-fix saves, corrupt slots, and fresh
eeps all behave per design; 21/21 sweep PASS.
2026-09-16 15:28:37 -04:00
James Dansereau 436484a926 docs: final v0.2.0 pre-tag pass
- Drop-in ROM wording everywhere (README quick start, Pages landing,
  release notes, bundled README): first launch auto-generates the
  derived assets via the frozen converter; no Python/toolchain needed.
  PAL/JP marked not-yet-supported with issue #85 pointers.
- building.md: Linux build is CI-compiled and ships in the bundle
  (was 'never built or run on Linux'); bundle note now describes the
  frozen ge007-convert auto-spawn; PAL/JP sidecar caveat (D258).
- Status rewording across README/Pages/release notes: drop the
  'first open-source port to our knowledge' claim; cutscene glitch
  description aligned to 'mostly with James Bond'; index.md status
  no longer contradicts the open D255 Deck crash.
- Em-dash cleanup across all GitHub Pages docs (index, building,
  internals, porting-notes, dev-process, agentic-development,
  release notes); internal dev logs untouched.
2026-09-13 23:51:27 -04:00
James Dansereau b2d5551ece Drop-in ROM: first-run sidecar conversion, no Python (issue #6) (#84)
* Drop-in ROM: first-run sidecar conversion, no Python (issue #6, Part A + interim)

The release bundle now turns a dropped-in NTSC-U ROM into a playable game
with zero tooling: when data/pcmodels-* / data/pccg-* are missing at boot,
the engine spawns the frozen prepare-assets converter shipped in the bundle
(prepare-assets/ge007-convert[.exe], PyInstaller --onefile), waits for it,
and re-checks before continuing (port/src/romconvert.c, called from
romdataInit after ROM validation). Failure aborts boot with a message
instead of the later D179-class crash.

- prepare-assets.py: runs in-process via runpy when frozen (no user Python
  to exec); NTSC-U-only gate with a clear message for PAL/JP ROMs; d88's
  sibling import needs an explicit sys.path entry (run_path does not set
  one). Verified: frozen exe output byte-identical to the unfrozen path and
  to the shipped sidecars.
- CI (linux + windows): PyInstaller freeze step before bundling
  (ensurepip / --break-system-packages for MSYS2's pip-less python).
- bundle scripts: copy ge007-convert into prepare-assets/ (hard-fail if not
  frozen), size guard 60 -> 120 MB.
- Release notes: US-only drop-in instructions, no Python required.
- D258: PAL/JP filelist naming defects documented; multi-region deferred to
  the Part B follow-up.

* CI: install pip via pacman on Windows (MSYS2 ensurepip is disabled); regen findings index for D258
2026-09-13 23:17:29 -04:00
James Dansereau 060d5ac86d Docs: README + Pages landing overhaul for v0.2.0; Deck settings docs.
- README: manual Contents TOC dropped (GitHub auto-generates); Download
  table moved to the top with Quick start under it; Status compressed to
  a Working paragraph + tight known-issue list (D-numbers live in the
  release notes / finding log now); How-it-works tightened.
- Pages (docs/index.md): demo gif is the hero; download table up top;
  new How-it-works and Honest-status sections; legal footer; the 19-shot
  playtest gallery unchanged.
- Options overlay is now gamepad-driven: README Steam Deck section, the
  bundle README (new @DECK@ block on Linux, controller column added to
  the controls table), and the release-notes template all document
  Select / D-pad / A / B / Start instead of a Steam key-mapping workaround.
2026-09-13 18:40:57 -04:00
James Dansereau 3ed75e72f5 bundle-linux.sh: match ldd's absolute paths in the SDL2 check (CI red fix)
The $ORIGIN verification compared ldd's absolute resolved path against the
relative $OUT, so it always failed even though the bundled libSDL2 was the
copy being resolved (CI 34778363397 showed exactly that). Match both forms;
the remaining-deps listing now filters by bundle dir name for the same
reason.
2026-09-13 15:49:37 -04:00
James Dansereau 4065a0007d Bundle SDL2 in the Linux tarball (turnkey Steam Deck sideload)
bundle-linux.sh now copies the exact libSDL2 the exe was linked against next
to it and sets the rpath to $ORIGIN via patchelf, with an ldd assertion that
the bundled copy is the one resolved. zlib/libGL remain system deps
(preinstalled on desktop distros and SteamOS). CI installs patchelf; the
generated in-bundle README gains a Steam Deck sideload note and the SDL2
zlib-license copyright file is carried when the distro ships one.

Also refreshes the release-facing docs that ship with / describe the cut:
  - .github/release-notes.md: v0.1.0-alpha body -> <version> pre-release body
    (what's new + known issues incl. D243/D230/D245/D246/D197/D75, the D251
    4K ghost row, and 'not yet verified on real Deck hardware')
  - tools_pc/dist/README.md.in: alpha claims (no audio / black sky / US-only)
    -> pre-release status; ROM table now lists all three regions (the asset
    step auto-detects by SHA-1)
2026-09-13 15:39:22 -04:00
James Dansereau 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.
2026-09-13 09:33:36 -04:00
James Dansereau 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 a976b2589c.

* docs(D244): log self-hosted-runner link failure — undefined reference to objectiveGetStatus_WEAK

First real build attempt on the reinstalled corpyvt6agent01 box (run
34707798707) fails at link with an unresolved #pragma weak alias. A local
repro of the identical alias pattern links fine on current MSYS2
(gcc 16.2.0/binutils 2.47); the box reports gcc 16.1.0 — likely a stale
toolchain, consistent with rollout step 2's earlier package-hygiene gap.

Claude-Session: https://claude.ai/code/session_01CUFSnpgrAyQSDVHoQQ6Sx3

* fix(verify.sh): surface run.log tail on NO-FRAMES instead of silently discarding it

sweep mode's per-level run.log (the game process's own stdout/stderr, where
an SDL/GL init failure would show up) was captured into a mktemp capdir but
unconditionally rm -rf'd with no diagnostic ever printed for the NO-FRAMES
(no-crash) case -- every level failed this way on the self-hosted runner's
first real sweep attempt with zero clue why.

Claude-Session: https://claude.ai/code/session_01CUFSnpgrAyQSDVHoQQ6Sx3

* fix(selfhosted.yml): stop deleting the ROM seed dir -- cache save silently fails on this box

actions/cache/save has been failing to persist the ROM cache entry on
corpyvt6agent01 (WARNING annotation, tar.exe exit code 2) but doesn't fail
the step -- by design, a cache-save failure is non-fatal, so the workflow
carried on and the now-unconditional "Delete one-time seed dir" step wiped
the only copy of the ROM every single run. Every dispatch since has failed
at "Seed ROM cache" with an empty seed dir and an empty cache -- exactly
the user-reported "it wipes out the ROM data each run".

Fix: don't delete the seed dir. It's cheap disk (three files, a few hundred
MB) and the existing restore-then-seed-if-missing logic already treats it as
the fallback source whenever the cache comes back empty, so leaving it in
place is safe and makes the workflow tolerant of the cache-save bug until
that's root-caused separately.

Claude-Session: https://claude.ai/code/session_01CUFSnpgrAyQSDVHoQQ6Sx3

* fix(verify.sh): sweep --json never actually produced a verdict array; fix NO-FRAMES diagnostic clipping it further

Two bugs, found together while chasing the self-hosted runner's first real
sweep attempt:

1. sweep mode's `out=$(verify_level ...); echo "$out" | head -1` always
   discarded emit_verdict's second (--json) output line -- RESULTS_JSON was
   declared for assembling a verdict array but never populated or printed,
   so `sweep --json` has never produced anything but `[]` for the
   workflow's "Extract JSON verdict" step. Now builds and prints a real
   `[{...}, {...}]` array before the RC exit.

2. My own D244-session addition (printing a NO-FRAMES level's run.log tail)
   wrote to stdout, which the same `out=$(...)/head -1` capture turned into
   the *new* first line, clipping the real verdict summary. Moved to stderr,
   which bypasses that capture.

Claude-Session: https://claude.ai/code/session_01CUFSnpgrAyQSDVHoQQ6Sx3

* docs: regenerate findings-index.csv for D244

Claude-Session: https://claude.ai/code/session_01CUFSnpgrAyQSDVHoQQ6Sx3
2026-09-12 15:33:07 -04:00
James Dansereau 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.
2026-09-12 09:33:54 -04:00
James Dansereau 14adbd3cad tools_pc/verify.sh: kill each level as soon as its last GE_PCDUMP frame lands
run_one always idled out the full watchdog (45s/level sweep, 90s/level
single) even though the game only needs to render its intro pan +
spawn-in once -- everything after the last requested frame was wasted
wall-clock. Poll for the last frame's PPM (or a crash log) every 0.2s and
kill immediately; the watchdog stays as the hang/load-flake safety net
for a level that never gets there. Also trims the pre-launch settle
1s -> 0.3s.

Falls back to the old timeout-wrapped launch if the --dump spec doesn't
parse as lo-hi[:step] integers.

Measured: bunker1 (9 frames, 80-400:40) 45s -> 18s; dam (3 frames, default
200-440:120) 90s -> 17s. A full 21-level sweep should drop from ~16 min
toward ~6 min.

Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG
2026-09-10 23:05:22 -04:00
James Dansereau 40e875bcbb tools_pc/golden: re-baseline bunker1 frames for D215 viewmodel
D215 (#37, merged 73768a03) un-gated the 1P weapon-model DL nodes, so the
PP7 + hand now render in the -level_09 intro-pan capture window. The
committed golden predated that, making `verify.sh bunker1` report
REGRESSION (worst_cell ~28) on every run.

Captured on main @ 3fa1ba53, clean default ini pinned to 640x480,
GE_PCDUMP=200-440:120. Gun eyeball-confirmed visible + correctly posed +
clean (no D217 garble in headless capture). Post-rebaseline
`verify.sh bunker1` = PASS worst_cell 12.3.

Claude-Session: https://claude.ai/code/session_01U7gyYC3pcatXHhD1kPCgsR
2026-09-09 23:20:05 -04:00
James Dansereau 4ceb917ad8 port(audio): D202 M-67-M-69 runtime probes — GE_VOICEDUMP, [VOICEMAP], timestamped [WIRE]/[EVT]/[STOP-EVT]
Env-gated diagnostics for the D202 per-voice sample-correctness work
(M-67/M-68/M-69), all inert unless the env var is set:

- GE_VOICEDUMP (mixer.c aEnvMixerImpl): per-voice resampled mono stream
  to voicedump.raw, records [stateAddr][nSamples][us][s16 x n]. NB the
  us stamp is mix time — records render ahead, samples END at the stamp.
- [VOICEMAP] (synthesizer.c, under GE_AUDIOTRACE): full PVoice ->
  decoder/resampler/env-state address map at synth init, tying voicedump
  slot addresses to WIRE filter addresses.
- [WIRE] (load.c) now carries a µs timestamp; sndPlaySfx trace gains
  t=µs + wavetable pointer.
- [EVT]/[STOP-EVT] (snd.c): every ALSndpEvent the player processes, plus
  the release ramp a STOP/DEACTIVATE computes (deltaUs).
- audiodebug.ps1: -VoiceDump switch.

With scratchpad/exact_match.py these prove every allocated voice plays
the exact ROM sample at the exact ROM pitch (286/320 CC >= 0.85 on the
M-69 full-corpus run; remaining failures are dropped allocations + wire
attribution, not corruption). Remove once D202 closes.
2026-09-06 08:21:14 -04:00
James Dansereau 9b31f864d3 tools(pc): audiodebug.ps1 audio-probe driver; gitignore run artifacts
audiodebug.ps1 drives the D202/D204 audio probes (health monitor,
GE_AUDIOTRACE/GE_AUDIODUMP, soundIndex histogram, -AB before/after runs —
how D204 was measured); companion to debug.ps1 (crashes). README table row
added. .gitignore: scratchpad/ + root-level audiodump/audiotrace/mixertrace
artifacts.
2026-09-05 19:07:43 -04:00
James Dansereau 884cfdb9b7 tools(verify): add verify.sh + crash_brief.py (speed-ups plan Step 2, 9)
verify.sh wraps the manual render-verify recipe (build-if-stale, 640x480
ini pin/restore, headless GE_PCDUMP capture, pixcount, framediff, crash
symbolication) into one command with a machine-readable PASS/CRASH/
NO-FRAMES/REGRESSION verdict + --json, for a single level or a sweep
subset. Live-tested on Windows: single-level and subset-sweep runs both
produce correct verdicts and leave the tree clean.

crash_brief.py turns a ge007.crash.log into a symbolicated, dispatch-ready
investigation brief: greps porting-notes.md/findings-index.csv for
sections covering the crashing function/file, and flags known
parked-crash signatures (D188 va_list, D189 stack overrun) so agents stop
re-investigating them. verify.sh calls it automatically on a CRASH
verdict. Live-tested against a synthetic-but-real crash log built from an
actual built symbol; caught and fixed 3 bugs before trusting it (addr2line
unresolvable under native Windows Python's PATH handling, a substring
keyword-match false-positive, a Windows drive-letter path-split bug).

Claude-Session: https://claude.ai/code/session_01P3WrUHAFQZUuhrHhiJgTUr
2026-09-04 22:09:58 -04:00
James Dansereau 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.
2026-09-04 19:21:35 -04:00
James Dansereau bde5e0bdfa docs(release): drop fixed items from the alpha known-issues list (#21)
The HUD weapon/ammo-type icon (D187, PR #19) and blank briefing objective
text (D178, cd1ed574) are both fixed and on main. Remove them from the
release-notes and bundled-README known-issues blurbs so a v0.1.0 tag ships
an accurate list. Black sky and the front-end 3D models (Nintendo logo,
MISSION COMPLETE / mode-select) are still open and stay listed.
2026-09-03 16:37:10 -04:00
James Dansereau ed6e677d06 build(release): fall back to 7z / Compress-Archive when zip is absent (#20)
A stock MSYS2 MINGW64 shell ships neither `zip` nor `unzip`, so
`tools_pc/bundle-win.sh` failed at the packaging step for a local release
build (the M-45 handoff's documented v0.1.0 cut path). CI is unaffected
(it `pacman -S zip unzip`, so the `zip` branch is still taken).

Fallback order: zip -> 7z on PATH -> "C:\Program Files\7-Zip\7z.exe" ->
powershell Compress-Archive. Verified end-to-end: bundle builds, unpacks
to a clean dir, prepare-assets runs, ge007.x86_64.exe boots -level_09 to
frame 2100+ crash-free.
2026-09-03 16:36:42 -04:00
James Dansereau 174233f89f ci: mark bundle-linux.sh executable
Committed without the +x bit from a Windows checkout -> CI hit
"Permission denied" (exit 126) invoking tools_pc/bundle-linux.sh.

Claude-Session: https://claude.ai/code/session_01V4pK3pT83YnPsBp3Ey4RP2
2026-09-03 11:50:29 -04:00
James Dansereau a0611727e9 release: add Linux x86-64 bundle + dual-platform release notes
- tools_pc/bundle-linux.sh: package a Linux build (exe + README + licenses +
  prepare-assets) as a .tar.gz. Mirrors bundle-win.sh's ROM / size guards and
  prepare-assets assembly. No shared libs bundled — the generated README tells
  the user to install SDL2 / zlib / libGL from their distro.
- tools_pc/dist/README.md.in: made OS-neutral via @PLATFORM@ / @EXE@ / @DEPS@ /
  @LICENSE_EXTRA@ tokens; refreshed the known-issues list (dropped the stale
  "ladders don't work" line — D177 fixed M-36; added black sky + missing HUD
  weapon icon).
- .github/workflows/ci.yml: linux-build now packages + uploads the Linux
  bundle; the release job attaches both platforms to the draft pre-release.
- .github/release-notes.md: rewritten for the two-platform NTSC-U alpha with
  the prepare-assets step and current known issues.

Alpha scope: NTSC-U only, one-time prepare-assets step (Part A launcher and
Part B native converter from release-dropin-rom-plan.md remain follow-ups).

Claude-Session: https://claude.ai/code/session_01V4pK3pT83YnPsBp3Ey4RP2
2026-09-03 10:54:06 -04:00
James Dansereau 205004242c fix(release): prepare-assets must also run d88_emit.py (per-level stage setup)
d88_emit.py appends the 21 Usetup*Z stage-setup files (object placement, AI
scripts, pads/nav) to the same data/pccg-<region>/ sidecar d69 produces.
Without it the port has no setup data for any level and segfaults on load
before frame 1 (this was mis-filed as D185 'd69 regression' — it was a
missing pipeline step). Vendor d88_emit.py + its one local import
d88_propdefs.py; run d43 -> d69 -> d88 --regen.

Verified: fresh d43+d69+d88 from an assembled bundle produces pccg.bin
(3604378 B, 74 rows) and pcmodels.bin byte-identical to the known-good
sidecars; -level_09 and -level_20 boot crash-free against them.
2026-09-02 20:59:49 -04:00
James Dansereau dfab1876e4 feat(release): self-contained asset-prep tool for the alpha bundle
The Windows bundle from bundle-win.sh / CI was unrunnable on its own (D179):
the port loads two ROM-derived directories (data/pcmodels-<region>/ and
data/pccg-<region>/) that are gitignored and cannot be shipped.

Add tools_pc/dist/prepare-assets/prepare-assets.py — a stdlib-only wrapper
(no compiler, no N64 toolchain, no network) that:
  - auto-detects the ROM near the bundle, verifies SHA-1 -> region
    (NTSC-U / PAL / NTSC-J), rejects unrecognised / byte-swapped dumps
  - runs d43_emit.py + d69_emit.py against a vendored input tree
  - writes data/pcmodels-<region>/ and data/pccg-<region>/ next to the exe

bundle-win.sh assembles prepare-assets/ fresh at package time (emit scripts
from tools_pc/, filelist.u.csv + file_resource_table.inc.c + the 512
modelFileHeader.inc.c files as vendored inputs) so it can never drift from
the scripts. Vendored inputs are decomp layout/symbol metadata, not game
assets. bundled README gets a one-time "prepare-assets" setup step.

Verified end-to-end: from a bundle containing only exe + DLLs + a US ROM,
prepare-assets.py produces all four output files byte-identical to
d43_emit.py / d69_emit.py run from the repo root. Bundle 14.1 MB (< 60 MB
guard). Error paths (no ROM / unknown SHA-1) tested.

Resolves BACKLOG "Alpha release — asset-prep tool" steps 1-3; step 4
(clean-machine smoke test with the game actually running) still owed.
2026-09-02 20:43:55 -04:00
James Dansereau def0e0ac56 release: Windows alpha bundle pipeline
- tools_pc/bundle-win.sh: package a Windows build for distribution — exe +
  MinGW/SDL2/zlib runtime DLLs + README + license texts, into
  dist/goldeneye-pc-port-<ver>-win64/{,.zip,.zip.sha256}. Hard-fails if a
  ROM image or an oversized blob lands in the bundle; verifies the DLL
  closure with ldd.
- tools_pc/dist/README.md.in: the bundled README template (ROM-supply
  instructions, run steps, controls, legal), @VERSION@-substituted.
- CI: build tag trigger (v*); windows-build now packages the bundle every
  run and uploads it; new release job (tags only, contents: write scoped
  to it) attaches the bundle to a DRAFT pre-release for a human to
  smoke-test and publish.
- README: Download section + Legal reworded to cover pre-built binaries
  (engine only, no game data, permissive runtime libs bundled with their
  licenses).
- .gitignore: /dist/ (root bundle output; tools_pc/dist/ template stays
  tracked).

Cutting a release: tag vX.Y.Z-alpha, push; CI builds + drafts the
pre-release; smoke-test with a ROM, then Publish.

Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
2026-09-02 00:44:54 -04:00
James Dansereau 5baa53eb61 tools_pc/debug.ps1: fix DLL-not-found on launch from a plain shell
The exe links MSYS2 mingw64 DLLs (SDL2, libgcc, libstdc++, libwinpthread,
zlib). Run from a normal PowerShell those aren't on PATH, so gdb reported
"During startup program exited with code 0xc0000135" and nothing ran.
Prepend the mingw64 bin dir (derived from -Gdb) to PATH before launching.

Also relax ErrorActionPreference around the gdb call so its stderr
progress lines don't raise NativeCommandError noise mid-run.

Claude-Session: https://claude.ai/code/session_01UpTM1AcBRJuTetoZNcCD5V
2026-09-01 20:28:13 -04:00
James Dansereau 142e9c8f26 D168 verified; goldens regenerated; D75 shrunk; D169 marked fixed
Built the migrated tree (MSYS2 mingw64 + ROM + sidecar regen) and ran
-level_09 with GE_PCDUMP: HUD text and the exit arrow now read
right-side-up. D114/D116 confirmed as capture artifacts.

- tools_pc/golden/*.png regenerated from a fresh correctly-oriented run
  (replaces the stopgap in-place flip); stray v*.png removed
- D75 shrunk: with upright captures the gun-barrel Bond RENDERS fine
  (walk + fire) -- the "absent" reports were the flipped capture. The
  Nintendo logo is genuinely broken (renders as 2 white blobs shifted
  left -- a real logoinst transform bug, not a flip). Cast roll not
  re-captured.
- D169 entry -> FIXED with the build/verify note

Claude-Session: https://claude.ai/code/session_01UpTM1AcBRJuTetoZNcCD5V
2026-09-01 20:22:32 -04:00
James Dansereau fa54f8fb90 docs: add operating manual, fix cross-references after doc restructure
- Commit CLAUDE.md + AGENTS.md so a fresh clone has the operating manual and
  the ABI/no-game-logic rules (trimmed of stale phase status; the personal
  pd_port path removed; points at the split docs).
- Sweep every stale doc reference across the tree after the restructure:
  PCPortResearch.md -> internals.md / dev/findings.md,
  PORT-LEARNINGS.md -> porting-notes.md,
  AGENT-WORKFLOW.md -> dev-process.md,
  docs/{GRAPHICS-BACKLOG,HANDOFF-ARCHIVE,GE-ENV-PROBES,LEVEL-*,TEXTURE-GLITCH-ANALYSIS,AUDIT-M6-*,AUDIO-PLAN}.md -> docs/dev/.
- Drop the superseded docs/PLAN-linear-level-sweep.md.
- docs/HANDOFF.md + docs/BACKLOG.md stay local-only (rolling working files);
  CLAUDE.md now handles their absence in a fresh clone.
2026-09-01 20:01:09 -04:00
James Dansereau 203549f57e D168: fix vertically-flipped GE_PCDUMP captures; close D114/D116
gfx_opengl_dump_bound_fbo wrote glReadPixels output (GL bottom-left
origin) straight into a top-row-first P6 PPM, so every GE_PCDUMP / F12
capture was upside-down. This is the entire source of the D114/D116
"HUD/text X-mirror" - four sessions chasing a fast3d mirror that never
existed, misreading inverted screenshots of asymmetric content. The
developer confirms the game renders correctly on real hardware.

- writer now emits rows top-to-bottom
- tools_pc/golden/*.png flipped in place to match + golden/README.md
- D114/D116 reclassified as capture-orientation artifacts (not deleted);
  D75 "logo mispositioned" / RC1 "photo 180 deg" flagged for re-judging
- annotations in findings.md, GRAPHICS-BACKLOG.md,
  TEXTURE-GLITCH-ANALYSIS.md, porting-notes.md D2, GE-ENV-PROBES.md

Not runtime-verified (migrated tree has no ROM/toolchain) - needs a
fresh capture to confirm text reads normally.

Claude-Session: https://claude.ai/code/session_01UpTM1AcBRJuTetoZNcCD5V
2026-09-01 19:44:39 -04:00
James Dansereau 52539d10de Prepare for public release
- README, LICENSE (MIT for the port layer), NOTICE, CONTRIBUTING.
- Restructure docs: internals.md, porting-notes.md, dev-process.md +
  dev/agentic-development.md (the research write-up), dev/ (raw record).
- Replace decomp status-badge CI with a PC-port validate workflow.
- Remove session/agent scaffolding and stale planning docs from the tree.
- Add .gitattributes (normalize line endings to LF); add screenshots.

The pre-2026-08 history is the GoldenEye 007 decompilation by Larry Ficken
(KholdFuzion) and contributors, preserved unchanged. The PC-port work from
2026-08-17 on is mine.
2026-09-01 19:28:59 -04:00
James Dansereau 1bc4928a55 d43_emit: validate PointUsage[] round-trip + index-range + non-zero (D120)
Confirms the D120 fix converter-side: 1469 PointUsage regions / 70177
s16 entries across the model set (every guard/character has several) now
byteswap-round-trip, every index < numVertices, and no region is
all-zero (the pre-M-30 bug). Guards against a future emit-gap
regression.
2026-08-31 11:13:53 -04:00