- tools_pc/bundle-mac.sh: a signed, double-clickable GoldenEye.app. Homebrew's
libstdc++/libgcc_s and SDL2 are not system libraries, so they are bundled
and rewritten to @executable_path/@loader_path. Note sdl2-compat (Homebrew's
`sdl2`) finds its SDL3 backend through a RELATIVE rpath baked into the dylib,
so SDL3 ships too and that rpath is repointed — copy it without that and
sdl2-compat raises a modal error dialog from its initialiser and the app
hangs before main(). prepare-assets/ goes in Contents/Resources/: codesign
treats a directory under Contents/MacOS/ as nested code and fails the bundle.
- tools_pc/level_sweep_mac.sh: per-level crash-log sweep, symbolicated with
atos, with a rendered-pixel check so "runs but renders nothing" is caught.
- D299: sndPlaySfx() rejected EVERY sound on arm64 — all in-level SFX were
silent, while music played (separate player). Its guard predated the shifted
window: `sp >= 0x400000000ULL` (16 GiB) is correct on x86_64 (DRAM at
0x70000000) and rejects everything on arm64 (DRAM at ~0x1000_707b_5740).
Fixed with a portAddrIsInWindow() helper rather than absolute bounds.
- D300: cleanupObjects() did `u32 *obj = (u32)g_CurrentSetup.propDefs`,
truncating a host pointer to 32 bits. Crashed at stage unload — only
reachable once a level actually ends.
Verified: 21/21 on both the no-input and full-control sweeps; SFX voices
allocate on every level.
Per the user's release decision, keep the toggle (default OFF) but make it
impossible to miss in-game: the row is now labelled 'All unlocked
(EXPERIMENTAL)' and its ON value renders 'ON - UNSAFE' in both the F10
overlay and the front PC Options screen (both read the shared row table).
ON value now bright-highlights like a normal On. Port-only, display-only
(config still stores 0/1); a safe architecture is v0.5.0 work.
Housekeeping: track docs/dev/M7-ALLUNLOCKED-PLAN.md (records the decision);
gitignore the stray repo-root Python/ install; refresh the D387 status.
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.
- GE-ENV-PROBES.md reconciled (18 new vars documented, GE_D243X3 tombstoned
after the M-170 permanent fix removed it) — CI gen_env_probes.py --check passes
- release notes gif alt text: ~32 s -> ~12 s (matches README/index captions)
- agentic-development.md stats regenerated for the month-one milestone
- docs/index.md status bumped to v0.3.0, honest-status synced with README
- .gitignore: local/ working-junk dir
All six TCRF article bodies were extracted (backlog/cut-content/articles/,
gitignored) and cross-referenced against src/ + assets/obseg/. Full verified
findings with file:line refs land in backlog/cut-content/RESEARCH.md.
Verified 1:1: the 11 unused cheats (CHEAT_ID enum vs cheat.c grant table),
music track index (MUSIC_TRACKS enum vs Notes-page GS index), MP character
table (~40 entries, 1.0446 eye height), all unused text strings (LmiscE/LgunE/
LpropobjE/LtitleE), build dates (compiletime.c), Spectrum emulator plumbing
(front.c MENU_SPECTRUM_EMU — only the transition is missing).
Corrections to TCRF/first pass: bg_dest is shipped Frigate geometry (not an
early version); cryp/dish are shipped Egypt/Temple; Library/Stack share
Basement's bg_ame; 28 of 29 unused items exist in the object table.
Tracked doc updated to point at the research file; backlog/ gitignored.
* 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
* README: 32 s gameplay preview montage (mp4) + preview-video runbook
* Prune: drop superseded goldeneye-demo.gif (replaced by gh-preview montage)
* Docs: v0.2.0 finalization pass — final wording, aligned known issues, Deck-verified line, social preview card
- README + pages site: 'v0.2.0 (pre-release)' -> v0.2.0 throughout; the
stale 'not yet verified on real Deck hardware' line is replaced with the
playtest fact (D253 fixed, D255 still open).
- Known-issues lists in README + site now match the release notes one-for-one
(adds the F10 bottom-row duplicate D251 and the intermittent Deck SIGSEGV
D255; D243/D230/D252/D245/D246/D75 carry their finding labels).
- Working list mentions AllUnlocked default-ON (D257) with the F10 opt-out.
- First-public-release note added to the README hero and site status line,
hedged with 'to our knowledge'.
- social-preview.png (1280x640 GitHub link card) composed from a gameplay
frame of the preview montage; scratchpad/make-social-preview.py is the
local recipe (gitignored, like make-preview.py).
- Commit docs/img/shots/manifest.csv (gallery -> source recording map);
gitignore the regenerable CONTACT-SHEET.jpg; drop scratch-gdb-cmds.txt.
* Social preview: regenerate card from updated briefing HTML (v0.2.0 final wording)
The PNG committed earlier was a screenshot of the rendered site (stale
wording, video element unrendered). The canonical source is
.github/social-preview.html per .github/social-preview.md; update its
Status/Statistics lines to the v0.2.0 final state and re-render:
- Status: RELEASED v0.2.0 — WINDOWS · LINUX · STEAM DECK
- All 21 solo missions — full campaign completed in playtesting
- ~22k lines C/C++ · ~10k Python (port layer + tools)
- ~250 bugs root-caused · two coding agents, one human, part-time
Verified by pixel-diff against the previous render: only the four text
bands changed; title/fonts/layout identical. Drop the unreferenced
root-level social-preview.png.
* docs: refresh social preview + status for v0.2.0 playtest results
- Social card: 'RELEASED v0.2.0', 21/21 missions completed in playtesting,
~250 bugs fixed, two agents + one human; re-rendered the PNG from the
canonical .github/social-preview.html source (pixel-diff vs the old render
shows only the four edited text bands changed)
- README Status: campaign now claimed as completable end to end (user
playtest: full campaign, Agent difficulty, PC); Working list matches
- findings: D255 updated with the Deck register capture (Rdi=nil -> NULL
ObjectRecord.model, destroy-path ordering case confirmed; truncation ruled
out) and the rule-#2 sign-off question for a guard; D254 field-verified
* docs: regenerate findings index (D254/D255 status updates)
* 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.
Update the public-facing docs to the M-49 state (v0.1.0 alpha, first
full-campaign playtest: 19/21 missions completable, D191 crashes Bunker ii
+ Statue, D193 AI pacing is the top flaw).
- README: lead with what it is + status, research angle second; new
Quick start section; Status/Download rewritten for the alpha + dual
platform; "By the numbers" refreshed (~3 weeks, ~320 commits, ~190
findings, ~49 sessions, 69 PORT files).
- .github/release-notes.md: v0.1.0 title + known-issues rewrite.
- .github/social-preview.html: status/stat lines (regen + re-upload the
PNG separately).
- docs/index.md, docs/dev/LEVEL-STATUS.md: M-49 status.
- docs/dev/findings.md, docs/dev/GRAPHICS-BACKLOG.md: D191-D197 +
D148/D170/D173 M-49 escalations.
Hygiene:
- Fix the `&` literal in internals.md H1.
- Repoint two ../README.md links that 404 on the Pages site.
- Standardise British -> American spelling across the public docs
(behaviour->behavior, colour->color, catalogue->catalog,
centre->center, etc.). Deep internal logs left untouched.
- .gitignore: document the local-notes convention (docs/HANDOFF.md,
docs/BACKLOG.md, and a new docs/dev/notes/ drop for roadmaps and
session plans) so it survives a fresh clone.
- 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
- README: attract-mode stills + demo GIF as the hero row, status/warning
callouts, metrics table, "Contents" index, doc-index table; simplified
the two-agent workflow diagram (LR -> TD, fewer edges) for GitHub web
- Table-of-contents indexes added to internals.md, porting-notes.md,
agentic-development.md, AUDIO-PLAN.md
- CI: add Windows/MSYS2 build job, minimal token (contents: read),
persist-credentials: false, concurrency group; run on main + master.
validate job regenerates the US ROM-asset symbol file only and configures
ntsc-final (PAL/JP need a region ROM; no ROM is used in CI)
- .github: SECURITY.md, issue forms + chooser config, PR template,
labels.yml, dependabot (Actions, monthly)
- Do not track the proprietary SGI IRIX IDO toolchain or SGIImageViewer.exe;
document in SetupGuide.md / building.md how to obtain IDO for N64 ROM
builds (the PC port does not need it)
- Stop tracking two committed *.pyc files; drop stale screenshots and a
redundant .gitignore line; misc doc cross-reference fixes
Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
D142 (src/bondconstants.h): GCC/mingw gives an all-non-negative enum an
UNSIGNED underlying type, so a descending loop that relies on the counter
going negative to terminate spins forever. LEVEL_SOLO_SEQUENCE hit this in
fileGetHighestStageDifficultyCompletedForFolder (file2.c:859/887), called
every frame by the SELECT FILE screen -> whole game froze (no crash, just a
kernel-heartbeat stall). Fix: #ifdef PORT negative sentinel forces the enum
signed; first real enumerator stays 0, sizeof stays 4, no stored value
changes. Same trick DIFFICULTY already uses (DIFFICULTY_MULTI = -1).
Verified: front-end now boots -> intro -> SELECT FILE and stays alive 3000+
frames; menu chain navigable (user playtest).
port/src/input.c: GE_INPUTSCRIPT env var -- headless scripted controller-0
input for front-end/menu walkthroughs. "<frame>:<tok>[,<tok>]" entries;
buttons (A B Z START L R UP DOWN LEFT RIGHT CUP CDOWN CLEFT CRIGHT) pulse,
stick tokens (SUP SDOWN SLEFT SRIGHT / SNONE) sustain. When set it is the
sole controller-0 source (real kbd/mouse ignored) so a no-focus relative-
mouse window can't inject phantom deltas. Unset => zero effect.
Distilled from Chris Judnich's 'Decompiling a Nintendo 64 Game in 84 Days'
(saved under docs/reference/): explicit per-task budgets exposed to the
agent, a shared append-only learnings file, file-partitioned parallelism,
cheap pre-flight passes, and a standard investigation-subagent brief
template. PORT-LEARNINGS.md is a one-screen index of the recurring
N64->PC bug classes into PCPortResearch.md §F; AGENTS.md points to both.
The propDefs stream is a flat s32[]; each record's serialized N64 word count
is fixed across all 21 levels (verified: the getools C sources tile every
propDefs region byte-for-byte against the ROM). Every pointer member is 0 in
the file, so records containing pointers grow 4->8B per pointer on PC.
d88_propdefs.py rewrites each record to its native PC struct layout: header
word (u16+u8+u8) and _mkword half-pairs swapped independently, scalars
bswap32'd, pointer slots widened to 8 zero bytes, runtime areas zero-filled.
d88_layoutprobe.c (expanded) is the compiler-verified sizeof/offsetof source
for PROPDEF_PC_BYTES. Converts + walk-verifies all 21 levels clean.
- Move the two N64 reference screenshots out of the repo root into
docs/reference/ (n64-footage-2026-08-24.jpg, n64-footage-gun-barrel.png);
HANDOFF.md now points there.
- Gitignore screenshots/ (session captures) and tools_pc/mupen64/ (local
Mupen64 Plus checkout used for N64 reference footage; 8 MB of binaries).
- Commit tools_pc/mtxtest.c, the standalone fast3d matrix-convention
harness that HANDOFF/D72.3 docs reference as kept-for-reference.
- Refresh PORTING.md: status was still 'scaffolding / building once
implementation lands'; now reflects Phase 2 in progress and points at
docs/HANDOFF.md for current session state.
D59/D64: the 'external GL DLL' crash was an unbounded blood-RLE write
(sentinel die_blood_image_end lost its N64 section adjacency on PC)
clobbering the gun-barrel sub-DL at ptr_logo_and_walletbond_DL+0x200.
D60: osPiStartDma DMA-target validation (VirtualQuery-based) in libultra.c.
D61/D63: TEMP env-gated ROM-read / VTX-pool / menu probes (strip list in
HANDOFF Task 3). D62: OSMesgQueue/OSScMsg PC layout verification.
D65/D65b: enum HEADS/BODIES signed sentinels under PORT (PC GCC picks
unsigned underlying types; N64 semantics were signed).
D66: romCopyAligned + ramrom replay 64-bit pointer width (s32 truncation
of .bss targets > 4 GiB). D67: struct image_entry N64 layout
reconstruction (dataoffset:24 first, all-u32 bitfields, sizeof==8).
D68: Globalimagetable BE->LE fixup (port/src/gimgfixup.c): bswap the
IMAGESEG-marked Gfx w1 words in the 17 ROM-copied DLs + sImageTableEntry.
index in the 32 tables after texReset's romCopy; marker scan moved to
bytes 6..7 under PORT; compiled globalDL shadows synced post-texLoad for
explosion.c's g_ExplosionDisplayLists.
Result: game boots, plays intro music, and renders the entire ~3.5-minute
intro (logo -> gun barrel with Brosnan -> cast screen) at ~59 fps. New
blocker D69: load_bg_file faults on first stage load - BG-file headers are
N64 big-endian segment-0x0F offsets read LE on PC. Full analysis + strategy
in PCPortResearch.md §F/D69; HANDOFF rewritten for the D69 session.
No-Intro 'GoldenEye 007 (U) [!].z64' placed in data/ and verified
byte-identical (MD5 70c52588...) to the dev ROM copies
(data/ge007.ntsc-final.z64, baserom.u.z64). Header magic/title/country
check out; embedded CRCs DCBC50D1/09FD1AA3 re-computed and matched via
tools/n64cksum.c's CIC-6102 routine.
Adds tools_pc/romverify.c: one-shot integrity check (size, GE header
magic at 0x00, title, country byte, CRC1/CRC2) so any future ROM swap
is detectable in seconds. Fingerprint + provenance recorded in
docs/HANDOFF.md; gitignore the built romverify.exe.
- Replace setjmp/longjmp green threads with real pthreads (8MB stacks);
PortQueue side table (mutex+condvar) for OSMesgQueue; dedicated tick
thread posts VI retrace + services timers (D24).
- Dual-mapped N64 DRAM: one 8MB store at V1 0x70000000 (s32-safe, all game
RAM symbols) and V2 0x80000000 (KSEG0 mirror for offset|0x80000000
rebuilds). Windows: CreateFileMappingW + MapViewOfFileEx; POSIX: memfd
+ MAP_FIXED. dram_syms.s pins cfb_16/_bssSegmentEnd/tlb block (D25).
- Address-width shims: PHYS_TO_K0 identity, OS_K0_TO_PHYSICAL = offset from
V1 base, OS_PHYSICAL_TO_K0 identity; fast3d seg_addr() remaps small
offsets into V2 (D26).
- ASLR off (-Wl,--disable-dynamicbase): absolute symbols referenced through
pointer-typed externs (.refptr + BASE reloc) must not rebase; startup
sanity check on image base 0x140000000 (D27).
- osPiStartDma posts its completion message on every path (fixes
romReceiveMesg deadlock) (D29).
- SEH-free crash handler: raw fault log first, validated manual EBP-chain
walk second (-fno-omit-frame-pointer), TerminateProcess; crashDumpThreads
via Toolhelp32 for heartbeat hang dumps (D30).
- PD fast3d integrated (port/fast3d/), replaces gfxstub.c; real src/sched.c
compiled; mainproc runs as a real pthread, host thread pumps SDL events.
- Docs: findings D24-D31 + Phase 2 status (section F/G).
Current state: boots through bossInitMainthreadData to langInit(), then
SIGSEGVs on the first file load — stack frame clobbered by a table of
truncated s32 pointers (D31, open). Next: attribute the write with
'set scheduler-locking on' + hardware watchpoint.