Commit Graph
412 Commits
Author SHA1 Message Date
James Dansereau ea11bdf01f docs: D176(a) -level_22 headless repro; retire stale D74 wrap-block row
- 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.
2026-09-03 00:49:38 -04:00
James Dansereau e548dec129 Merge pull request #9 from jkdansereau/perf/release-opt
build: keep release -O2 instead of forcing -Og
2026-09-03 00:26:25 -04:00
James Dansereau 7549570bb5 build: keep release optimisation (-O2) instead of forcing -Og
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.
2026-09-03 00:18:45 -04:00
James Dansereau a0ba900d48 Merge pull request #8 from jkdansereau/feat/linux-ci-compile
Linux: CI compile job + romdata POSIX mmap
2026-09-03 00:17:20 -04:00
James Dansereau 05b89374e8 Merge branch 'main' into feat/linux-ci-compile 2026-09-03 00:13:40 -04:00
James Dansereau c346f5c18c Merge pull request #7 from jkdansereau/docs/dropin-release-plan-and-status
Harness: Local Qwen 3.8 dispatch
2026-09-03 00:13:26 -04:00
James Dansereau ff957c21fc linux: quiet the false-positive kernel heartbeat watchdog
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.
2026-09-03 00:00:07 -04:00
James Dansereau b49907ded9 build: opt-in ccache + BUILD_DIR override
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.
2026-09-02 23:54:36 -04:00
James Dansereau 20cd247084 fast3d: reliable pre-swap frame capture + fb_id 0 = window
- 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.
2026-09-02 23:54:35 -04:00
James Dansereau cd6af6c5fd linux-runtime: image base 0x20000000 to clear the N64 address ranges
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.
2026-09-02 23:16:53 -04:00
James Dansereau 7b9e078991 linux-runtime: declare getenv()/realloc() for the strict-host build
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.
2026-09-02 23:03:46 -04:00
James Dansereau fe3d543647 linux-runtime: BUNKER1 boots + renders on Linux
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.
2026-09-02 22:50:56 -04:00
James Dansereau 875b3c7653 linux-build: POSIX crashDumpThreads (watchdog thread dump)
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.
2026-09-02 22:35:37 -04:00
James Dansereau fe7a34c56e linux-build: port/shim/sched.h routes C++ TUs to the host <sched.h>
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).
2026-09-02 22:31:33 -04:00
James Dansereau 8eb2efa6ca linux-build: resolve host <stddef.h> via -print-file-name
<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).
2026-09-02 22:16:20 -04:00
James Dansereau 7c3bb42687 linux-build: -std=gnu extensions + fix remaining snprintf decls
- 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.
2026-09-02 22:12:58 -04:00
James Dansereau 59751d608c linux-build: POSIX branches for time()/bcopy/bzero and Win32-only probes
- 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.
2026-09-02 22:07:36 -04:00
James Dansereau 999bca81f5 linux-build: use host <strings.h> for bcopy/bcmp/bzero on the POSIX port
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).
2026-09-02 21:57:24 -04:00
James Dansereau 97b0539ef7 linux-build: cast chrflags->Model in vtxstore modelGetNodeRwData call
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.
2026-09-02 21:52:57 -04:00
James Dansereau 1077a2db23 linux-build: define wchar_t in the C-on-Linux <stddef.h> shim
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.
2026-09-02 21:48:44 -04:00
James Dansereau 9baf2c67f5 linux-build: PORT-guarded append_text_picked_up signature for GCC 14
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.
2026-09-02 21:47:11 -04:00
James Dansereau 31a0d6c42d linux-build: -fpermissive for GCC 14 hard-error promotions
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.
2026-09-02 21:43:09 -04:00
James Dansereau 12c3941fd3 linux-build: -fplan9-extensions for the 'inherits' idiom; pin gcc-14
- 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).
2026-09-02 21:36:15 -04:00
James Dansereau ccef9497ac port: Linux libc-header compat for <stdarg.h> and snprintf
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).
2026-09-02 21:31:21 -04:00
James Dansereau a8c5652dac port(stddef shim): Windows keeps the stub; Linux/macOS use GCC builtins
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.
2026-09-02 21:24:43 -04:00
James Dansereau 01fd39a089 port: route C TUs to the host <stddef.h>; link no-PIE on Linux
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).
2026-09-02 21:19:46 -04:00
James Dansereau 4e49f11813 ci: add Linux (Ubuntu/GCC) compile job
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).
2026-09-02 21:15:47 -04:00
James Dansereau b01c7fa89c port(romdata): POSIX fixed-address ROM map (mmap) for the Linux build
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.
2026-09-02 21:15:01 -04:00
James Dansereau ce9e090527 ci: skip build on docs-only pushes to main
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.
2026-09-02 20:21:06 -04:00
James Dansereau 0e1d430213 docs: drop-in-ROM release plan, mermaid rendering, Qwen dispatch guide
- 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.
2026-09-02 20:17:10 -04:00
James Dansereau fe9106baef docs: correct download status — no pre-built binaries yet
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.
2026-09-02 20:17:10 -04:00
James Dansereau 702dee61fc docs(D176): root-cause D176(a) black sky (RDPHALF no-op); D176(b) M-37 update 2026-09-02 19:07:55 -04:00
James Dansereau 8fff8bd258 Merge pull request #5 from jkdansereau/fix/d177-ladders
D177: fix non-functional ladders (progression blocker)
2026-09-02 18:47:32 -04:00
James Dansereau 293e7436d3 port: D177 — ladders non-functional (progression blocker), pointer-width ABI
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.
2026-09-02 18:26:38 -04:00
James Dansereau 65f91226a9 Merge pull request #3 from jkdansereau/qol/native-pc-input-menu
QoL: native-PC mouse capture + 1:1 menu pointer + options-menu foundation (D180/D181)
2026-09-02 18:11:16 -04:00
James Dansereau a01e732c55 Merge remote-tracking branch 'origin/main' into qol/native-pc-input-menu 2026-09-02 18:05:22 -04:00
James Dansereau 809e7378e1 port: D176(a) Surface black-sky investigation + GE_D176 probe (M-36, partial)
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.
2026-09-02 18:04:36 -04:00
James Dansereau 19715e0997 docs(pages): SEO setup + Cayman design pass
_config.yml:
- canonical url/baseurl for jekyll-seo-tag + jekyll-sitemap
- jekyll-seo-tag + jekyll-sitemap plugins; og:image link-preview card
- keep the raw engineering record (dev/ finding log, per-level status,
  triage notes) out of sitemap.xml; keep the two linked dev/ docs in

_includes/head-custom.html:
- Google/Bing Search Console verification tags
- full design pass over the stock Cayman theme: IBM Plex Sans/Mono,
  restrained amber-on-slate palette, light + dark themes, faded
  attract-mode screenshot header treatment, tightened typographic
  hierarchy, dark code blocks, quieter tables
2026-09-02 17:47:18 -04:00
James Dansereau 4069677b82 docs: M-36 triage — record Family A/B outcomes (B fixed, A disproven)
Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
2026-09-02 11:01:06 -04:00
James Dansereau 4b71435d73 port: D183 — fast3d source-pitch de-stride + texture diagnostics (M-36 Family A)
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
2026-09-02 11:00:11 -04:00
James Dansereau cd1ed57449 port: D178 — byte-swap the raw briefing segment (blank objectives / briefing text)
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
2026-09-02 10:56:03 -04:00
James Dansereau 9a69d57afd docs: M-36 triage sweep — bug families, correct the D159 mis-attribution
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
2026-09-02 10:06:56 -04:00
James Dansereau ef10985343 docs: sidecar build step + D176/D182 Surface/file-select bug reports (M-35)
- 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
2026-09-02 09:54:25 -04:00
James Dansereau 433907b566 port: absolute menu cursor + dual-pump event fix + auto window size (M-35)
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
2026-09-02 09:54:17 -04:00
James Dansereau 10b278311f docs: M-35 QoL run — review sheet, playtest checklist, options-menu plan
- 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
2026-09-02 01:30:01 -04:00
James Dansereau 849ee512cd port: Game.ScreenShakeIntensity route-(b) hook in viShake (D181)
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
2026-09-02 01:26:41 -04:00
James Dansereau ad74865404 port: click-to-lock mouse capture + absolute menu cursor + aim retune (D180)
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
2026-09-02 01:26:33 -04:00
James Dansereau 91b7c5aa4e port: config system — add Float/UInt option types with clamps
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
2026-09-02 01:26:24 -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 d55d5e065f findings: log D173–D178 from QA playtest reports
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
2026-09-02 00:27:21 -04:00