Commit Graph
27 Commits
Author SHA1 Message Date
James Dansereau 7d90aca7d3 Merge remote-tracking branch 'origin/main' into docs/m42-graphics-honesty
# Conflicts:
#	docs/dev/findings.md
2026-09-03 10:18:06 -04:00
James Dansereau 2b7d3d5df4 Merge branch 'main' into docs/d176a-sky-stream-spec 2026-09-03 10:16:50 -04:00
James Dansereau 70d8ebdd4d Merge pull request #10 from jkdansereau/feat/f10-options-overlay
port: D184 — F10 in-game options overlay
2026-09-03 10:09:55 -04:00
James Dansereau ccff1374b5 Merge branch 'main' into feat/f10-options-overlay 2026-09-03 10:03:23 -04:00
James Dansereau fdf34a7dcd Merge branch 'main' into feat/alpha-asset-prep-tool 2026-09-03 10:03:05 -04:00
James Dansereau 1df53d4cf3 Merge branch 'main' into docs/d176a-sky-stream-spec 2026-09-03 10:03:02 -04:00
James Dansereau e3a3edd121 Merge branch 'main' into docs/m42-graphics-honesty 2026-09-03 10:00:52 -04:00
James Dansereau 2d7add1ef9 Merge branch 'main' into feat/f10-options-overlay 2026-09-03 01:15:26 -04:00
James Dansereau 475385edb1 docs(D176a): full RDPHALF sky-tri stream spec + two implementation paths
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.
2026-09-03 01:13:47 -04:00
James Dansereau 7863ec096f port: D186 — refuse a sub-30 Video.FpsCap instead of throttling the sim
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.
2026-09-03 00:51:40 -04:00
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 dae14c674c docs(D185): resolved — missing d88 step, not a d69 regression 2026-09-02 21:00:00 -04:00
James Dansereau 2a098eb3c4 docs(D185): fresh d69_emit.py run produces a crashing pccg sidecar — alpha blocker 2026-09-02 20:55:22 -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 2f5d7f9063 port: D184 — F10 port-layer options overlay (approach C)
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).
2026-09-02 19:03:37 -04:00
James Dansereau 7e2e371a0a docs(D177): mark ladders closed — PR #5 merged, climb test confirmed 2026-09-02 18:51:23 -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 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 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 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 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
James Dansereau 142e9c8f26 D168 verified; goldens regenerated; D75 shrunk; D169 marked fixed
Built the migrated tree (MSYS2 mingw64 + ROM + sidecar regen) and ran
-level_09 with GE_PCDUMP: HUD text and the exit arrow now read
right-side-up. D114/D116 confirmed as capture artifacts.

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

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

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

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

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

The pre-2026-08 history is the GoldenEye 007 decompilation by Larry Ficken
(KholdFuzion) and contributors, preserved unchanged. The PC-port work from
2026-08-17 on is mine.
2026-09-01 19:28:59 -04:00