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 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.
- 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.
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.
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.
- 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
- 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
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
- 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
Pre-spawn intro-camera views captured with GE_PCDUMP after the D168
flip fix (so they're upright). Not yet wired into the README - awaiting
placement direction (keep archives-1 / bunker1-1 as the top pair).
- attract-bunker1.png: control room + "Satellite Control Bunker" caption
- attract-silo.png: guards + toxic-waste drums, silo hangar behind
- attract-dam.png: guards on patrol, control panel, snowy peaks
Claude-Session: https://claude.ai/code/session_01UpTM1AcBRJuTetoZNcCD5V
Built the migrated tree (MSYS2 mingw64 + ROM + sidecar regen) and ran
-level_09 with GE_PCDUMP: HUD text and the exit arrow now read
right-side-up. D114/D116 confirmed as capture artifacts.
- tools_pc/golden/*.png regenerated from a fresh correctly-oriented run
(replaces the stopgap in-place flip); stray v*.png removed
- D75 shrunk: with upright captures the gun-barrel Bond RENDERS fine
(walk + fire) -- the "absent" reports were the flipped capture. The
Nintendo logo is genuinely broken (renders as 2 white blobs shifted
left -- a real logoinst transform bug, not a flip). Cast roll not
re-captured.
- D169 entry -> FIXED with the build/verify note
Claude-Session: https://claude.ai/code/session_01UpTM1AcBRJuTetoZNcCD5V
- Commit CLAUDE.md + AGENTS.md so a fresh clone has the operating manual and
the ABI/no-game-logic rules (trimmed of stale phase status; the personal
pd_port path removed; points at the split docs).
- Sweep every stale doc reference across the tree after the restructure:
PCPortResearch.md -> internals.md / dev/findings.md,
PORT-LEARNINGS.md -> porting-notes.md,
AGENT-WORKFLOW.md -> dev-process.md,
docs/{GRAPHICS-BACKLOG,HANDOFF-ARCHIVE,GE-ENV-PROBES,LEVEL-*,TEXTURE-GLITCH-ANALYSIS,AUDIT-M6-*,AUDIO-PLAN}.md -> docs/dev/.
- Drop the superseded docs/PLAN-linear-level-sweep.md.
- docs/HANDOFF.md + docs/BACKLOG.md stay local-only (rolling working files);
CLAUDE.md now handles their absence in a fresh clone.
Front-end mouse pointer can't reach the outer mission-select grid cells
(only an inner ~3x3). The D165 P-controller in port/src/input.c hard-codes
a 320x240 virtual field; GE's front end runs 440x330, so the pointer
target/estimate saturate at 300/220 before the grid's outer split points
(317 / 235.5). File-select/mode/main-menu unaffected (targets inside the
320x240 sub-box). Fix proposed (derive clamp bounds from
getPlayer_c_screenwidth/height, seed estimator from the real cursor) but
NOT applied - no build in the migrated tree to verify link + regression.
Claude-Session: https://claude.ai/code/session_01UpTM1AcBRJuTetoZNcCD5V
gfx_opengl_dump_bound_fbo wrote glReadPixels output (GL bottom-left
origin) straight into a top-row-first P6 PPM, so every GE_PCDUMP / F12
capture was upside-down. This is the entire source of the D114/D116
"HUD/text X-mirror" - four sessions chasing a fast3d mirror that never
existed, misreading inverted screenshots of asymmetric content. The
developer confirms the game renders correctly on real hardware.
- writer now emits rows top-to-bottom
- tools_pc/golden/*.png flipped in place to match + golden/README.md
- D114/D116 reclassified as capture-orientation artifacts (not deleted);
D75 "logo mispositioned" / RC1 "photo 180 deg" flagged for re-judging
- annotations in findings.md, GRAPHICS-BACKLOG.md,
TEXTURE-GLITCH-ANALYSIS.md, porting-notes.md D2, GE-ENV-PROBES.md
Not runtime-verified (migrated tree has no ROM/toolchain) - needs a
fresh capture to confirm text reads normally.
Claude-Session: https://claude.ai/code/session_01UpTM1AcBRJuTetoZNcCD5V
- README: "not playable yet" callout under the screenshots (no audio,
broken front-end models/text, expect crashes past the level intro).
- README/building.md/ci.yml: platform testing is Windows-only. The Linux
build is provided but has had zero functional testing at runtime; the
CI job is a CMake-configure smoke test, not a Linux-support claim.
Claude-Session: https://claude.ai/code/session_01UpTM1AcBRJuTetoZNcCD5V