From 978306d14ff1e5cfe9bc407c069785f978b9e24e Mon Sep 17 00:00:00 2001 From: James Dansereau <106631954+jkdansereau@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:32:27 -0400 Subject: [PATCH] D73+D74: intro logo fixed (sinf/cosf endianness + texture import/sub-tile wrap) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D73 (root cause of old D72.3): GE's sinf/cosf build their double constants through the du bit-union with big-endian word pairs {hi,lo}; on LE PC the .d read has the words swapped (rpi.d ~ 2^733), range reduction overflows, and FTOFIX32 saturates to -32768 for every sin/cos entry of guMtxF2L — so the Rareware logo's rotation matrix was garbage and all its triangles projected off-screen. Fix: DVAL() macro in src/libultra/gu/guint.h (PORT-only; #else branch is the original expression) re-packing hi/lo into the correct LE double; sinf.c/cosf.c .d reads replaced. N64 build untouched. D74 (texture side, three port-layer bugs in fast3d): - import_texture's fallback condition (rdp.tex_lod && tile >= first+detail) overwrote valid gDPLoadBlock tmem data with line*tile.height: letter mip chains 2744B -> 2048B (mips 1-5 dropped), D_02005FF0 32x32 -> 32x3 (stale tile.height from gsDPSetTileSize). Now falls back only when the tmem slot was never written. - TextureCacheKey gains size_bytes so a truncated first import cannot poison later full imports of the same address. - VBO UV path now wraps by TILE size for WRAP sub-tiles (N64 semantics); the logo's 20x3 tile at offset (11.5,29) was sampling row 0 instead of rows 29-31. Per-vertex pre-wrap; known limits: half-texel edge bleed, mirror sub-tiles unhandled. Verified: clean runs reach frame ~2100 then hit the D69 load_bg_file fault (unchanged — still the milestone blocker); PPM frame ~555 shows the four gold RARE letters on the dark-blue plate (final pixel-perfect check vs N64 footage left as a low-priority task). All env-gated probes from this session (GE_DBGUV/DBGTRI/DBGTALL/DBGMAT/D74IMP/D74DUMP/ GE_DBGLOAD) stripped; previously committed TEMP D63 blocks + GE_D71LOG remain on the HANDOFF strip list. Docs: PCPortResearch.md §F D73/D74 entries, §G/§H status updated; HANDOFF.md rewritten for Session I (D69 primary); AGENTS.md phase line. --- AGENTS.md | 2 +- docs/HANDOFF.md | 139 ++++++++++++++++++++-------------------- docs/PCPortResearch.md | 138 ++++++++++++++++++++++++++++++--------- port/fast3d/gfx_pc.cpp | 94 +++++++++------------------ port/fast3d/gfx_pc.h | 4 ++ src/libultra/gu/cosf.c | 8 +-- src/libultra/gu/guint.h | 15 +++++ src/libultra/gu/sinf.c | 10 +-- 8 files changed, 237 insertions(+), 173 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index fffbb77f..30840511 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,6 +48,6 @@ Run `/linkcheck` for this sweep. Record new findings in `docs/PCPortResearch.md` - **Compile+link milestone:** all ~235 TUs compile and `ge007.x86_64.exe` links clean (`ninja ge007 -k 0`, 236/236). Findings D7–D17 in `docs/PCPortResearch.md` §11; asset stubs in `port/src/assetstubs.c`, fast3d no-ops in `port/src/gfxstub.c` (delete when real fast3d lands). - **Phase 1 (boot to window):** done — ROM loads/validates/maps at cart base 0x10000000 (`romdata.c`), SDL2 window + GL clear loop (`video.c`), absolute asset symbols from `scripts/gen_romassets.py` → `port/src/romassets_.s` (findings D18–D23). - **Phase 1.5 (boot to first frame):** DONE — boot chain complete AND real frames render (`[NOTE] frame N rendered in … us`). D34–D42 closed the blockers on top of D31–D33: ANIM_DATA lvalues, music seq-table ABI+endianness, music-heap sizing, libaudio bank-tree re-layout, implicit-declaration prototype shim (D38), Globalimagetable rebasing + true segment size (D39), ModelHitEntry pool sizing (D40), cross-thread GL context release (D41), rsp task-settings toggle (D42). ROM dump verified against the No-Intro good dump (byte-identical, header CRCs re-checked — `tools_pc/romverify.c`). -- **Phase 2 (rendering):** IN PROGRESS — fast3d integrated + real frames render. **Plan B EXECUTED (D50, committed):** all 512 NTSC model files converted offline by `tools_pc/d43_emit.py` → `data/pcmodels-ntsc-final/pcmodels.bin` + manifest.csv (single concatenated RZ image; regenerate per region with the script — sidecars are gitignored), served through the existing load path via a port-layer table patch (`port/src/pcmodels.c`, romdata cart-extension, one-shot `pcmodelsPatchTable()` + poolRemaining reset hook in `load_object_fill_header`); runtime C fixups for language-bank BE offset tables + font re-layout (D50.3/D50.4); legal-screen UB seed (D50.5); **D50.6 fixed the first model-render crash** — `texCopyGdls` copied only w0 of each 16-byte Gfx slot on PC (the union's trailing `long long` is 8 bytes), leaving stale mempool w1s in the compacted GDLs; byte-proven against PlegalpageZ, fixed with a full-slot copy under #ifdef PORT. Model GDLs now execute to completion (PlegalpageZ sub-DLs). **D51 RESOLVED** (font `pixeldata` fixup wrote the pointer at the wrong offsets — frame-5 `import_texture_i8` crash gone; GE opcode space decoded along the way, G_TEXRECT=0xE4/0xE5 non-F3DEX numbering), **D52 RESOLVED** (`osGetCount` now advances at the N64 RSP counter rate ≈ 46.5525 ticks/µs — fixed the non-deterministic post-frame-2 hang), **D53 RESOLVED** (model RW-data pool addressing: D53.1 `Model.datas` word-stride → `u32 *` under PORT + casts; D53.2 ModelSlot/AnimModelSlot re-laid on top of the PC struct Model — in-use@0x10 / pool@0x20, slot size = sizeof(Model) = 0xE8 — plus PORT-guarded heap-fallback bumps in model.c), **D54 RESOLVED** (cseq ALCMidiHdr BE header: port-layer `romdataFixupCseq` bswap32 of the first 17 u32s, called from music.c after each decompress), **D54b RESOLVED** (synth param slots sized for ALStartParamAlt = 0x28 in synthesizer.c alSynInit under PORT), **D55 RESOLVED** (RLE folder-menu background BE w/h header: bswap32 of the first word at `(u32 *)&unknown2` at cart-map time in romdataInit, guarded). Build GREEN; game boots, plays intro music, renders through logo/gun-barrel, instantiates Brosnan, and executes the gun-barrel hole DL clean to ENDDL. **D56 RESOLVED** (watch Model/RW-pool raw offsets: field-name Model access + static 0xC8-byte pool under PORT in bondview2.c), **D57 RESOLVED** (pointer-grown rwdata records overflowed N64-sized spare pools — Brosnan PC numRecords 153 > 140: capacities grown in initunk_005520.c + dynamic slot+pool fallback in model.c), **D58 RESOLVED** (gun-barrel DL: vertex-pointer K0 idiom rebuilt via `OS_K0_TO_PHYSICAL(…) | 0x80000000` in title.c + DL reserve 0x100→0x200 because `sizeof(Gfx)` is 16 on PC). **D59–D68 RESOLVED (committed):** the "GL DLL" crash was D64's unbounded blood-RLE write (sentinel section-adjacency lost on PC) clobbering the gun-barrel sub-DL; then D60–D62 (DMA target validation, OSMesgQueue layout), D65/D65b (HEADS/BODIES enum signed sentinels under PORT), D66 (romCopyAligned + ramrom replay 64-bit pointer width), D67 (struct image_entry N64 layout), **D68 (Globalimagetable BE→LE fixup** — `port/src/gimgfixup.c` bswaps IMAGESEG Gfx w1 words + sImageTableEntry.index after texReset's romCopy; compiled globalDL shadows synced for explosion.c). The game now renders the **entire ~3.5-minute intro** (logo → gun barrel w/ Brosnan → cast) at ~59 fps. **Current blocker D69:** `load_bg_file` (bg.c:830) faults on first stage load — BG-file headers/records are N64 big-endian segment-0x0F offsets, read LE on PC; the bg `.seg` + `Tbg_*_stanZ` format work is next (decode from bg.c/stan.c → offline sidecar conversion per the D43 pattern or runtime port-layer fixup; PD `preprocess/filebg.c` is approach-reference only — its BG format differs). **D70–D72 (intro-logo pixels):** D70 env-gated PPM frame capture (`GE_PCDUMP` → `./ppm/`, gitignored); **D71 RESOLVED** — C-array texture sources (the four rarewarelogo.c RGBA16 images) byte-swapped on LE PC → pink/green logo; port-layer per-source bswap in `import_texture`; **D72 RESOLVED** the UV path (GE always uses authored `tc[]` UVs — no G_TT_BASE/CLAMP anywhere; `lookat_enabled` defaults false since N64 boots with RSP mem zeroed) but **D72.3 OPEN**: all logo triangles project off-screen and a flat dark-blue checkerboard triangle rasterizes instead (identity pending via `GE_DBGUV`/`GE_DBGTALL` traces); D69 remains the milestone blocker. Committed through D72. Recurring sub-task: ABI/layout fixes per the D32 procedure (§H) as more asset types are first touched (PD's `preprocess/` module already solves most of them; prefer the offline pattern for format work — D48; full-struct Gfx copies on PC — D50.6; audit struct type-puns after pointer-width changes — D53.2; raw byte offsets into pointer-containing structs shift on PC — D56; N64-sized reservations must be re-checked against 16-byte Gfx/Vtx widths — D58). +- **Phase 2 (rendering):** IN PROGRESS — fast3d integrated + real frames render. **Plan B EXECUTED (D50, committed):** all 512 NTSC model files converted offline by `tools_pc/d43_emit.py` → `data/pcmodels-ntsc-final/pcmodels.bin` + manifest.csv (single concatenated RZ image; regenerate per region with the script — sidecars are gitignored), served through the existing load path via a port-layer table patch (`port/src/pcmodels.c`, romdata cart-extension, one-shot `pcmodelsPatchTable()` + poolRemaining reset hook in `load_object_fill_header`); runtime C fixups for language-bank BE offset tables + font re-layout (D50.3/D50.4); legal-screen UB seed (D50.5); **D50.6 fixed the first model-render crash** — `texCopyGdls` copied only w0 of each 16-byte Gfx slot on PC (the union's trailing `long long` is 8 bytes), leaving stale mempool w1s in the compacted GDLs; byte-proven against PlegalpageZ, fixed with a full-slot copy under #ifdef PORT. Model GDLs now execute to completion (PlegalpageZ sub-DLs). **D51 RESOLVED** (font `pixeldata` fixup wrote the pointer at the wrong offsets — frame-5 `import_texture_i8` crash gone; GE opcode space decoded along the way, G_TEXRECT=0xE4/0xE5 non-F3DEX numbering), **D52 RESOLVED** (`osGetCount` now advances at the N64 RSP counter rate ≈ 46.5525 ticks/µs — fixed the non-deterministic post-frame-2 hang), **D53 RESOLVED** (model RW-data pool addressing: D53.1 `Model.datas` word-stride → `u32 *` under PORT + casts; D53.2 ModelSlot/AnimModelSlot re-laid on top of the PC struct Model — in-use@0x10 / pool@0x20, slot size = sizeof(Model) = 0xE8 — plus PORT-guarded heap-fallback bumps in model.c), **D54 RESOLVED** (cseq ALCMidiHdr BE header: port-layer `romdataFixupCseq` bswap32 of the first 17 u32s, called from music.c after each decompress), **D54b RESOLVED** (synth param slots sized for ALStartParamAlt = 0x28 in synthesizer.c alSynInit under PORT), **D55 RESOLVED** (RLE folder-menu background BE w/h header: bswap32 of the first word at `(u32 *)&unknown2` at cart-map time in romdataInit, guarded). Build GREEN; game boots, plays intro music, renders through logo/gun-barrel, instantiates Brosnan, and executes the gun-barrel hole DL clean to ENDDL. **D56 RESOLVED** (watch Model/RW-pool raw offsets: field-name Model access + static 0xC8-byte pool under PORT in bondview2.c), **D57 RESOLVED** (pointer-grown rwdata records overflowed N64-sized spare pools — Brosnan PC numRecords 153 > 140: capacities grown in initunk_005520.c + dynamic slot+pool fallback in model.c), **D58 RESOLVED** (gun-barrel DL: vertex-pointer K0 idiom rebuilt via `OS_K0_TO_PHYSICAL(…) | 0x80000000` in title.c + DL reserve 0x100→0x200 because `sizeof(Gfx)` is 16 on PC). **D59–D68 RESOLVED (committed):** the "GL DLL" crash was D64's unbounded blood-RLE write (sentinel section-adjacency lost on PC) clobbering the gun-barrel sub-DL; then D60–D62 (DMA target validation, OSMesgQueue layout), D65/D65b (HEADS/BODIES enum signed sentinels under PORT), D66 (romCopyAligned + ramrom replay 64-bit pointer width), D67 (struct image_entry N64 layout), **D68 (Globalimagetable BE→LE fixup** — `port/src/gimgfixup.c` bswaps IMAGESEG Gfx w1 words + sImageTableEntry.index after texReset's romCopy; compiled globalDL shadows synced for explosion.c). The game now renders the **entire intro** (logo → gun barrel w/ Brosnan → cast; ~frame 2100, ≈2 min wall-clock at the current ~20 fps clean-run rate). **Current blocker D69:** `load_bg_file` (bg.c:830) faults on first stage load — BG-file headers/records are N64 big-endian segment-0x0F offsets, read LE on PC; the bg `.seg` + `Tbg_*_stanZ` format work is next (decode from bg.c/stan.c → offline sidecar conversion per the D43 pattern or runtime port-layer fixup; PD `preprocess/filebg.c` is approach-reference only — its BG format differs). **D70–D74 (intro-logo pixels, RESOLVED):** D70 env-gated PPM frame capture (`GE_PCDUMP` → `./ppm/`, gitignored — kept as permanent dev tooling); **D71** C-array texture sources (the four rarewarelogo.c RGBA16 images) byte-swapped on LE PC → port-layer per-source bswap in `import_texture`; **D72** the UV path (GE always uses authored `tc[]` UVs; `lookat_enabled` defaults false); **D73 = root cause of old D72.3** — sinf/cosf `du` double constants are big-endian word pairs, garbage on LE PC → guMtxF2L emitted −32768 for every sin/cos entry → logo triangles projected off-screen; fixed with the PORT-only `DVAL()` macro in `src/libultra/gu/guint.h` (sinf.c/cosf.c `.d` reads replaced; N64 build untouched); **D74** texture import fallback no longer truncates valid gDPLoadBlock data (was dropping letter mip chains + reducing D_02005FF0 to 32×3), `TextureCacheKey` gained `size_bytes`, and the VBO path wraps UVs by tile size for WRAP sub-tiles (N64 semantics). The Rareware logo now renders its four gold letters on the dark-blue plate (PPM-verified ~frame 555; final pixel-perfect check vs N64 footage optional). D69 remains the milestone blocker. Committed through D74. Recurring sub-task: ABI/layout fixes per the D32 procedure (§H) as more asset types are first touched (PD's `preprocess/` module already solves most of them; prefer the offline pattern for format work — D48; full-struct Gfx copies on PC — D50.6; audit struct type-puns after pointer-width changes — D53.2; raw byte offsets into pointer-containing structs shift on PC — D56; N64-sized reservations must be re-checked against 16-byte Gfx/Vtx widths — D58). - **Phase 3 (audio + input):** libaudio → SDL — largely copy-and-adapt from PD (`mixer.c` 722 lines vs our 31-line stub; `input.c` SDL_GameController backend; §2.4); decide the ASP strategy (C2: `aspMain*` dummies in `ucode.c`). - **Phase 4 (saves + polish):** file-backed EEPROM — build on PD's `fs.c` (file-backed save dir); PFS/motor already no-op stubs. diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 79c3ed3e..e98ecf08 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -1,62 +1,55 @@ -# Handoff brief — GoldenEye 007 PC port (Phase 2: Session H — D72.3 intro-logo pixels + D69 BG/stan stage loading) +# Handoff brief — GoldenEye 007 PC port (Phase 2: Session I — D69 BG/stan stage loading) _Paste-ready briefing. Authoritative detail lives in `AGENTS.md` and -`docs/PCPortResearch.md` (§F/D59–D69 findings, §G status, §H procedure); this +`docs/PCPortResearch.md` (§F/D59–D74 findings, §G status, §H procedure); this is the summary + the immediate tasks._ ## Your job -- **Session H:** two live threads, both verified reproducible: - 1. **D72.3 (intro logo pixels):** after D71 (logo byte order) + D72 (tc[] - UVs), the Rareware logo still doesn't appear — all its triangles project - off-screen, and a flat dark-blue checkerboard triangle (broken - rasterization of something else) fills the lower screen instead. Tools - are in place: `GE_PCDUMP` frame capture + `GE_DBGUV`/`GE_DBGTALL` - traces in gfx_pc.cpp. Find which triangle draws the checkerboard, then - check MP matrix + viewport at logo time (title.c - load_display_rare_logo). - 2. **D69 (milestone):** make the first stage (BUNKER1) load and render — - BG-file data is N64 big-endian; reverse-engineer GE's bg `.seg` + - `Tbg_*_stanZ` formats from the decompiled consumers, then convert/fix - for PC. - Work agentically: decode → implement → build (~5 s) → verify → commit at - each sub-milestone (document findings as D6x/D7x in §F). +- **Session I — D69 (the milestone blocker):** make the first stage (BUNKER1) + load and render. BG-file data is N64 big-endian; reverse-engineer GE's bg + `.seg` + `Tbg_*_stanZ` formats from the decompiled consumers, then convert + or fix them for PC. This is the only thing standing between the intro and + gameplay (verified: clean runs reach frame ~2100 in ≈2 min wall-clock, then + fault deterministically in `load_bg_file`, bg.c:830). +- **Secondary (low priority):** final pixel check of the intro logo. D73 + + D74 fixed it at the data level and PPM frames ~550–560 show four gold + letters on the dark-blue plate; compare against N64 reference footage if + time allows (reference screenshots are at repo root: `Graphics Screenshot + 2026-08-24 121659.jpg`, `gun barrel.png`). +- Work agentically: decode → implement → build (~5 s) → verify → commit at + each sub-milestone (document findings as D7x in §F). ## Current state (verified this session) - **Build GREEN.** `export PATH="/c/msys64/mingw64/bin:$PATH" && ./build-pc.sh ntsc-final`. -- **D59–D68 all resolved** (details in PCPortResearch.md §F). Highlights: - - **D59/D64** — the old "GL DLL" crash was an unbounded blood-RLE write - (sentinel `die_blood_image_end` lost its N64 section adjacency on PC) - clobbering the gun-barrel sub-DL. Sentinel now defined as one-past-the- - end of the array under PORT. - - **D65/D65b** — `enum HEADS`/`BODIES` sentinels: N64 semantics were signed - (`HEAD_FIXED == -1`); PC GCC 16 picks unsigned underlying types, deleting - the guard branches. Negative literals under PORT restore the bit pattern - with signed semantics. - - **D66** — `romCopyAligned` + ramrom replay did s32 pointer math; targets - in `.bss` > 4 GiB truncated to wild addresses. 64-bit version under PORT, - returns `void *`. - - **D67** — `struct image_entry` re-laid (all-u32 bitfields, `dataoffset:24` - first) so texLoad's raw word read + chrprop.c's 8-byte stride agree; - sizeof == 8 on both targets. - - **D68** — Globalimagetable BE→LE fixup (`port/src/gimgfixup.c`): bswap the - IMAGESEG-marked Gfx w1 words (17 DLs) + `sImageTableEntry.index` (32 - tables, counts from D39 layout) after texReset's romCopy; marker scan in - `texLoadFromDisplayList` moved to bytes 6..7 under PORT; compiled - globalDL shadows synced after texLoad (`gimgSyncCompiledGlobalDLs`). -- **D70–D72 (intro logo pixels, latest session):** D70 env-gated PPM frame - capture (`GE_PCDUMP` → `./ppm/`, gitignored); D71 RESOLVED — C-array - texture sources (the four rarewarelogo.c RGBA16 images) were byte-swapped - on LE PC → pink/green logo; port-layer per-source bswap normalizes them. - D72 RESOLVED the UV path (GE always uses authored tc[] UVs; lookat defaults - off) but **D72.3 OPEN**: all logo triangles project off-screen and a flat - dark-blue checkerboard triangle rasterizes instead — see Your job #1. -- **Runtime:** boots → intro music → full intro (logo, gun barrel w/ Brosnan, - cast screen) at ~59 fps → **crash in `load_bg_file` (bg.c:830)** on first - stage load. EXCEPTION 0xc0000005, FAULT ADDR ≈ header buffer + 0x500000F. -- **Committed through D72** (see git log). TEMP diagnostics from the - D51–D72 sessions are still in the tree — strip list in Task 3. +- **D59–D68 resolved** (details in PCPortResearch.md §F): blood-RLE sentinel, + HEADS/BODIES signed sentinels, romCopyAligned/ramrom 64-bit, image_entry + layout, Globalimagetable BE→LE fixup (`port/src/gimgfixup.c`), etc. +- **D70–D74 (intro logo) resolved:** + - D70: `GE_PCDUMP` PPM frame capture (kept — permanent dev tooling). + - D71: C-array texture sources (rarewarelogo.c RGBA16 images) bswapped on + LE PC → port-layer per-source normalization in `import_texture`. + - D72: GE always uses authored `tc[]` UVs; `lookat_enabled` defaults false. + - **D73 (root cause of the old D72.3):** sinf/cosf `du` double constants + are big-endian word pairs → garbage on LE PC → guMtxF2L emitted −32768 + for every sin/cos entry → logo triangles projected off-screen. Fixed with + the `DVAL()` macro in `src/libultra/gu/guint.h` (PORT-only; N64 build + untouched). All scenes using guRotate/guLookAt-derived matrices were + affected, not just the logo. + - **D74:** texture import fallback no longer truncates valid gDPLoadBlock + data (was dropping letter mip chains and reducing D_02005FF0 to 32×3); + `TextureCacheKey` gained `size_bytes`; VBO path now wraps UVs by tile + size for WRAP sub-tiles (N64 semantics; the logo's 20×3 sub-tile at + offset (11.5, 29) was sampling row 0 instead of rows 29–31). +- **Runtime:** boots → intro music → full intro (Rareware logo with gold + letters, gun barrel w/ Brosnan, cast screen) → **crash in `load_bg_file` + (bg.c:830)** on first stage load. EXCEPTION 0xc0000005; the crash log at + repo root (`ge007.crash.log`, gitignored) is from this exact fault: + PC=0x14000627c, FAULT ADDR = Rax+0x28 (reading `pPointTableBin` through the + mis-rebased room-fileposition pointer). +- **Committed through D74.** This session's TEMP probes are all stripped; + previously committed TEMP diagnostics remain — strip list in Task 3. ## The blocker, precisely (D69) @@ -98,7 +91,8 @@ GE's own consumers (bg.c, stan.c, and the N64 disasm where in doubt). 1. `AGENTS.md` — non-negotiables, critical files, build, verification ritual. 2. `docs/PCPortResearch.md` **§F/D69** (blocker + strategy), then D68 (Globalimagetable fixup — the template for BE data in this segment family), - §G status, and the environment reminders at the end of §H. + D73–D74 (this session), §G status, and the environment reminders at the end + of §H. 3. The consumers: `src/game/bg.c` (`load_bg_file` ~line 795+, levelinfotable line 183, BG_SEG_TO_PTR in bg.h:23), `src/game/stan.c` (stanLoadFile / stanDetermineEOF), `src/game/ob.c` (obLoadBGFileBytesAtOffset — note the @@ -135,24 +129,23 @@ GE's own consumers (bg.c, stan.c, and the N64 disasm where in doubt). ## Task 3 — strip TEMP diagnostics before committing -Env-gated probes to remove (grep `GE_D` + `TEMP D`): -- ob.c: D69 probe (both obLoadBGFileBytesAtOffset versions) + its stdio/stdlib - includes. -- image.c / image_bank.c: D66 texLoad probe if still present. -- gfx_pc.cpp: D59 seg probes, D63 dram-branch/modelRenderNodeGundl/rspGfxTaskStart. -- gfx_pc.cpp: D70/D71/D72 TEMP — `GE_DBGUV`/`GE_DBGTALL` UV+triangle loggers - (gfx_dbg_uv_enabled + the DBGTRI/DBGTALL blocks in gfx_sp_vertex/gfx_sp_tri1) - and the `GE_D71LOG` normalize log. Keep the D71 normalization itself — it's - the fix, not a probe. -- video.c + gfx_opengl.cpp: D70 `GE_PCDUMP` frame capture (videoEndFrame dump - hook, gfx_opengl_pcdump_* implementations). -- blood_animation.c: D63 g_GfxMemPos probe. -- front.c: D63 menu probes (d63MenuProbe/After + call sites). +This session's probes (GE_DBGUV/DBGTRI/DBGTALL/DBGMAT/D74IMP/D74DUMP/DBGLOAD) +are already stripped. Previously committed TEMP diagnostics still in the tree +(grep `TEMP D` + `GE_D`): +- gfx_pc.cpp: **TEMP D63** — the dram-branch trail (`s_d63taskcount`, + `d63RecordBranch`/`d63DumpTrail`/`d63LogLastBranchReread` externs near the + top of gfx_run_dl, the call in the G_DL case that also spams `[NOTE] D63 + dram-branch` every frame, the GE_D63 dump block, and the function + definitions at the end of the file); D59 seg probes if still present. +- gfx_pc.cpp: `GE_D71LOG` one-shot normalize log (keep the normalization + itself — it's the D71 fix). +- front.c: `d63MenuProbe`/After + call sites. blood_animation.c: D63 + g_GfxMemPos probe. ob.c: D69 probe (both obLoadBGFileBytesAtOffset + versions) + its stdio/stdlib includes — **strip only after D69 is solved**. - rsp.c, model.c, initanitable.c, language.c, title.c, ramromreplay.c, n64stubs.c, libultra.c, romdata.c: leftover TEMP blocks from D51–D66 - (D51 vi-post logging, D54 music, D56 watch, D57 pool, D60/D61 DMA logging — - decide per item whether the guard stays permanent; D60's target validation - is a permanent safety net and should stay). + (decide per item; D60's DMA target validation is a permanent safety net and + should stay). - Scratch files at repo root: `run_*.log`, `d62mesg.log`, `all.txt`, `b3.c`, `btest*.c`, `buildtest.txt`, `err.txt`, `gcout.txt`, `preproc.txt`, `vsize.c/.exe`, `scratch-logs/` — delete (add to .gitignore if they recur). @@ -166,16 +159,21 @@ Env-gated probes to remove (grep `GE_D` + `TEMP D`): duplicates, syntax (`./build-pc.sh` is the final word). - **Run the game from the repo root** (`cd REPO_ROOT && ./build-pc/ge007.x86_64.exe`) — running from build-pc/ picks up the wrong - ROM (baserom.u.z64) and misses the sidecar. + ROM (baserom.u.z64) and misses the pcmodels.bin sidecar → spurious frame-4 + crash in modelPromoteNodeOffsetsToPointers. - `Gfx.words.w0/w1` are both uintptr_t (16-byte Gfx on PC); ROM-copied N64 Gfx DLs stay 8 bytes in memory — walk them at 8-byte stride when scanning. - N64 CPU/RDP data in ROM is big-endian; any raw `*(u32*)` read of ROM-copied buffers needs a bswap under PORT (D68 pattern) or offline conversion. +- Heavy env-gated logging perturbs tick-driven timing (a full-triangle trace + hung the kernel heartbeat at ~frame 107 while clean runs run fine past + frame 2100) — use short targeted probe windows, and diagnose stability only + with clean runs. ## Environment reminders - MSYS2 tools: `export PATH="/c/msys64/mingw64/bin:$PATH"`. Build ~5 s. -- Crash log `ge007.crash.log` now includes FAULT ADDR, STACK@RSP window, and +- Crash log `ge007.crash.log` includes FAULT ADDR, STACK@RSP window, and module list — first stop for any fault. Symbolicate offline: `addr2line -e build-pc/ge007.x86_64.exe -f -C `. - gdb is launch-mode only and far too slow for timing-dependent crashes — @@ -186,3 +184,8 @@ Env-gated probes to remove (grep `GE_D` + `TEMP D`): - ROM: data/ge007.ntsc-final.z64 (byte-identical to the No-Intro good dump); sidecar: data/pcmodels-ntsc-final/ (gitignored, regenerate with tools_pc/d43_emit.py per region). +- Artifacts from this session: `ppm/` frames ~510–590 are the logo sequence + (frame ~555 is the best front-facing "RARE" shot); `tools_pc/mtxtest.c` is a + scratch matrix-convention harness (kept for reference). PPM files from + timeout-killed runs can be truncated — validate header + pixel count before + parsing. diff --git a/docs/PCPortResearch.md b/docs/PCPortResearch.md index 512288eb..fd578832 100644 --- a/docs/PCPortResearch.md +++ b/docs/PCPortResearch.md @@ -2214,28 +2214,40 @@ was D64's unbounded blood-RLE write clobbering the barrel sub-DL), **D67 resolved** (struct image_entry N64 layout reconstruction), **D68 resolved** (Globalimagetable BE→LE fixup for IMAGESEG Gfx words + sImageTableEntry.index; compiled globalDL shadows synced after texLoad). -The game now boots, plays the intro music, and renders the **entire ~3.5- -minute intro** (Nintendo logo → gun barrel with Brosnan → cast screen) at -~59 fps, then crashes in `load_bg_file` on the first stage load — +The game now boots, plays the intro music, and renders the **entire intro** +(Nintendo logo → gun barrel with Brosnan → cast screen; ~frame 2100, ≈2 min +wall-clock at the current ~20 fps clean-run rate), then crashes in +`load_bg_file` on the first stage load — **D69, the milestone blocker**: BG-file headers are N64 big-endian (segment-0x0F offsets) and PC reads them LE. Details in §F/D59–D69. -**D70–D72 (intro-logo pixel work):** D70 env-gated PPM frame capture +**D70–D74 (intro-logo pixel work):** D70 env-gated PPM frame capture (`GE_PCDUMP` → `./ppm/`) for numerical visual debugging; **D71 resolved** — C-array texture sources (the four rarewarelogo.c RGBA16 images) were byte-swapped on LE PC (pink/green logo); port-layer per-source bswap in `import_texture`. **D72 resolved** the UV path (GE always uses authored tc[] -UVs; `lookat_enabled` defaults false) but **D72.3 is OPEN**: all logo -triangles project off-screen and a flat dark-blue checkerboard triangle -rasterizes instead — identity pending (GE_DBGUV/GE_DBGTALL traces). +UVs; `lookat_enabled` defaults false). **D73 resolved** — root cause of +D72.3: sinf/cosf `du` double constants are big-endian word pairs, garbage on +LE PC → guMtxF2L emitted −32768 for every sin/cos entry → logo triangles +projected off-screen; DVAL() macro fix in guint.h/sinf.c/cosf.c under PORT. +**D74 resolved** — texture import fallback no longer truncates valid +gDPLoadBlock data (mip chains + sub-tiled textures), TextureCacheKey gains +`size_bytes`, and the VBO path now wraps UVs by tile size for WRAP sub-tiles +(N64 semantics). The logo now renders its four gold letters on the dark-blue +plate (PPM-verified at frame ~555); a final pixel-perfect comparison against +N64 reference footage is still open. **D69 remains the milestone blocker** +(stage load). -**Committed through D72**: D51–D72 fixes + TEMP diagnostics still present -(strip list in HANDOFF Task 3); build is GREEN. +**Committed through D74**: D51–D74 fixes; this session's TEMP probes are +stripped, but previously committed TEMP diagnostics (D63 blocks, GE_D71LOG, +and the older D51–D66 leftovers) are still in the tree — strip list in +HANDOFF Task 3. Build is GREEN. ### H. Handoff & plan (current session) -Full paste-ready brief: **docs/HANDOFF.md** (two live threads: D72.3 -intro-logo pixels, then D69 BG/stan stage loading). For D69: +Full paste-ready brief: **docs/HANDOFF.md** (primary thread: D69 BG/stan +stage loading — the milestone blocker; secondary: final pixel check of the +now-rendering intro logo). For D69: reverse-engineer GE's bg .seg + Tbg_*_stanZ formats from the decompiled consumers (bg.c, stan.c), then choose offline sidecar conversion (Plan-B pattern, D43) vs runtime port-layer fixup; PD's preprocess/filebg.c is a @@ -2243,25 +2255,28 @@ reference for the *approach* only — its BG format is different (zipped multi-section). Summary: -**State.** D50–D72 resolved and verified (except D72.3, open); committed -through the D72 milestone. The game boots, plays intro music, and renders -the entire ~3.5-minute intro (logo → gun barrel with Brosnan → cast screen) -at ~59 fps — logo lettering now gold-on-blue (D71/D72) but not yet visible -(D72.3). TEMP diagnostics from the D51–D72 sessions are still in the tree -(strip list in HANDOFF Task 3); visual-debug tooling: `GE_PCDUMP` frame -capture, `GE_DBGUV`/`GE_DBGTALL` UV+triangle traces. Build is GREEN. +**State.** D50–D74 resolved and verified (D73 = sinf/cosf endianness, D74 = +texture import fallback + sub-tile UV wrap); committed through the D74 +milestone. The game boots, plays intro music, and renders the entire intro +(logo → gun barrel with Brosnan → cast screen) — the Rareware logo now shows +its four gold letters on the dark-blue plate (D71–D74; PPM-verified at frame +~555, final pixel-perfect check vs N64 footage still open). TEMP diagnostics +from earlier sessions are still in the tree (strip list in HANDOFF Task 3); +visual-debug tooling: `GE_PCDUMP` frame capture. Build is GREEN. -**Thread 1 — D72.3 (intro logo).** All logo triangles project off-screen -(clip x=y≈−1.6e11, w<0) while a flat dark-blue (0,0,64) checkerboard -triangle rasterizes in the lower screen; matrix-format mismatches ruled out -(§F/D72). Next: identify the checkerboard's triangle via GE_DBGTALL, then -verify MP matrix + viewport at logo time (title.c load_display_rare_logo). - -**Thread 2 — D69 (milestone blocker).** `load_bg_file` (src/game/bg.c:830) -faults on the first stage load (BUNKER1): BG-file header words are N64 -big-endian segment-0x0F offsets; PC reads them LE, so the +**Thread 1 — D69 (milestone blocker, now primary).** `load_bg_file` +(src/game/bg.c:830) faults on the first stage load (BUNKER1): BG-file header +words are N64 big-endian segment-0x0F offsets; PC reads them LE, so the room-fileposition-list pointer lands ~5 MB past the stack header buffer. -Full analysis + strategy options in §F/D69. +Full analysis + strategy options in §F/D69. This is the next milestone: +decode the bg .seg + Tbg_*_stanZ formats, convert/fix for PC, get a stage to +load and render. + +**Thread 2 — logo final check (low priority).** Compare the rendered logo +frames (ppm/, frame ~550–560) against N64 reference footage; if any letter +is still off, re-add a lightweight triangle/texture attribution probe (the +D74 probes are stripped; the import sizes and sub-tile wrap are verified at +the data level). **After D69:** get a stage to load + render (bg .seg + Tbg_*_stanZ format work), then continue the diagnose→fix→verify loop through gameplay; strip @@ -2354,8 +2369,71 @@ gated by `GE_D71LOG`. modified variant writing the interleaved hi16/lo16 s32 Q15.16 packed format that gfx_sp_matrix decodes exactly (FTOFIX32=×65536); `D_8002A7D0` is a zero-init u32 so `[D_8002A7D0]`==`[0]`; alloc_intro_matrices() runs from - initmenus. Next: find the checkerboard's triangle via GE_DBGTALL, then - verify MP matrix + viewport at logo time (title.c load_display_rare_logo). + initmenus. **SUPERSEDED by D73** — the off-screen projection was never a + matrix-format or UV problem: guMtxF2L's sinf/cosf inputs were garbage on PC. + +**D73 (RESOLVED — root cause of D72.3):** GE's `sinf`/`cosf` +(src/libultra/gu/sinf.c, cosf.c) build their double constants through the +`du` bit-union (`{ struct { u32 hi; u32 lo; } word; double d; }`) with +big-endian word pairs `{hi, lo}` (rpi, pihi, and the P[] polynomial tables). +On an LE PC the `.d` read has the two words swapped: `rpi.d` ≈ 2^733, +`pihi.d` ≈ 2^257. Range reduction then overflows: `dn = dx*rpi.d` huge → +ROUND(dn) saturates in cvttsd2si (n = −2^31) → `dx = dx − dn*pihi.d` ≈ 2^288 +→ xsq ≈ 2^576 → the result overflows float → sinf/cosf return ±inf/NaN. +FTOFIX32's `(int)(±inf·65536)` is another cvttss2si saturation → +0x80000000 (−32768). Every sin/cos-derived entry of guMtxF2L became −32768, +so the Rareware logo's guRotate matrix was garbage and all its triangles +projected off-screen (the D72.3 symptom; the "checkerboard" was a separate +large triangle rasterizing over the empty logo region). The logo scene is the +first visually verifiable consumer of guMtxF2L sin/cos output in the intro; +other scenes that build matrices via guRotate/guLookAt are affected by the +same bug and were fixed by this change. Fix (narrow ABI exception — constant +interpretation only, algorithm verbatim): `duD()` static-inline + `DVAL(x)` +macro in src/libultra/gu/guint.h under #ifdef PORT re-pack hi/lo into the +correct LE double; the 5 `.d` reads in sinf.c and 4 in cosf.c now use +`DVAL(...)`. The N64 build is untouched (#else branch = original expression). +Only sinf.c/cosf.c use `du` (grep-verified); `fu` (single u32) constants are +endian-safe. Verified numerically: P decodes to the expected perspective +matrix, Rot(−40°Y) gives clean cos=0.766/sin=−0.643, and logo vertices clip +to small finite on-screen NDC values. (tools_pc/mtxtest.c is a standalone +scratch harness for the matrix-convention question that was ruled out along +the way — GE stores all matrices transposed; row-vector ≡ column pipeline.) + +**D74 (RESOLVED — final pixel check pending):** with D73 in, logo geometry +was correct but the texture side was still wrong: a dim red-brown blob plus +a flat vertical bar at the right edge, one letter visible at a time. Three +port-layer bugs, all in port/fast3d/gfx_pc.cpp (with gfx_pc.h): +- **Import fallback truncation.** `import_texture`'s old condition + `(rdp.tex_lod && tile >= rdp.first_tile_index + rdp.tex_detail) || + !loaded_texture.addr` overwrote valid gDPLoadBlock tmem data with + `line_size_bytes * tile.height`. For the logo: letter mip chains 2744 B → + 2048 B (mips 1–5 dropped), and D_02005FF0's 32×32 → 192 B (32×3, stale + tile.height=3 left by `gsDPSetTileSize(0, 46, 116, 124, 124)` in DL + D_02004758). Fix: fall back only when `!loaded_texture.addr` (N64 TMem is + persistent — a populated slot is the faithful source). +- **Cache poisoning.** TextureCacheKey lacked the upload size, so the first + (truncated) import of an address poisoned every later import. Added + `size_bytes` to the key (gfx_pc.h + both key initializers + the bucket-only + aggregate in gfx_texture_cache_delete). +- **Sub-tile UV wrap period.** N64 wraps UVs by the TILE size when a render + tile is a sub-region of the image; the port wrapped at the full uploaded + image size (GL_REPEAT). D_02004758's 20×3 tile at offset (11.5, 29) + therefore sampled row 0 instead of rows 29–31 — the flat bar's color + matched texel [0,4] of D_02005FF0 exactly. Fix: per-vertex pre-wrap in the + VBO path — for a WRAP sub-tile (`tex_width2 < tex_width`, where + `tex_width2 = (lrs−uls+4)/4` is the tile window in texels), + `u = fmodf(u, tw); if (u<0) u += tw; u += uls/4.0f` (same for v). Known + limitations: half-texel edge bleed at the sub-tile window edges (GL + bilinear neighbors may sample just outside [O, O+W)); mirror sub-tiles are + not handled (the logo doesn't use them; audit if another scene regresses). +Verified: runtime import sizes correct (D_02005FF0 = 2048 B, imgRAre = +2744 B); PPM frame ~555 shows the four-letter RARE band with gold/warm +colors and per-letter segmentation; clean `GE_PCDUMP` runs are stable past +1156 frames. Final pixel-perfect confirmation against N64 reference footage +is still open (see HANDOFF Task 1). All env-gated probes added for D72.3/D74 +(GE_DBGUV/GE_DBGTRI/GE_DBGTALL/GE_DBGMAT/GE_D74IMP/GE_D74DUMP/GE_DBGLOAD) +were stripped with this change; the previously committed TEMP D63 blocks and +the GE_D71LOG normalize log remain on the HANDOFF strip list. **Non-negotiable #2 refinement (applied to AGENTS.md).** The original "game code compiles unmodified / fix belongs in port/" is too absolute: pointer-width layout diff --git a/port/fast3d/gfx_pc.cpp b/port/fast3d/gfx_pc.cpp index 2f0975e9..a63d816c 100644 --- a/port/fast3d/gfx_pc.cpp +++ b/port/fast3d/gfx_pc.cpp @@ -568,7 +568,7 @@ void gfx_texture_cache_delete(const uint8_t* orig_addr) { } while (gfx_texture_cache.map.bucket_count() > 0) { - TextureCacheKey key = { orig_addr, { 0 }, 0, 0 }; // bucket index only depends on the address + TextureCacheKey key = { orig_addr, { 0 }, 0, 0, 0 }; // bucket index only depends on the address size_t bucket = gfx_texture_cache.map.bucket(key); bool again = false; for (auto it = gfx_texture_cache.map.begin(bucket); it != gfx_texture_cache.map.end(bucket); ++it) { @@ -914,7 +914,12 @@ static void import_texture(int i, int tile, bool importReplacement) { const uint32_t tex_flags = loaded_texture.tex_flags; const uint8_t palette_index = rdp.texture_tile[tile].palette; - if ((rdp.tex_lod && tile >= rdp.first_tile_index + rdp.tex_detail) || !loaded_texture.addr) { + // D74: only fall back when the tmem slot was never written by a load + // command. The old `rdp.tex_lod && tile >= first+detail` branch also + // overwrote valid gDPLoadBlock data with line*tile.height, which (a) dropped + // mip chains and (b) truncated sub-tiled textures to the sub-tile's row + // count (e.g. the Rare-logo D_02005FF0 20x3 tile uploaded as 32x3). + if (!loaded_texture.addr) { // set up miplevel 0; also acts as a catch-all for when .addr is NULL because my texture loader sucks loaded_texture.addr = rdp.texture_to_load.addr; loaded_texture.line_size_bytes = rdp.texture_tile[tile].line_size_bytes; @@ -935,9 +940,10 @@ static void import_texture(int i, int tile, bool importReplacement) { TextureCacheKey key; if (fmt == G_IM_FMT_CI) { - key = { orig_addr, { rdp.palette_addrs[0], rdp.palette_addrs[1] }, fmt, siz, palette_index }; + key = { orig_addr, { rdp.palette_addrs[0], rdp.palette_addrs[1] }, fmt, siz, palette_index, + loaded_texture.size_bytes }; } else { - key = { orig_addr, {}, fmt, siz, palette_index }; + key = { orig_addr, {}, fmt, siz, palette_index, loaded_texture.size_bytes }; } if (gfx_texture_cache_lookup(i, key)) { @@ -1255,57 +1261,12 @@ static inline int gfx_lod_tile_offset(const int i) { return (rdp.tex_lod ? rdp.tex_detail : i); } -// TEMP D72: env-gated UV pipeline logging (GE_DBGUV=1). -static bool gfx_dbg_uv_enabled(void) { - static int e = -1; - if (e < 0) e = getenv("GE_DBGUV") != nullptr; - return e; -} - static void gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx, bool is_rect) { struct LoadedVertex* v1 = &rsp.loaded_vertices[vtx1_idx]; struct LoadedVertex* v2 = &rsp.loaded_vertices[vtx2_idx]; struct LoadedVertex* v3 = &rsp.loaded_vertices[vtx3_idx]; struct LoadedVertex* v_arr[3] = { v1, v2, v3 }; - // TEMP D72: GE_DBGTRI=1 — trace triangles; GE_DBGTALL=1 logs all. - static int dbg_tri_count = 0; - if (gfx_dbg_uv_enabled() && getenv("GE_DBGTALL")) { - float sx[3] = { v1->x / (v1->w), v2->x / (v2->w), v3->x / (v3->w) }; - float sy[3] = { v1->y / (v1->w), v2->y / (v2->w), v3->y / (v3->w) }; - if (dbg_tri_count++ < 4000) { - fprintf(stderr, "[DBGTALL] n=%d scr=(%.0f,%.0f)(%.0f,%.0f)(%.0f,%.0f) col=(%d,%d,%d,%d) clip=(%x,%x,%x)\n", - dbg_tri_count, sx[0], sy[0], sx[1], sy[1], sx[2], sy[2], - v1->color.r, v1->color.g, v1->color.b, v1->color.a, - v1->clip_rej, v2->clip_rej, v3->clip_rej); - } - } - if (gfx_dbg_uv_enabled()) { - bool gold = false, blue = false; - for (int i = 0; i < 3; i++) { - if (v_arr[i]->color.b == 0 && v_arr[i]->color.r > 90) gold = true; - if (v_arr[i]->color.b > 100 && v_arr[i]->color.r < 50) blue = true; - } - if (gold || blue) { - float dx1 = v1->x / (v1->w) - v2->x / (v2->w); - float dy1 = v1->y / (v1->w) - v2->y / (v2->w); - float dx2 = v3->x / (v3->w) - v2->x / (v2->w); - float dy2 = v3->y / (v3->w) - v2->y / (v2->w); - if (dbg_tri_count++ < 64) { - fprintf(stderr, "[DBGTRI] %s cull=%d clip=(%x,%x,%x) cross=%.3f scr=(%.1f,%.1f)(%.1f,%.1f)(%.1f,%.1f) clip4=(%.0f,%.0f,%.0f,%.2f) col=(%d,%d,%d)\n", - gold ? "GOLD" : "BLUE", - rsp.geometry_mode & G_CULL_BOTH, - v1->clip_rej, v2->clip_rej, v3->clip_rej, - dx1 * dy2 - dy1 * dx2, - v1->x / (v1->w), v1->y / (v1->w), - v2->x / (v2->w), v2->y / (v2->w), - v3->x / (v3->w), v3->y / (v3->w), - v1->x, v1->y, v1->z, v1->w, - v1->color.r, v1->color.g, v1->color.b); - } - } - } - if ((rsp.extra_geometry_mode & G_NO_CLIPPING_EXT) == 0) { if (v1->clip_rej & v2->clip_rej & v3->clip_rej) { // The whole triangle lies outside the visible area @@ -1567,6 +1528,25 @@ static void gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx, bo u -= rdp.texture_tile[rdp.first_tile_index + tile].uls / 4.0f; v -= rdp.texture_tile[rdp.first_tile_index + tile].ult / 4.0f; + // D74: N64 wraps UVs by the TILE size when a render tile is a + // sub-region of the image (uls/ult offset + lrs/lrt window); GL + // would wrap at the full uploaded image size instead. Pre-wrap per + // vertex (GE/PD triangles never span more than one tile period). + { + const uint32_t tw = tex_width2[i]; + const uint32_t th = tex_height2[i]; + if ((rdp.texture_tile[rdp.first_tile_index + tile].cms & G_TX_WRAP) && tw < tex_width[i]) { + u = fmodf(u, (float)tw); + if (u < 0.0f) u += (float)tw; + u += rdp.texture_tile[rdp.first_tile_index + tile].uls / 4.0f; + } + if ((rdp.texture_tile[rdp.first_tile_index + tile].cmt & G_TX_WRAP) && th < tex_height[i]) { + v = fmodf(v, (float)th); + if (v < 0.0f) v += (float)th; + v += rdp.texture_tile[rdp.first_tile_index + tile].ult / 4.0f; + } + } + if (!is_rect) { if (!(rdp.other_mode_h & G_TP_PERSP)) { u *= 0.5f; @@ -1583,22 +1563,6 @@ static void gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx, bo buf_vbo[buf_vbo_len++] = u / tex_width[t]; buf_vbo[buf_vbo_len++] = v / tex_height[t]; - // TEMP D72: GE_DBGUV=1 — log the UV pipeline per vertex, but - // only for C-array (logo) textures so the cap isn't exhausted. - if (gfx_dbg_uv_enabled() && - gfx_tex_source_is_c_array( - rdp.loaded_texture[rdp.texture_tile[rdp.first_tile_index + tile].tmem].addr)) { - static int dbg_uv_count = 0; - if (dbg_uv_count++ < 30000) { - fprintf(stderr, "[DBGUV] v.u=%d v.v=%d shifts=%d shiftt=%d uls=%d ult=%d -> u=%.4f v=%.4f (tex %dx%d)\n", - v_arr[i]->u, v_arr[i]->v, shifts, shiftt, - rdp.texture_tile[rdp.first_tile_index + tile].uls, - rdp.texture_tile[rdp.first_tile_index + tile].ult, - u / tex_width[t], v / tex_height[t], - tex_width[t], tex_height[t]); - } - } - bool clampS = tm & (1 << 2 * t); bool clampT = tm & (1 << (2 * t + 1)); diff --git a/port/fast3d/gfx_pc.h b/port/fast3d/gfx_pc.h index 0ca55152..46fd9f6e 100644 --- a/port/fast3d/gfx_pc.h +++ b/port/fast3d/gfx_pc.h @@ -25,6 +25,10 @@ struct TextureCacheKey { const uint8_t* palette_addrs[2]; uint8_t fmt, siz; uint8_t palette_index; + // D74: the same source address can be loaded with different sizes + // (gDPLoadBlock mip chains vs partial loads); include the size so a + // truncated first import cannot poison later full imports. + uint32_t size_bytes; bool operator==(const TextureCacheKey&) const noexcept = default; diff --git a/src/libultra/gu/cosf.c b/src/libultra/gu/cosf.c index 096a29a3..3d37a038 100644 --- a/src/libultra/gu/cosf.c +++ b/src/libultra/gu/cosf.c @@ -96,18 +96,18 @@ int ix, xpt; dx = absx; - dn = dx*rpi.d + 0.5; + dn = dx*DVAL(rpi) + 0.5; n = ROUND(dn); dn = n; dn -= 0.5; - dx = dx - dn*pihi.d; - dx = dx - dn*pilo.d; /* dx = x - (n - 0.5)*pi */ + dx = dx - dn*DVAL(pihi); + dx = dx - dn*DVAL(pilo); /* dx = x - (n - 0.5)*pi */ xsq = dx*dx; - poly = ((P[4].d*xsq + P[3].d)*xsq + P[2].d)*xsq + P[1].d; + poly = ((DVAL(P[4])*xsq + DVAL(P[3]))*xsq + DVAL(P[2]))*xsq + DVAL(P[1]); result = dx + (dx*xsq)*poly; diff --git a/src/libultra/gu/guint.h b/src/libultra/gu/guint.h index c3206dd5..6efcc4a2 100644 --- a/src/libultra/gu/guint.h +++ b/src/libultra/gu/guint.h @@ -24,6 +24,21 @@ typedef union double d; } du; +#ifdef PORT +/* The {hi,lo} initializers of du constants are written in big-endian word + * order (as on the N64). On a little-endian host the .d member would read + * the two words swapped; re-pack them to recover the same double value. */ +static inline double duD(const du u) +{ + union { unsigned long long b; double d; } t; + t.b = ((unsigned long long)u.word.hi << 32) | (unsigned long long)u.word.lo; + return t.d; +} +#define DVAL(x) duD((x)) +#else +#define DVAL(x) ((x).d) +#endif + typedef union { unsigned int i; diff --git a/src/libultra/gu/sinf.c b/src/libultra/gu/sinf.c index ba229556..fbd95ae3 100644 --- a/src/libultra/gu/sinf.c +++ b/src/libultra/gu/sinf.c @@ -96,7 +96,7 @@ int ix, xpt; xsq = dx*dx; - poly = ((P[4].d*xsq + P[3].d)*xsq + P[2].d)*xsq + P[1].d; + poly = ((DVAL(P[4])*xsq + DVAL(P[3]))*xsq + DVAL(P[2]))*xsq + DVAL(P[1]); result = dx + (dx*xsq)*poly; @@ -114,20 +114,20 @@ int ix, xpt; /* reduce argument to +/- pi/2 */ - dn = dx*rpi.d; + dn = dx*DVAL(rpi); n = ROUND(dn); dn = n; - dx = dx - dn*pihi.d; - dx = dx - dn*pilo.d; /* dx = x - n*pi */ + dx = dx - dn*DVAL(pihi); + dx = dx - dn*DVAL(pilo); /* dx = x - n*pi */ /* compute sin(dx) as before, negating result if n is odd */ xsq = dx*dx; - poly = ((P[4].d*xsq + P[3].d)*xsq + P[2].d)*xsq + P[1].d; + poly = ((DVAL(P[4])*xsq + DVAL(P[3]))*xsq + DVAL(P[2]))*xsq + DVAL(P[1]); result = dx + (dx*xsq)*poly;