The D117 findings.md addenda in the prior commit changed D117's status
text without regenerating findings-index.csv, and the new GE_DETERM_TRACE
env var wasn't yet mentioned in GE-ENV-PROBES.md -- both fail their
--check gate in CI's validate job. Caught by a real CI run, not predicted.
Claude-Session: https://claude.ai/code/session_01P3WrUHAFQZUuhrHhiJgTUr
The "worker's shell is cmd.exe, no grep/cat" note was wrong -- verified
directly against the delegate-local harness's own _run_bash: grep, sed,
cat, ls, head, pipes, and grep -rn all work (Git-for-Windows/MSYS2 on
PATH). The stale claim, baked into 4 of 5 local agent .md files, caused a
real dispatch to burn its whole turn budget full-read_file-ing files
instead of grepping them, hit the turn limit, and returned nothing.
Claude-Session: https://claude.ai/code/session_01P3WrUHAFQZUuhrHhiJgTUr
Adds an env-gated (GE_DETERM=1, default OFF), test-only deterministic-clock
mode to port/src/libultra.c per the design in docs/dev/findings.md section D117:
osGetCount() returns a virtual clock advanced synchronously in osRecvMesg()
when __scMain (the sole consumer of the scheduler's retrace queue) asks for
the next message, instead of tracking real elapsed time. Goal: two runs of
the same build take the same simulation path, for exact frame-diff
regression testing and reproducible crash repro.
Four passes this session, each is a real, verified result, not a guess:
1. Fixed the predicted pre-first-frame deadlock (a poll-driven fallback
caused it in the first cut).
2. Replaced the poll-driven fallback with synchronous per-ask generation
in osRecvMesg -- fixed the deadlock cleanly, but two-run divergence was
still 33-38%.
3. Capped the pre-first-task tick backlog to a fixed constant -- divergence
dropped to 17-20%, still not the ~0% target.
4. Added GE_DETERM_TRACE=1 instrumented tracing and diffed two runs' clock
event logs directly. Proved the tick mechanism is now fully
deterministic (byte-identical clock values through the first real task).
Root-caused the actual remaining divergence: every synthesized retrace
increments the scheduler's frameCount, which gates a half-rate client
(the audio client) by parity -- since the pre-task spin count is still
real-time-scheduling-dependent, that parity isn't guaranteed to match
between runs.
GE_DETERM is NOT achieving its determinism goal yet and is not meant to be
used for anything today -- it ships here purely as a well-documented,
zero-risk-when-off checkpoint (verify.sh bunker1 PASS, worst_cell unchanged,
confirmed after every change this session) so the next attempt starts from
proof instead of re-deriving four sessions of dead ends. Full trace numbers,
exact line citations, and the mechanism: docs/dev/findings.md section D117
(four M-52 addenda).
Claude-Session: https://claude.ai/code/session_01P3WrUHAFQZUuhrHhiJgTUr
CMakeLists.txt already opt-in enables CMAKE_{C,CXX}_COMPILER_LAUNCHER=
ccache when found (b49907de); CI just never installed ccache or persisted
its cache dir across runs. Wires actions/cache + ccache install into both
linux-build and windows-build, keyed on CMakeLists.txt's hash. Game
sources are frozen, so this should give a near-total hit rate after the
first run on each job.
YAML-validated; the actual hit-rate payoff needs a real CI run (cold, then
warm) to confirm -- not verifiable locally.
Claude-Session: https://claude.ai/code/session_01P3WrUHAFQZUuhrHhiJgTUr
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
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.
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.
* fix(linux): survive the decomp's latent stack-buffer overruns (D189)
The decompiled game code has a class of fixed-size stack-buffer over/under-
runs that write a few words into adjacent stack scratch:
* stan.c sub_GAME_7F0B1DDC — `StandTile *tileStack[39]` with a bail check
of `if (cat >= 41)` that only runs once per outer iteration, so `cat`
reaches 40 + pointCount and the append writes several slots past the end.
BUNKER1 (-level_09) prop room-list setup legitimately visits ~40 tiles.
* bondview2.c bondviewCalcIntroSwirlCamera — `f32 pointbuf[10]` indexed
`&pointbuf[i*3]` for i in [-1..2] then written [0..5], i.e. [-3 .. 11].
On the N64 and on the MinGW/Windows build (neither uses a stack protector)
these are harmless in practice — the Windows build is stable over thousands
of frames. Only Ubuntu's gcc default (`-fstack-protector-strong`) turns them
into a fatal `__stack_chk_fail`, at BUNKER1 stage load and again at the intro
swirl camera (~frame 5).
Two-part fix:
* `-fno-stack-protector` globally (CMakeLists) — matches the N64 and MinGW
builds so the same code runs the same way. A hardened Linux build would
need the whole overrun class swept first.
* `StandTile *tileStack[64]` under `#ifdef PORT` (stan.c) — the one case
that is cleanly understood and bounded (`pointCount` is 4-bit, so 40+15
is the true worst case). Pure local scratch, never escapes, no behaviour
change. N64 keeps `[39]` under `#else`.
* stan.c:2225 link-resolve also switched to the D177 PORT_PTRADD macro for
idiom consistency (the blob maps below 4 GiB so the bare cast is
currently lossless; #else is byte-identical).
Stacked on the D188 xprintf fix. Verified: Windows -level_09 framediff 3/3
within golden threshold (all changes are #ifdef PORT or no-op on MinGW);
Linux -O2 -level_09 now runs to frame 2100+ with no crash.
* docs: D189 update (stack-protector / latent overrun class) + porting-notes D8
* docs: D190 — Linux -level_45 objective.c load crash (open); D189 sweep 5/6
* fix(linux): thread a va_list* through _Printf (x86-64 SysV ABI)
On the x86-64 System V ABI `va_list` is an array type, so a by-value
`va_list` parameter decays to a pointer. `_Printf()` was passing `&args`
(the address of that local pointer, not the argument list) to `_Putfld()`,
which does `va_arg(*args, ...)` — so `_Putfld` walked garbage and
segfaulted. Latent at `-Og`, fatal at `-O2` (PR #9), which is why the
Linux release build crashed at boot:
bossInitMainthreadData (boss.c:247, the D121 per-level -m token sprintf)
-> sprintf("-level_%c%c %s", ...) -> _Putfld -> SIGSEGV @ xprintf.c
Same UB class as D187. Windows x64's `va_list` is a scalar (`char*`), so
`&args` happened to work there — the Windows build is unaffected.
Fix: under `#ifdef PORT`, `_Printf` takes `va_list *` and passes it
straight to `_Putfld` (which already expects `va_list *`); `src/sprintf.c`
(the only PC caller — `rmon.c` is excluded from the build) passes `&args`
of a real `va_list` object. N64 build untouched (`#ifdef PORT`, and it
byte-matches the ROM).
Verified:
- Windows: build links, `-level_09` framediff 3/3 within golden threshold.
- Linux -O2: boot crash gone, now advances past bossInitMainthreadData
into stage load (uncovers a separate pre-existing stan.c stack overflow
during BUNKER1 prop setup — tracked separately).
* docs: D188 (xprintf va_list ABI) + D189 (stan.c stack-smash) findings + porting-notes D7
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.
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.
The HUD weapon/ammo-type icon (bottom-right, and bottom-left when
dual-wielding) stopped drawing ~2026-09-02; the ammo *numbers* still
render. Root cause: set_rgba_redirect_generate_microcode (gunfire.c, the
sole icon emitter) is non-void but has no return statement (the decomp
banner flags it). At -Og the tail call's result stayed in $v0 and it
worked; commit 7549570b restored -O2 for playtest builds, where the
undefined return register makes callers resume the DL from the icon's
start offset and overwrite the icon commands with the following
microcode_constructor / gunDrawHudInteger output.
Fix: add the return under AVOID_UB (already -D'd in the PC build;
CMakeLists.txt:207), N64 byte-match path unchanged under #else.
Codegen-correctness only, same class as the existing AVOID_UB edits.
Owed: build (-O2) + -level_09 capture diff vs docs/img/bunker1-2.png.
A/B: Debug build (-Og) shows the icon, -O2 build without this fix does not.
- 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
Drop `paths-ignore` from the `pull_request:` trigger. With it, a docs-only
PR never triggered the workflow, so the required `validate` / `windows-build`
checks stayed "expected" forever and the PR could not be merged without an
owner bypass (M-44 hit this on #14/#15).
The workflow now always runs on PRs; the `changes` job diffs the PR against
its base sha (mirroring the existing branch-push logic against
`github.event.before`) and, for a docs-only change, reports the build jobs
as skipped — which branch protection accepts as success.
Claude-Session: https://claude.ai/code/session_01V4pK3pT83YnPsBp3Ey4RP2
gbi.h's gDP* DL macros use _SHIFTL/_SHIFTR without defining them; game TUs
pull them in via <ultra64.h>. This is the first port/src TU to build a
display list, and on GCC/ld the missing macro became an undefined symbol
(MinGW's include chain happens to provide it). Define the two pure macros
locally, verbatim from PR/mbi.h -- including mbi.h itself drags in N64 OS
headers that shadow libc.
- Fix invisible text: drawText passed the measured text w/h into textRender's
width/height args, which are the on-screen CLIP rect (not the text size), so
every glyph failed the clip test. Pass the full 2D viewport like bondview2.c's
debug-text path.
- Mouse UI: hover-to-highlight; left-click in the value column toggles/cycles;
drag sliders; red X close box (clicking a row's label only focuses it, so
selecting can't flip a toggle). inputSuspendForOverlay() forces the OS cursor
free + visible while the overlay owns the mouse.
- New rows: Fullscreen (live) and Resolution (windowed presets filtered to the
desktop, live resize + recenter). SDL window ops are posted from the
scheduler thread and drained host-side in videoPumpEvents.
- Layout recomputed for the wide BankGothic font: right-aligned values, wider
row pitch, all 13 rows fit the ~240-unit 2D space; nothing clips.
Port-layer only. Links clean; runtime-verified on a real display (front end +
-level_09): layout, hover/click/drag, live VSync/filter/fullscreen/resolution.
The M-37 scratch note (D176a-sky-rootcause.md) was never committed. Re-derive
the complete format from src/game/sky.c and include/PR/gbi.h:
- skyRenderTri/skyRenderFull emit a verbatim N64 RDP triangle command
(2-word header + 3 edge-slope pairs + 16-word shade block + 16-word
texture block) chopped into gImmp1(G_RDPHALF_1/_CONT) pairs, terminated
by G_RDPHALF_2. GE's RSP ucode DMAs it straight to the RDP.
- sub_GAME_7F097388 already yields complete screen-space verts; the
projection math is endian-clean and is not the defect.
Two implementation paths documented with a recommendation (Path B: an
#ifdef PORT emit swap in sky.c, first; Path A: an RDP-tri decoder in
gfx_pc.cpp, as the long-term answer). Word layout cross-checked against
gbi.h packing conventions.
sync_framerate_with_timer() runs inline on the scheduler thread
(osSpTaskStartGo -> gfx_run -> swap_buffers_begin), so a low frame cap
blocks VI-retrace delivery and drags every game thread down to the cap
rate rather than just dropping presented frames. There is no N64 analogue
(RSP/RDP/VI are separate silicon; the CPU never waits on them).
Clamp 0 < FpsCap < 30 to 0 (uncapped) with a warning, at both:
- videoInit, so a bad ge007.ini value (e.g. dinged to 10 via the F10
options overlay) is normalised and persists sane on next configSave();
- gfx_sdl_set_target_fps, the fast3d chokepoint, which also covers the
overlay's live set path.
Caps >= 30 are unchanged. Default (FpsCap = 0) is byte-identical to
golden (-level_09 framediff 3/3). The real fix — moving frame pacing off
the scheduler thread — is documented as owed in findings.md D186.
- findings.md / GRAPHICS-BACKLOG.md: -level_22 (Statue, night) is a clean
no-input headless repro of the D176(a) black sky — bare boot, frame ~360,
upper half pure black. Best verification target for the RDPHALF sky-tri
decoder. Note -level_36/-43 intro cams face the terrain (not a sky test
without GE_INPUTSCRIPT); -level_29 top-of-frame is dim, not black.
- GRAPHICS-BACKLOG "D74 wrap-block": struck through — the dead-code / OOB
in-vertex-loop wrap block was reworked and hoisted to a per-texunit
pre-wrap window behind Video.WrapFix at M-30 (RC3/D167). No longer latent.
building.md listed only d43 + d69; d88 (per-level Usetup*Z stage setup,
appended to the pccg dir) was omitted, so a source build per these
instructions produced a game that crashed on first level load.
The non-Debug branch unconditionally appended -Og, which (last -O wins)
overrode RelWithDebInfo's -O2 for every TU. -Og is a ~10% frame-time
regression on the software RSP/rasteriser hot path and is not what a
release/playtest build wants. Restrict -Og to explicit Debug builds.
Note: does NOT fix the ~1 fps issue under investigation — the software
renderer is ~950 ms/frame at both -Og and -O2 on this box; this is just
the correct default.
portHeartbeatCheck fired on any >3s frame gap and re-fired every 2s,
spamming ge007.crash.log with thread dumps during gdb pauses, level
loads, and SIGTERM teardown on a slow/software-GL setup. Widen to 8s and
stop after 3 reports. A genuine hang is permanent so it still gets caught.
CMakeLists.txt: CMAKE_{C,CXX}_COMPILER_LAUNCHER=ccache when found (no-op
otherwise); game sources are frozen so branch-switch rebuilds get near-free.
build-pc.sh: BUILD_DIR=${BUILD_DIR:-build-pc} so parallel agents/worktrees
can use separate build dirs.
- gfx_opengl_start_draw_to_framebuffer: fb_id 0 now binds GL framebuffer 0
(the window) instead of framebuffers[0].fbo, a generated attachment-less
FBO. Harmless on Windows (verified: -level_09 renders, mean RGB ~80);
strict GL (Mesa) treats the incomplete FBO as an error and drops draws.
- GE_PCDUMP / F12 screenshots move to a pre-swap hook (gfx_pre_swap_hook,
called from gfx_sdl_swap_buffers_begin before SDL_GL_SwapWindow). Reading
the back buffer AFTER the swap is undefined on buffer-exchange drivers
(Mesa/WSLg) -> black capture. Also: read GL_BACK explicitly, glFinish
first, GL_PACK_ALIGNMENT=1 (639*3 is not 4-aligned -> row shear), and log
any glReadPixels error.
Windows -level_09 verified: renders, no crash, capture non-black.
The no-PIE default base 0x400000 puts .data/.bss around 0x00500000-
0x00600000, colliding with the address space fast3d's seg_addr() decodes.
A game-built display list embeds &symbol (osVirtualToPhysical identity
shim); seg_addr sees w1 < 0x800000, treats it as a K0-physical DRAM
offset and remaps to 0x80xxxxxx (zeroed DRAM mirror) -> 'unknown GBI
opcode 0x00 at 0x80575d40' twice per frame -> DL walk aborts.
Move the image to 0x20000000 -- the gap between segmented addrs
(<0x10000000), the cart map (0x10000000), the D131 module range
([0x40000000,0x70000000)) and DRAM (>=0x70000000). seg_addr passes
0x20xxxxxx through untouched. Still <2GiB so -mcmodel=small relocations
are fine (0x140000000 needed mcmodel=large). ELF analogue of the Windows
0x140000000 base. D146 opcode-0x00 errors: 2/frame -> 0.
The N64 <stdlib.h> stub declares neither. MinGW's other CRT headers leak
them; a strict host GCC (Linux/macOS, -std=gnu11) does not, so getenv()'s
64-bit char* return is assumed int and truncated -> configGetFrameDump
(GE_PCDUMP path) dereferenced a low-32 pointer and segfaulted. Add K&R
decls: pc_protos.h (D38 mechanism -> game TUs via <ultra64.h>) and the
port/shim/stdlib.h non-Windows branch (-> port TUs that #include
<stdlib.h>). K&R form stays compatible with the real prototypes; malloc/
calloc/free are already covered elsewhere so not repeated.
First runtime bring-up on Linux (Ubuntu, gcc-14). -level_09 now renders
past frame 1200 crash-free with a clean shutdown, matching Windows.
- port/src/romdata.c: no-PIE loads the image at 0x400000, so the D34
'clear low 32 bits of a symbol address' gives 0 -> false 'image base not
4GiB-aligned' abort. The ANIM_DATA_* lvalues are only ever address-taken
(never dereferenced), so fall back to a fixed 4GiB-aligned sentinel
(0x100000000) for g_pc_animdata_base.
- port/src/main.c: the 'image must load at 0x140000000' assertion is
PE-specific (.refptr base relocations); ELF no-PIE resolves dram_syms.s
absolute symbols literally regardless of load address. Guard it to
PLATFORM_WINDOWS.
- port/src/libultra.c: mmap game-thread stacks with MAP_32BIT so they sit
in the low 2GiB. The decomp truncates stack-buffer pointers to u32 (e.g.
in texLoad); glibc pthread stacks live above 4GiB and
those truncations corrupt. One fix covers the whole idiom class. Windows
pthread stacks are already low.
- src/game/image.c: belt-and-braces #ifdef PORT -- align compbuffer at
uintptr_t width (identical result on N64).
- CMakeLists.txt: drop the -Ttext-segment experiment (needs -mcmodel=large;
breaks R_X86_64_32 relocs) -- comment only.
Windows: build 240/241, -level_09 crash-free past frame 1440.
crashDumpThreads was defined only in the PLATFORM_WINDOWS block; the
kernel-heartbeat watchdog in port/src/libultra.c references it
unconditionally -> undefined reference at link on Linux. Add a
PLATFORM_LINUX implementation: log the live thread roster + the watchdog
thread's own backtrace (a full cross-thread stackwalk needs signal-based
peer unwinding -- deferred). Linux-only block; Windows unaffected.
The decomp's src/sched.h (N64 OSSc* scheduler) is on the include path and
shadows the system header. libstdc++'s <thread>/<mutex> gthr headers in the
fast3d C++ TUs #include <sched.h> and need the real one (cpu_set_t,
sched_yield) -- with -std=gnu++20 libstdc++ defines _GNU_SOURCE so
<pthread.h> also references cpu_set_t. New shim: C++ -> generated
hostsched.h (absolute path, hoststring pattern); C -> #include_next so
src/sched.h still wins as before. Windows build verified green (240/241).
<stddef.h> is compiler-internal (lib/gcc/<triple>/<ver>/include), not in
bin/../include on a native Linux GCC, so the MinGW-layout guess produced
/usr/include/stddef.h which does not exist -> fatal error in every fast3d
C++ TU. Ask the compiler with -print-file-name=include/stddef.h and fall
back to the old path. MinGW resolves to its own lib/gcc internal copy;
Windows build verified green (240/241).
- CMakeLists: CMAKE_C_EXTENSIONS/CXX_EXTENSIONS ON. Strict -std=c11/c++20
define __STRICT_ANSI__, which disables glibc _DEFAULT_SOURCE and hides
clock_gettime(CLOCK_REALTIME) etc. that port/src/libultra.c needs.
-std=gnu11/gnu++20 is GCC's default and what MinGW already used.
- port/src/{pccg,pcmodels,romdata}.c: local 'extern int snprintf(...,
unsigned long long maxsize, ...)' decls conflict with glibc's size_t one
(same bug already fixed in pc_protos.h in M-39); spell it size_t.
- libultra.c: drop the _GNU_SOURCE define (pulls cpu_set_t refs into
<pthread.h> without <sched.h>); the D60 POSIX stack-bounds diagnostic
now just prints n/a rather than needing pthread_getattr_np.
Windows: full rebuild 240/241, -level_09 crash-free past frame 1740.
- pc_protos.h: the vestigial 'long long time()' prototype conflicts with
glibc's time_t time(time_t*); no game code calls time(), guard it to
_WIN32 (MinGW shadows <time.h> with the N64 stub; POSIX gets the real one).
- n64stubs.c: don't redefine bcopy/bzero on POSIX -- glibc exports them
(via <strings.h>, size_t sizes); the local int-signature definitions
conflict. _WIN32-only now.
- libultra.c: #define _GNU_SOURCE on POSIX for clock_gettime(CLOCK_*) and
pthread_getattr_np; guard the D60 VirtualQuery committed-memory probe and
GetCurrentThreadStackLimits behind PLATFORM_WINDOWS with a
pthread_getattr_np-based stack-bounds fallback for the diagnostic path.
Windows build verified green.
glibc's <strings.h> (pulled in transitively, e.g. via _GNU_SOURCE in
port/src/crash.c) declares these with size_t sizes; the N64 prototypes
here use int, which GCC 14 rejects as conflicting types. On
PORT && !_WIN32 include <strings.h> and drop the local decls. Windows
(PORT+_WIN32) and the N64 build (no PORT) keep the original prototypes
unchanged; Windows build verified green (239/239).
GCC 14 hard-errors enum(CHRFLAG)->pointer as 'incompatible type for
argument' with no controlling warning flag (not downgraded by
-fpermissive / -Wno-error=int-conversion). Line 157 of the same WIP-decomp
function already casts the identical (Model*)var_v0->chrflags expression;
match it on line 162. Codegen-identical.
SDL2's headers (SDL_stdinc.h, SDL_hidapi.h) and glibc's <bits/wchar2.h>
reference wchar_t. In C it is a library typedef owned by the compiler's
shadowed <stddef.h>, which our shim replaces with builtin-derived types
on Linux/macOS. Add wchar_t from __WCHAR_TYPE__, guarded by _WCHAR_T so a
real <stddef.h> reached another way stays a no-op. C-on-Linux/macOS
branch only; MinGW and C++ paths untouched.
The vestigial u8* param2/param3 receive an AMMOTYPE enum and a u32 at
both callsites. GCC 14 treats enum->pointer as a hard 'incompatible type
for argument' error that -fpermissive does not downgrade (unlike the
int<->pointer permerrors). Guard the definition with #ifdef PORT to match
the callsites' actual types; params stay unused so codegen is unchanged
and the N64 source path is literally untouched.
GCC 14 turned int<->pointer (known prototype), incompatible-pointer arg
types, and implicit int/function into hard errors. The WIP decomp still hits
these (append_text_picked_up in propobj.c passes AMMOTYPE/u32 to vestigial
u8* params). -fpermissive downgrades them to warnings — the C analogue of
the existing -Wno-error= lines, and does not touch game code. Non-MinGW C only.
- src/bondtypes.h's `#define inherits struct` makes the base an unnamed
tagged member and reads its fields directly. mingw32 GCC enables this via
implicit -fms-extensions; native GCC does not -> "'ObjectRecord' has no
member named 'type'". Add -fplan9-extensions (C only) for non-MinGW.
- Pin the CI job to ubuntu-24.04 + gcc-14 so the first Linux bring-up chases
real portability gaps, not old-GCC pedantry (dev toolchain is GCC 16).
Next GCC-on-Linux breaks after the stddef fix (linux-build CI):
- port/shim/stdarg.h routed to the decomp include/stdarg.h, which defines
va_list from __builtin_va_list but NOT the __gnuc_va_list alias that
glibc's <stdio.h> references — 'unknown type name __gnuc_va_list' across
every TU that pulls <stdio.h>. Add a Linux/macOS branch that provides
va_list / __gnuc_va_list / va_* straight from compiler builtins and does
not drag <ultra64.h> into system-header parsing. Windows branch unchanged.
- pc_protos.h declared snprintf's size argument as 'unsigned long long' —
correct on MinGW (LLP64) but not glibc, where size_t is 'unsigned long':
'conflicting types for snprintf'. Spell it size_t (now visible via the
stddef fix); identical to ull on MinGW.
Windows: both changes are no-ops on MinGW (stdarg Windows branch byte-identical;
size_t == unsigned long long).
The first cut routed C TUs to hoststddef.h unconditionally. That regresses
MinGW: its <stddef.h> defines `errno (*_errno())`, which collides with
PR/os.h's `u8 errno;` field. And hoststddef.h's bin/../include/stddef.h path
does not exist on Linux anyway.
Split three ways: C++ -> hoststddef.h (unchanged); C on Windows -> the empty
N64 stub (unchanged, pre-existing behaviour); C on Linux/macOS -> size_t /
ptrdiff_t / NULL / offsetof from __SIZE_TYPE__ / __PTRDIFF_TYPE__ /
__builtin_offsetof (always predefined, no header, no collision).
Verified: Windows ntsc-final builds 240/240, -level_09 boots crash-free.
Two GCC-on-Linux build breaks that MinGW masked (surfaced by the new
linux-build CI job):
- port/shim/stddef.h routed C TUs to the empty N64 include/stddef.h stub.
MinGW's CRT headers leak size_t/ptrdiff_t transitively; glibc/GCC define
them only in the compiler's own <stddef.h>, so include/PR/ultratypes.h's
non-N64 path ("typedef ptrdiff_t ssize_t") failed with "unknown type name".
Route C TUs to hoststddef.h too (C++ already was). Additive — the active
N64 stub typedefs nothing.
- CMakeLists: dram_syms.s absolute symbols at 0x70000000 need a non-PIE image
on Linux exactly as they need --disable-dynamicbase on Windows. Add an
elseif(UNIX AND NOT APPLE) branch: -fno-pie / -no-pie.
Windows unaffected (host <stddef.h> is strictly more correct than the stub;
the MINGW linker branch is unchanged).
validate only CMake-configures on Ubuntu; it never compiles, so GCC-on-Linux
strictness gaps (implicit declarations, -Wint-conversion, missing includes,
MinGW-only builtins) stay invisible until someone builds on a real Linux box.
Add linux-build: system GCC, `cmake --build` for ntsc-final. Green here means
"compiles + links on Linux", not a runtime claim (the port has never run on
Linux). Not wired as a required check yet.
Pairs with the romdata POSIX mmap patch on this branch (per docs/dev/LINUX-PORT.md).
The cart-base map at 0x10000000 (so absolute ROM-asset symbols are live host
pointers) was Windows-only; the #else branch was a stub that logged
"not implemented" and fell to the degraded heap-copy path.
- Factor the post-map work (memcpy + pcmodels/pccg sidecar load + D55 RLE
header fixup) into romdataFinishCartMap(), shared by both paths.
- POSIX #else: anonymous mmap at CART_BASE with MAP_FIXED_NOREPLACE where
available; on any failure munmap + fall through to the identical heap copy.
- romdataDestroy() munmaps on POSIX (length tracked in mappedLen).
Verified on Windows: builds 242/242, -level_09 boots crash-free (identical
behaviour with/without the refactor). Linux compile + runtime verification
owed — see docs/dev/LINUX-PORT.md. No behaviour change on Windows.
building.md listed only d43 + d69; d88 (per-level Usetup*Z stage setup,
appended to the pccg dir) was omitted, so a source build per these
instructions produced a game that crashed on first level load.
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.
The cart-base map at 0x10000000 (so absolute ROM-asset symbols are live host
pointers) was Windows-only; the #else branch was a stub that logged
"not implemented" and fell to the degraded heap-copy path.
- Factor the post-map work (memcpy + pcmodels/pccg sidecar load + D55 RLE
header fixup) into romdataFinishCartMap(), shared by both paths.
- POSIX #else: anonymous mmap at CART_BASE with MAP_FIXED_NOREPLACE where
available; on any failure munmap + fall through to the identical heap copy.
- romdataDestroy() munmaps on POSIX (length tracked in mappedLen).
Verified on Windows: builds 242/242, -level_09 boots crash-free (identical
behaviour with/without the refactor). Linux compile + runtime verification
owed — see docs/dev/LINUX-PORT.md. No behaviour change on Windows.
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.
The pull_request trigger already has paths-ignore (**.md, docs/**,
LICENSE, NOTICE), but push to main deliberately has none so tag builds
always run and the required check never hangs pending. A docs-only merge
to main therefore still ran a full ~15 min Windows build for nothing.
Add a lightweight 'changes' job (pure git diff against github.event.before,
no third-party action) that mirrors those globs. validate + windows-build
now gate on needs.changes.outputs.code == 'true'; the job forces true for
tags and for pull_request / workflow_dispatch, so only docs-only branch
pushes are skipped. Skipped required checks report green to branch
protection.
- docs/dev/release-dropin-rom-plan.md: proposal for a 'download zip, drop
in your ROM, double-click' release — covers the romassets/pcmodels/pccg
sidecar gap (D179), a native first-run converter, CI and region handling.
- docs/_includes/head-custom.html: render fenced mermaid blocks on the
docs site (kramdown code fence -> mermaid.run, theme-aware).
- CLAUDE.md: 'Local Qwen dispatch' section — tools, worker list, the
dispatch rubric, backend reality, and the cmd.exe spot-check quirk.
Nothing runs end to end (Phase 2 WIP), so the 'pre-built Windows bundles
are published under Releases' language in the README and site was
inaccurate. Reword to: no binaries yet, build from source; bundles may
follow once the port is playable. Also rename the local model Qwen 3 ->
Qwen 3.8 in the docs to match reality.
New port/src/optionsoverlay.c + port/include/optionsoverlay.h: an
immediate-mode options overlay drawn as its own fast3d 2D display list,
appended after the game DL. Implements docs/dev/OPTIONS-MENU-PLAN.md §2/§3.
- config.c/h: configForEachOption() + configSetOptionMeta() label/step/enum
side table; CONFIG_OPT_* enum. config.c stays free of specific-key knowledge.
- gfx_pc.cpp gfx_run(): one-line hook runs optionsOverlayEmit() after the game
DL, before gfx_flush. Returns NULL when closed -> zero bytes appended, golden
dumps byte-identical.
- video.c: F10 toggles (videoPumpEvents, next to F12); ESC closes; wheel
scrolls; videoRequestLiveConfig()/liveCfgDirty re-applies VSync/FpsCap/
TextureFilter at frame start.
- input.c: while open, controller 0 returns a neutral pad and nav
(arrows/enter/wheel/mouse) routes to optionsOverlayHandleInput() — mirrors
the D180 WI-1 swallow pattern.
- v1 rows: VSync, FpsCap, MSAA(restart), TextureFilter, MouseAimSpeed,
MouseTurnSpeed, MouseInvertY, MouseCaptureMode, ScreenShakeIntensity.
Live-apply where possible; configSave() on close.
- GE_OPTIONSOVERLAY=1 auto-opens at boot (env-gated diagnostic, left in).
Port-layer only, no src/ edits, no #ifdef PORT in game code. Build links
241/241. Runtime framediff/soak/eyeball owed on an interactive machine
(this headless env kills the GUI process early and writes 0-byte PPMs).
MoveBond gates the entire ladder-collision path on
stanGetLocusCount(&curLocus), which was pinned at 0 on PC. Three compounding
pointer-width bugs in the stan navigation layer (same class as D79/D90),
no game logic touched:
1. stanCheckLinkedSpecialTile receives the caller's
struct StandTileLocusCallbackRecord typed as `s32 *outFlags` and writes
the LADDER signal with raw `outFlags[1] = 1`. The record's first member
is `s32 *rooms` — 8 bytes on PC — so `[1]` is its high half and `count`
(moved +4 -> +8) is never written. stanGetLocusCount() reads ->count,
stays 0, ladder path never runs. (FORCECROUCH survived by luck: LE low
half of rooms is at +0.) Fix: #ifdef PORT cast to the real struct, write
->rooms / ->count by name.
2. curLocus was declared `struct move_bond_temp_struct` — an 8-byte
"placeholder while matching" that equals the N64 record size but is too
small for the widened PC record, so stan's field writes overflowed the
local. Fix: #ifdef PORT declare it as the real
struct StandTileLocusCallbackRecord.
3. stanGetMoveBondCollisionTiles would AV on the first climb:
`(coord3d *)((s32)coords + off)` truncates the 64-bit &bondCollision
stack pointer. Fix: PORT_PTRADD (uintptr_t arithmetic) added to stan.c.
All #ifdef PORT; N64 path kept verbatim under #else. Candidate root-cause
from an M-36 subagent (parts 1/3); part 2 found on review.
Verified: build links clean; -level_09 framediff 3/3 (frame_200 run
variance only); -level_36 (Surface, mandatory ladders) boots crash-free;
interactive climb confirmed working in-game by the owner.
findings.md D177 (§F + §H), porting-notes.md §A.
Not root-caused; narrowed substantially. Headless -level_36 + GE_D176 probe
(bgfog.c fogLoadLevelEnvironment, sky.c skyRender):
- Environment data is CORRECT: fog_tables[] matches Id=36, Sky.Clouds=1,
warm CloudRGB=240,120,30, SkyImageId=0. Not a serialized-struct
byte-order bug (fog_tables is a compiled C initializer).
- skyRender runs the cloud path; corner classification is correct
(switch case 12, "sky is top half").
- But skyRenderTri/skyRenderFull emit nothing - not even the env->RGB
fill shows. Sky polys degenerate after projection, culled, or the
skywaterimages[0] bind is failing.
Next (findings.md "D176(a)"): probe sub_GAME_7F097388 projected coords;
verify skywaterimages resolves on PC (image_bank.c GIMG offset 0xFB4);
fast3d sky-tri trace; cross-check Dam/Statue exteriors.
GE_D176=1 probe left in tree (#ifdef PORT, env-gated, inert). Build
240/240 links clean; -level_09 framediff 3/3.
Family A of the M-36 triage (Surface "grey static" D176b, file-select
interlacing D182-2). The assigned hypothesis — import_texture_* shear when
line_size_bytes != full_image_line_size_bytes — is DISPROVEN for the Surface
repro:
- Only gfx_dp_load_tile produces a strided load; GE loads every texture via
gDPLoadBlock (tex.c texWriteLoadToTmem*), which sets line == full. Measured:
0/64 GE_DTEX + 0/166 GE_TEXDUMP imports strided on -level_36; 0 in the
front end either.
- Independent offline pitch scan (new GE_TEXRAW dump) shows the importers
pick exactly the pitch the source is stored at. The Surface cliff texture
is a 32x32 IA8 whose ROM bytes are genuinely a noise field — decoded
faithfully, tiled ~1 texel/px. D176(b) is likely a tiling-density / UV /
shift-LOD issue (RC3/D167 family) or a wrong-texture bind, not a decode
bug. Full evidence: findings.md D183. D176(b)/D182(2) stay OPEN.
- Re-confirmed D159's texSwapAltRowBytes no-op is correct (re-applying it
makes the dumps worse).
Shipped anyway (golden-safe, reversible):
- import_texture() compacts strided rows into a contiguous buffer before the
format dispatch, so every importer sees line == full — the behaviour the
seven SUPPORT_CHECK(full == line) asserts only assert (and assert compiles
out under NDEBUG). Provably inert when full == line (all current loads).
GE_TEXPITCH=0 restores the old flat read.
- GE_DTEX gains line=/full=/STRIDED markers; GE_TEXRAW=1 dumps raw
importer-input bytes for offline decode-vs-source analysis.
Verified: build 240/240; -level_09 framediff 3/3; GE_STARTMENU=10 briefing
crash-free, renders. Investigation: subagent (worktree).
Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
Family B of the M-36 triage. The Ubrief*Z segment (assets/obseg/brief/) is
a raw 48-byte ROM image of struct BriefStruct { u16 brief[4]; struct { u16
textid; u16 enabled_difficulty; } objective[10]; }, loaded by front.c
load_briefing_text_for_stage() via _fileNameLoadToAddr() with no converter
and no BE->LE fixup. On the LE host all 24 u16 read byte-swapped:
- textid 0x2C04 (getStringID(LDAM,4)) -> 0x042C -> langGet() resolves an
unloaded bank -> NULL -> blank objective/briefing text. This is the whole
of the D143 "briefing text blank" residual (the bank loaded fine; the ids
into it were swapped).
- enabled_difficulty 0x0001 -> 0x0100 = 256, so `selected_difficulty >=
enabled_difficulty` is always false -> difficulty-gated objectives vanish
(on Agent only the one AGENT-difficulty line survived -> lone "a." bullet).
Fix: romdataFixupBriefing() in port/src/romdata.c, called from a #ifdef PORT
block in load_briefing_text_for_stage() right after the load — same shape as
langFixupLoadedBank(). BE-serialized-struct reconciliation (AGENTS.md #2),
semantics-preserving, N64 build untouched. GE_D178=1 dumps raw + fixed words.
Verified: Dam briefing renders all four objectives at 00 Agent with correct
difficulty gating and "007: James Bond" header; -level_09 framediff 3/3;
GE_STARTMENU=7/=13 crash-free; build links clean 240/240.
Investigation + patch: subagent (worktree). Suspect 2 (Gfx-width temp_s0
arithmetic) was ruled out — benign 512-byte gap, langLoadToBank records the
bank base itself. Follow-ups noted in findings.md: annotate D143 as
superseded; latent 64->32 pointer truncation at front.c:6569 / :2202.
Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
Headless GE_PCDUMP (-level_36, GE_STARTMENU=10) + static read of the
implicated port code. Two headline findings:
- The "comb interlacing" (D176b, D182-2) is NOT the D159 family.
texSwapAltRowBytes is a full #ifdef PORT no-op (image.c:2199) and
cannot cause anything. Real cause: gfx_pc.cpp import_texture_* assume
line_size_bytes == full_image_line_size_bytes and shear non-PoT /
sub-rect tiles into diagonal noise. New "Family A". Backlog D176/D182
corrected.
- D178 briefing objectives are BLANK, not wrong-listed: header renders,
then just "a." with no objective text. That is the D143/D151
langGet-NULL blank-text family ("Family B"), not a propDef walk desync.
docs/dev/M-36-TRIAGE.md: full family table (A fast3d texture pitch,
B string bank, C D75 models [defer], D blood/particle, E transient
hang [defer], F misc). Recommends Families A and B as the two
high-ROI targets, disjoint enough for two subagents.
Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
- building.md: document the d43_emit.py / d69_emit.py sidecar generation
as an explicit required step (4), split Run to step 5. A plain clone
needs no ROM to build but the sidecars are required to run (D179).
- GRAPHICS-BACKLOG.md: expand D176 (Surface exterior: black sky +
comb-interlaced tree-line, screenshots) and add D182 (SELECT FILE
background degrades after navigate-away-and-back: Bond image stops
drawing + comb interlacing). Both observed this session, not fixed.
D176(b) and D182(2) interlacing share the D159 texSwapAltRowBytes
family recurring on texture re-import.
- docs/img/bugs/: d176 our-build vs reference, d182 backout capture.
Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
Follow-on to ad748654 (D180). Playtested this session: ESC-to-free now
works; Dam -> Surface seamless on Agent.
- Dual SDL event pump race: port/src/video.c (host thread) and
port/fast3d/gfx_sdl2.cpp (render thread) both drain the same SDL queue,
so either loop can dequeue a given event. ESC-to-free / click-to-lock /
mouse wheel / focus events were being eaten by the render-thread pump
and lost. Now handled in BOTH pumps (inputReleaseCapture /
inputNotifyClick / inputPostWheel / inputSetMouseGrab).
- Menu pointer rewritten for capture mode. front.c's cursor integrator
caps at ~5.75 virtual px/poll, so the D165/D169 P-controller fed from
an absolute mouse position was always laggy/floaty and could not reach
the bottom of the grid. With MouseCaptureMode=1 the front end has a
real free OS cursor: write cursor_h_pos/v_pos directly from the
absolute mouse position and emit a zero stick. Legacy relative path
(MouseCaptureMode=0) unchanged, and its estimate clamp now tracks the
live front-end rect (was pinned to 320x240 -> right/bottom unreachable).
- OS cursor hidden while the window is focused (applyCursorVisibility);
shown again on focus loss.
- Window.Width/Height <= 0 now auto-fits a 4:3 window to ~85% of the
desktop (gfx_sdl2 gfx_sdl_init) instead of 640x480 native. video.c
passes 0 through; [Window] persistence still pins whatever you leave.
Verified: build-pc.sh ntsc-final green 240/240; -level_09 framediff
3/3 within threshold (nonclear d <= 0.01pp, phash = documented D117).
Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
- M-35-QOL-REVIEW.md: per-change file:area breakdown, config keys with
ranges/defaults, verification evidence.
- M-35-PLAYTEST-CHECKLIST.md: the human handoff — what to do, what
correct feels like, which key tunes it. Item 0 (default-config
regression) first.
- OPTIONS-MENU-PLAN.md: WI-3 design — recommends a port-layer fast3d 2D
overlay over a src/ menu edit; resume checklist.
Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
First behavioral src/ hook under the AGENTS.md #2 documented-exception
path. src/fr.c viShake() gets one #ifdef PORT line multiplying the
incoming amplitude by portScreenShakeScale (extern f32, registered
0.0-10.0 in port/src/video.c, committed with WI-1). Default 1.0f is an
exact no-op so headless golden dumps are byte-unaffected; 0.0 disables
explosion/effect screen shake. N64 build keeps the original line under
#else. findings D180/D181 + F-index.
Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
WI-1: Input.MouseCaptureMode (0 = legacy always-grab, default; 1 =
Quake/Nightdive-style click-to-lock). In mode 1 the OS cursor is free
until a click lands in the game window; ESC, focus loss, or entering
any front-end menu frees it again, and re-entering a stage while armed
re-locks so unpausing needs no click. Mouse buttons are withheld from
the game while the cursor is free in-stage (no phantom fire). Controller
input paths are untouched. reconcileGrab() runs once per controller-0
poll off the game's current_menu global (UI context only).
WI-2: when capture mode is on and the cursor is free in a menu, the
D165/D169 pointer P-controller takes its target from the real cursor's
absolute window position mapped onto the live virtual front-end rect
(getPlayer_c_screen*), for true 1:1 tracking. The relative-delta path
(legacy / grabbed) is unchanged.
B3: Input.MouseAimSpeed default 25 -> 16 (aim mode still overshot at 25).
Build 241/241; -level_09 framediff 3/3 within threshold (unregressed);
GE_STARTMENU menu boot crash-free. Feel-checks owed (headless can't
drive the mouse).
Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
Modelled on the PD port's configRegister{Float,UInt}. Needed for the
in-game video/options menu work (WI-3): sliders that store fractional
or unsigned values (sensitivity, screen-shake, fov). configSave() now
flattens all four option kinds into one section-grouped pass so mixed
int/float keys in the same [Section] stay together, and the
newly-registered-key migration check covers them.
Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
- 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
New observed-but-not-investigated entries from user QA:
- D173 third-person Bond model spawns too high (level start + Cuba credits)
- D174 no visible blood effect (likely unverified D120 decal fix)
- D175 in-game stutter/brief hang on door open (Runway, Surface)
- D176 skybox broken on Surface
- D177 ladders non-functional — progression blocker, high priority
- D178 pre-mission briefing objectives listed incorrectly
Cross-referenced to the nearest known finding families; next-step
pattern-match notes in GRAPHICS-BACKLOG.md.
Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
- README: lead with what it is + the AI-agent-collaboration research
framing + current status in the first paragraph; rename H1 to
"GoldenEye 007 PC Port"; add a live CI badge.
- New "How this differs from the other GoldenEye PC projects" section:
this is a source port of the N64 original; the Recomp projects
binary-translate the Xbox 360 XBLA remaster (different game, no shared
code).
- CITATION.cff for GitHub's "Cite this repository" widget / Zenodo.
- GitHub Pages site off /docs (_config.yml + index.md landing page).
- agentic-development.md: searchable title + abstract.
- .github/social-preview.{png,html,md}: repo social-preview card styled
after the GE mission-briefing screen, kept editable (CSS source + a
regen/how-to doc beside the PNG).
- docs/dev/game-behavior-reference.md: distilled the raw GameFAQs
walkthrough dump down to the behaviour-defining parts (combat/AI model,
difficulty scaling, per-level objective sets, timers, weapon data,
original-game quirks). Credits: Brett "Nemesis" Franklin, marshmallow.
Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT