diff --git a/docs/dev/GE-ENV-PROBES.md b/docs/dev/GE-ENV-PROBES.md index 04a86235..464a9667 100644 --- a/docs/dev/GE-ENV-PROBES.md +++ b/docs/dev/GE-ENV-PROBES.md @@ -17,7 +17,7 @@ Two classes: | Env var | File:line | What it does | Status | |---|---|---|---| -| `GE_PCDUMP` | `port/fast3d/gfx_opengl.cpp:1079` (`is_pcdump`), `gfx_opengl.cpp:700`, `port/src/video.c:188`, `port/src/config.c:46` (`configGetFrameDump`) | Per-frame framebuffer PPM dump to `$PCDUMP` dir (verification-ritual golden captures, `tools_pc/framediff.py`/`pixcount.py`). Also settable via `[Debug] FrameDump` ini (env wins). | **live** | +| `GE_PCDUMP` | `port/fast3d/gfx_opengl.cpp:1079` (`is_pcdump`), `gfx_opengl.cpp:700`, `port/src/video.c:188`, `port/src/config.c:46` (`configGetFrameDump`) | Per-frame framebuffer PPM dump to `$PCDUMP` dir (verification-ritual golden captures, `tools_pc/framediff.py`/`pixcount.py`). Also settable via `[Debug] FrameDump` ini (env wins). **M-33/D168: the writer now emits rows top-to-bottom — captures before that fix are vertically flipped.** | **live** | | `GE_INPUTSCRIPT` | `port/src/input.c:306` | Headless scripted controller-0 input: `":,…;…"` (`SUP/SDOWN/SLEFT/SRIGHT/SNONE` sustain, buttons pulse). Sole input source when set. | **live** | | `GE_INPUTLOG` | `port/src/input.c:329`, `port/src/config.c:53` (`configGetInputLog`) | Logs computed pad state (buttons + stick) each poll when non-zero. Also `[Debug] InputLog` ini. Also traces D165 `menuptr est/tgt/eff/stick`. | **live** | | `GE_STARTMENU` (+ `GE_STARTMENU_PAGE`, `GE_STARTMENU_DIFF`) | `src/game/lv.c:398-402` (`#ifdef PORT`) | Boot straight into a front-end menu id (13=MISSION_COMPLETE, 10=BRIEFING, 7=MISSION_SELECT, 12=MISSION_FAILED, 6=MODE_SELECT); `_PAGE`=folder row (def 1=Dam), `_DIFF`=0..3. Crash-test a screen fast. | **live** | @@ -26,7 +26,7 @@ Two classes: | `GE_D160` | `src/boss.c:744`, `src/game/bondview2.c:791`, `src/aicommands.def:10055,10181,10236,10242,10247` (all `#ifdef PORT`) | Dam exit-cutscene (D148/D160) diagnostic: trace points in `bossReturnTitleStage`, `bondviewSetCameraMode`, and AI cmds `EndLevel`/`exit_level`/`CameraLookAtBondFromPad`/`CameraSwitch`. **Investigation in progress** — user owes a `GE_D160=1` Dam run. | **live** | | `GE_DTEX` | `port/fast3d/gfx_pc.cpp:1001` | Per-`import_texture` param log (dims / line / size / lod / gen_mipmaps). Used to root-cause RC2 and D159. Kept as an inert diagnostic. | **live** (RC2/RC3 not finished) | | `GE_TEXDUMP` | `port/fast3d/gfx_pc.cpp:1020`, `port/fast3d/gfx_opengl.cpp:700` | PPM dump of every uploaded texture + a `fmt/siz/palfmt/palidx/pal[0..3]` line. Root-caused D161 (Depot ceiling). Kept as an inert diagnostic. | **live** (RC3 not finished) | -| `GE_D116` | `port/fast3d/gfx_pc.cpp:1710,2362`, `src/game/textrelated.c:265,541` (cached `ge_d116`) | HUD/text X-mirror (D114/D116) vbo/uv trace. Bug is **parked / DO-NOT-re-static-trace** (PORT-LEARNINGS §D2) but the probe is cheap (cached int) — leave until that cosmetic bug is picked up with RenderDoc. | idle (parked bug) | +| `GE_D116` | `port/fast3d/gfx_pc.cpp:1710,2362`, `src/game/textrelated.c:265,541` (cached `ge_d116`) | HUD/text "X-mirror" (D114/D116) vbo/uv trace. **D114/D116 CLOSED — NOT A BUG (M-33/D168): the captures were upside-down, not mirrored.** Probe is now dead scaffolding — strip on the next cleanup pass. | dead (D114/D116 closed) | ## Dxx diagnostic probes — finding closed unless noted diff --git a/docs/dev/GRAPHICS-BACKLOG.md b/docs/dev/GRAPHICS-BACKLOG.md index 74ce6f0b..03cea743 100644 --- a/docs/dev/GRAPHICS-BACKLOG.md +++ b/docs/dev/GRAPHICS-BACKLOG.md @@ -8,12 +8,12 @@ once the game is playable start-to-finish without crashes. | ID | Symptom | Status / notes | |----|---------|----------------| -| **D114/D116** | HUD text + ammo digits render each glyph individually X-mirrored (screen positions correct, texture content flipped). Likely also explains "inverted" guard skins / mislocated door props. | Root cause NOT found after 4 sessions (M-6/7/8/11). Every stage `textRenderGlyph`→GBI→fast3d→`buf_vbo`→GL runtime-verified non-mirrored (`GE_D116` `[D116/vbo]` probe). Contradiction unresolved. **Next attempt requires a RenderDoc/apitrace capture of one glyph texrect, or the asymmetric-1-texel-texture experiment** — do NOT re-run the static trace, and do NOT apply a global texrect S-swap (mirrors the whole screen). | +| ~~**D114/D116**~~ | ~~HUD text + ammo digits render each glyph individually X-mirrored~~ | **CLOSED — NOT A BUG (M-33, §F D168).** The `GE_PCDUMP` PPM writer never reversed `glReadPixels` rows, so every capture was upside-down; "mirrored" was a misread of inverted asymmetric content. The game renders correctly on hardware (developer-confirmed). Writer fixed (`gfx_opengl.cpp`). "Every stage verified non-mirrored" was correct — there was no mirror. | | **D74 wrap-block** | `gfx_pc.cpp:1546/1551` PORT block is dead code: guard `cms & G_TX_WRAP` with `G_TX_WRAP==0` is always false; and it indexes per-texunit `[2]` arrays with the vertex index `i` (0..2) → OOB if reached. | Latent. Harmless for CLAMP glyphs; could affect wrapped textures on triangles (room panels, model skins). Fix: `cms == G_TX_WRAP`, and compute the tile window once per tile not per vertex. Verify against `tools_pc/golden/` after. | -| **D75** | Nintendo logo mispositioned; gun-barrel Bond model absent; cast-roll character models absent. (Rareware logo OK.) | Front-end 3D model path. Partially overlaps D114 (shared mirror on asymmetric content). Triage plan in `findings.md` §F D75. | +| **D75** | Nintendo logo mispositioned; gun-barrel Bond model absent; cast-roll character models absent. (Rareware logo OK.) | Front-end 3D model path. The "overlaps D114 shared mirror" note is withdrawn (M-33, D168 — D114 was a flipped capture). "Logo mispositioned" should be re-judged from a right-way-up capture; the absent-models half stands. Triage plan in `findings.md` §F D75. | | **D76** | 2D disclaimer screen only partially drawn. | §F D76. | | **D77** | No audible music on PC. | Phase 3 (audio). §F D77. | -| **D116 (HUD)** | (subset of D114) proportional-font strings + ammo counter mirrored. | Same root cause as D114. | +| ~~**D116 (HUD)**~~ | ~~proportional-font strings + ammo counter mirrored~~ | **CLOSED — NOT A BUG (M-33, §F D168).** Same capture-orientation artifact as D114. | | **D148** | Level-end cinematics don't play. Dam: hitting the exit trigger should show Bond rappelling down the dam (the bungee jump); instead it cuts straight to the mission-complete report. | Scripted cutscene / cinematic-camera + Bond rappel anim. Found M-27 during the first full Dam→Facility playthrough. Progression is unaffected — the report and next briefing load fine. **M-31 (§F D160):** static-traced the Dam end ailists (`ai_24`→`ai_17`, `camera_switch`→`CutsceneRecord` type-46 lookup). Diagnostic `GE_D160=1` shipped (`c95713f5`) — user runs Dam to the exit with it set; the trace pins whether the cutscene AI runs, and whether the propDef-index walk (D122/D132 family) hands `camera_switch` a bogus cutscene record. Leading hypothesis: propDef `sizepropdef` stride desync (medium confidence), not D75 anim-model. | | **D149** | Front-end MISSION COMPLETE / mode-select 3D models (dossier, wallets) render as garbled full-screen geometry or not at all — one of the compiled sub-DLs (seg5+0x9ee4) is malformed, D146 now skips it instead of aborting. | D75 family. §F D146. | diff --git a/docs/dev/TEXTURE-GLITCH-ANALYSIS.md b/docs/dev/TEXTURE-GLITCH-ANALYSIS.md index 95d07615..74701710 100644 --- a/docs/dev/TEXTURE-GLITCH-ANALYSIS.md +++ b/docs/dev/TEXTURE-GLITCH-ANALYSIS.md @@ -7,6 +7,16 @@ Companion artifacts: `` (outside repo). ## 0. Current status (M-31) — READ THIS FIRST +> **M-33 overlay (D168):** every `GE_PCDUMP` / F12 screenshot this doc's +> analysis relied on was **vertically flipped** — the PPM writer never reversed +> `glReadPixels` rows. This is what the D114/D116 "HUD/text X-mirror" finding +> actually was (now closed, not a bug). It also contaminates the **RC1** row +> below: the wallet-Bond photo's "180°-rotated / mis-transformed" description +> was made from an upside-down capture, so at least the vertical component is +> the capture bug, not the model transform. Re-judge RC1 from a fresh +> right-way-up capture before doing any more work on it. Writer fixed +> (`port/fast3d/gfx_opengl.cpp`). + The 2026-08-29 body below is the original diagnosis. Several root causes have since been fixed or retracted. Authoritative current state: diff --git a/docs/dev/findings.md b/docs/dev/findings.md index ca835b90..62df6be3 100644 --- a/docs/dev/findings.md +++ b/docs/dev/findings.md @@ -380,13 +380,14 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D59–D68 | intro render: blood-RLE clobber, DMA validate, OSMesgQueue, HEADS/BODIES sentinels, romCopy width, image_entry, Globalimagetable BE→LE | resolved | | D69 · D78–D84 | stage load (`load_bg_file`): bg/stan offline sidecar (`d69_emit.py`) + StandTile/bg_room_data ABI | resolved | | D70–D74 | intro-logo pixels: C-array bswap, UV path, sinf/cosf `DVAL()`, texture-import truncation | resolved | -| D75 · D77 | front-end 3D model transforms · audio | OPEN (parked). **M-32 triage (see §F "D75 ADDENDUM"): (a) D73-scope-gap RULED OUT (gu tree fully endian-clean). Splits in two: Bug 1 = logo/photo transform = the parked D114/D116 fast3d viewport mirror (not game code). Bug 2 = absent animated models = category (b). M-32b runtime probe (`GE_D75=1`): `render_pos`/`dynAllocate`-arena hypothesis RULED OUT (render_pos valid + fresh each frame), model instances valid (nMtx 21/1), zero fast3d DL warnings — failure is downstream in `drawjointlist`/`dotube` vtx/node-DL resolution or an off-screen `basemtx`. Needs a drawjointlist-level probe.** | +| D75 · D77 | front-end 3D model transforms · audio | OPEN (parked). **M-32 triage (see §F "D75 ADDENDUM"): (a) D73-scope-gap RULED OUT (gu tree fully endian-clean). Splits in two: Bug 1 = logo/photo transform = the parked D114/D116 fast3d viewport mirror (not game code). Bug 2 = absent animated models = category (b). M-32b runtime probe (`GE_D75=1`): `render_pos`/`dynAllocate`-arena hypothesis RULED OUT (render_pos valid + fresh each frame), model instances valid (nMtx 21/1), zero fast3d DL warnings — failure is downstream in `drawjointlist`/`dotube` vtx/node-DL resolution or an off-screen `basemtx`. Needs a drawjointlist-level probe. M-33: the "Bug 1 = D114/D116 fast3d viewport mirror" half is retracted — D114/D116 were an upside-down capture (D168). "Logo mispositioned" needs re-checking against a right-way-up capture; the absent-models half (Bug 2) stands.** | | D76 · D164 | disclaimer/legal screen only draws line 1 — **root-caused (M-31)**: `constructor_menu00_legalscreen` text loop bounds `legal_text_end` on `&legalscreen_MRD`, a linker-adjacency assumption that mingw breaks (`legalscreen_MRD` links 0x60 *before* `legalpage_text_array`) → `do{}while(ptr **M-33 CORRECTION (D168): this finding is retracted as a capture artifact.** +> Every `GE_PCDUMP` PPM was written upside-down (`gfx_opengl_dump_bound_fbo` +> did not reverse `glReadPixels` rows). The developer confirms the game renders +> correctly on real hardware. The "shared fast3d mirror" this and the D116 +> entries chase never existed — the probes kept coming back clean because there +> was nothing wrong; the analysts were reading inverted screenshots of +> asymmetric content (guards, text, logo) and calling them "mirrored". The PPM +> writer is fixed. The genuinely-open item that remains is D75 Bug 2 (front-end +> animated models absent), which is unrelated. Original text kept below for +> history. + **D114 (session M-6) — character inversion / mispositioned crate: OPEN, matrix chain verified sane, residual is a shared fast3d mirror (D75 class).** A subagent statically traced the whole joint-matrix chain @@ -3335,6 +3347,13 @@ watch-preview Model pool (`watchRwPool[0xC8]`) is N64-sized and the inline Model overruns live watch fields. See the audit doc for the full table and recommended fix order. +> **M-33 CORRECTION (D168): retracted as a capture artifact — see the D114 +> correction above and D168.** "Every stage probed is clean, yet the output is +> mirrored" was the tell: the output was not mirrored, it was upside-down (the +> PPM writer never reversed `glReadPixels` rows). No `GE_D116` S-swap / U-flip +> was ever warranted. Probe scaffolding (`GE_D116` in `textrelated.c` / +> `gfx_pc.cpp`) can be stripped. Original text kept below for history. + **D116 (session M-7) — the HUD/menu text mirror (D75 class) is a per-glyph TEXTURE-space flip, NOT a screen/framebuffer/matrix mirror.** Rebuilt at D115 (`14b6b432`), build green, captured `-level_09` frames diff --git a/docs/porting-notes.md b/docs/porting-notes.md index 46d55743..9617910c 100644 --- a/docs/porting-notes.md +++ b/docs/porting-notes.md @@ -176,8 +176,10 @@ through a converter or a runtime bswap fixup reads scrambled. `matrixmath.c` (`matrix_4x4_set_lookat*`, `_set_projection`, `_f32_to_s32`) is likewise native-LE (D114). **Do not re-audit gu or matrixmath for a "float endianness" bug** — if a matrix comes out wrong - on PC the cause is upstream data, a struct-field pun (D100/D140/D156), or - a fast3d gap (D114/D116 viewport mirror), never these files. + on PC the cause is upstream data, a struct-field pun (D100/D140/D156), + never these files. (The "D114/D116 viewport mirror" example is withdrawn — + M-33/D168 showed D114/D116 were an upside-down `GE_PCDUMP` capture, not a + real flip.) - Header offset tables / pointers: D54 (cseq ALMidiHdr), D68 (Globalimagetable), D87 (ramromfilestructure), D88 (Usetup* tables). - Negative-terminated index chains (`PointUsage[]`) in converted model @@ -372,19 +374,23 @@ through a converter or a runtime bswap fixup reads scrambled. indexed by texunit, gated on `Video.WrapFix`); RC3/D167 adds the non-PoT mask-period case to the same block. Still default OFF. -## D2. The HUD/model X-mirror (D114/D116) — DO NOT re-static-trace +## D2. The HUD/model "X-mirror" (D114/D116) — RESOLVED: it was an upside-down capture -Sessions M-6/M-7/M-8/M-11 all reached the same wall: `textRenderGlyph` -→ GBI → fast3d `gfx_dp_texture_rectangle` → `buf_vbo` → GL are **each -runtime-verified non-mirrored** (`[D116/vbo]` probe: x-left↔u=0), yet -glyphs render X-flipped. M-11 additionally confirmed the ammo digits use -the *same* `textrelated.c` path (via `gunfire.c:5906`), no separate -renderer. Every prior attempt to "fix" it drifts toward a global -S-swap that mirrors the whole screen — a bad trade for a cosmetic bug. -**Next attempt needs a RenderDoc/apitrace capture of one glyph texrect -OR the asymmetric-1-texel-texture experiment — nothing else.** Cosmetic, -deprioritised below level-progression / crash work. See -`docs/dev/GRAPHICS-BACKLOG.md`. +**M-33 (finding D168).** There was no mirror. `gfx_opengl_dump_bound_fbo` +(`port/fast3d/gfx_opengl.cpp`) wrote `glReadPixels` output — bottom-row-first, +GL origin — straight into a top-row-first P6 PPM, so **every `GE_PCDUMP` and +F12 capture was vertically flipped**. Sessions M-6/M-7/M-8/M-11 kept finding +`textRenderGlyph` → GBI → fast3d → `buf_vbo` → GL **each verified +non-mirrored** — because nothing *was* mirrored; they were staring at +upside-down screenshots of asymmetric content (text, ammo digits, guard skins, +the Nintendo logo) and reading "inverted" as "X-mirrored". The developer +confirms the game renders correctly on real hardware. + +The lesson worth keeping: **when every stage of a pipeline probes clean but +the output "looks wrong", suspect the observation tool before adding a +correction.** A cosmetic defect that no probe can localise after four sessions +is a strong signal that the defect isn't in the code. Fix: PPM writer now +emits rows top-to-bottom; `tools_pc/golden/*.png` were flipped to match. ## D3. GCC/mingw makes an all-non-negative `enum` UNSIGNED diff --git a/port/fast3d/gfx_opengl.cpp b/port/fast3d/gfx_opengl.cpp index 9f75aba4..66e5aea1 100644 --- a/port/fast3d/gfx_opengl.cpp +++ b/port/fast3d/gfx_opengl.cpp @@ -1087,7 +1087,15 @@ extern "C" bool gfx_opengl_dump_bound_fbo(uint32_t width, uint32_t height, const FILE* f = fopen(path, "wb"); if (!f) { free(pixels); return false; } fprintf(f, "P6\n%u %u\n255\n", width, height); - fwrite(pixels, 1, n, f); + /* D168: glReadPixels() returns rows bottom-to-top (GL framebuffer origin is + * bottom-left); PPM P6 is top-row-first. Emit rows in reverse so the capture + * is the right way up. Previously written unreversed -> every GE_PCDUMP was + * vertically flipped (the source of the bogus D114/D116 "text X-mirror"). */ + { + size_t stride = (size_t)width * 3; + for (uint32_t row = 0; row < height; row++) + fwrite(pixels + (size_t)(height - 1 - row) * stride, 1, stride, f); + } fclose(f); free(pixels); return true; diff --git a/tools_pc/golden/README.md b/tools_pc/golden/README.md new file mode 100644 index 00000000..89445823 --- /dev/null +++ b/tools_pc/golden/README.md @@ -0,0 +1,19 @@ +# Golden baseline frames + +`frame_NNNNNN.png` — reference `GE_PCDUMP` captures for `framediff.py` +(`-level_09` BUNKER1 intro pan). Compared structurally, not exactly — the port +is not frame-deterministic (see `docs/internals.md` "D117"). + +## D168 re-orientation (2026-09-01) + +The `GE_PCDUMP` PPM writer (`port/fast3d/gfx_opengl.cpp +gfx_opengl_dump_bound_fbo`) used to emit `glReadPixels` rows unreversed, so +every capture — and therefore these goldens — was **vertically flipped**. The +writer now emits rows top-to-bottom. The PNGs in this directory were flipped +vertically in place to match; content is otherwise unchanged. Regenerate them +properly from a fresh run when convenient: + +```sh +GE_PCDUMP="200-440:120" ./build-pc/ge007.x86_64.exe -level_09 +python tools_pc/framediff.py ppm --update +``` diff --git a/tools_pc/golden/frame_000200.png b/tools_pc/golden/frame_000200.png index 832c303e..065fadca 100644 Binary files a/tools_pc/golden/frame_000200.png and b/tools_pc/golden/frame_000200.png differ diff --git a/tools_pc/golden/frame_000320.png b/tools_pc/golden/frame_000320.png index 40fbec8d..6180666a 100644 Binary files a/tools_pc/golden/frame_000320.png and b/tools_pc/golden/frame_000320.png differ diff --git a/tools_pc/golden/frame_000440.png b/tools_pc/golden/frame_000440.png index da67c52b..d1b6033f 100644 Binary files a/tools_pc/golden/frame_000440.png and b/tools_pc/golden/frame_000440.png differ