mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-24 00:02:45 +02:00
M-29 docs/tools: SMALL-FIXES notes, LEVEL-OBJECTIVES decode, animgen dump helpers
This commit is contained in:
1 parent
87d8e065d5
commit
290e85c50d
5 files changed
+920
No files matched your search
@@ -0,0 +1,210 @@
|
||||
|
||||
=== level_09 (level 9, UsetupsevbunkerZ) ===
|
||||
Objective 0 [briefing text id 30749] min difficulty: Agent
|
||||
- DESTROY_OBJECT tag 0 -> CCTV
|
||||
- DESTROY_OBJECT tag 1 -> CCTV
|
||||
- DESTROY_OBJECT tag 2 -> CCTV
|
||||
- DESTROY_OBJECT tag 3 -> CCTV
|
||||
Objective 1 [briefing text id 30750] min difficulty: Agent
|
||||
- COPY_ITEM (key-analyzer/datavac copy flag)
|
||||
Objective 2 [briefing text id 30751] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x100 (bit(s) [8])
|
||||
- FAIL_CONDITION stage flag mask 0x200 (bit(s) [9]) -- mission FAILS when set
|
||||
Objective 3 [briefing text id 30752] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x400 (bit(s) [10])
|
||||
- FAIL_CONDITION stage flag mask 0x800 (bit(s) [11]) -- mission FAILS when set
|
||||
- FAIL_CONDITION stage flag mask 0x200 (bit(s) [9]) -- mission FAILS when set
|
||||
Objective 4 [briefing text id 30753] min difficulty: Agent
|
||||
- PHOTOGRAPH tag 5 -> MULTI_MONITOR; photo-flag word @8=0x0
|
||||
|
||||
=== level_20 (level 20, UsetupsiloZ) ===
|
||||
Objective 0 [briefing text id 34834] min difficulty: Agent
|
||||
- DEPOSIT_IN_ROOM ref @4=34 ref @8=4; runtime word @c=0x0
|
||||
- DEPOSIT_IN_ROOM ref @4=34 ref @8=12; runtime word @c=0x0
|
||||
- DEPOSIT_IN_ROOM ref @4=34 ref @8=88; runtime word @c=0x0
|
||||
- DEPOSIT_IN_ROOM ref @4=34 ref @8=95; runtime word @c=0x0
|
||||
Objective 1 [briefing text id 34835] min difficulty: Agent
|
||||
- PHOTOGRAPH tag 0 -> PROP; photo-flag word @8=0x0
|
||||
Objective 2 [briefing text id 34836] min difficulty: Agent
|
||||
- COLLECT_OBJECT tag 1 -> PROP
|
||||
Objective 3 [briefing text id 34837] min difficulty: Agent
|
||||
- COLLECT_OBJECT tag 2 -> PROP
|
||||
- COLLECT_OBJECT tag 3 -> PROP
|
||||
- COLLECT_OBJECT tag 4 -> PROP
|
||||
- COLLECT_OBJECT tag 5 -> PROP
|
||||
- FAIL_CONDITION stage flag mask 0x100 (bit(s) [8]) -- mission FAILS when set
|
||||
- FAIL_CONDITION stage flag mask 0x200 (bit(s) [9]) -- mission FAILS when set
|
||||
- FAIL_CONDITION stage flag mask 0x400 (bit(s) [10]) -- mission FAILS when set
|
||||
- FAIL_CONDITION stage flag mask 0x800 (bit(s) [11]) -- mission FAILS when set
|
||||
Objective 4 [briefing text id 34838] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x8000 (bit(s) [15])
|
||||
- FAIL_CONDITION stage flag mask 0x4000 (bit(s) [14]) -- mission FAILS when set
|
||||
|
||||
=== level_21 (level 21, UsetupsevbunkerZ) ===
|
||||
Objective 0 [briefing text id 30749] min difficulty: Agent
|
||||
- DESTROY_OBJECT tag 0 -> CCTV
|
||||
- DESTROY_OBJECT tag 1 -> CCTV
|
||||
- DESTROY_OBJECT tag 2 -> CCTV
|
||||
- DESTROY_OBJECT tag 3 -> CCTV
|
||||
Objective 1 [briefing text id 30750] min difficulty: Agent
|
||||
- COPY_ITEM (key-analyzer/datavac copy flag)
|
||||
Objective 2 [briefing text id 30751] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x100 (bit(s) [8])
|
||||
- FAIL_CONDITION stage flag mask 0x200 (bit(s) [9]) -- mission FAILS when set
|
||||
Objective 3 [briefing text id 30752] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x400 (bit(s) [10])
|
||||
- FAIL_CONDITION stage flag mask 0x800 (bit(s) [11]) -- mission FAILS when set
|
||||
- FAIL_CONDITION stage flag mask 0x200 (bit(s) [9]) -- mission FAILS when set
|
||||
Objective 4 [briefing text id 30753] min difficulty: Agent
|
||||
- PHOTOGRAPH tag 5 -> MULTI_MONITOR; photo-flag word @8=0x0
|
||||
|
||||
=== level_22 (level 22, UsetupstatueZ) ===
|
||||
Objective 0 [briefing text id 35897] min difficulty: Agent
|
||||
- FAIL_CONDITION stage flag mask 0x200000 (bit(s) [21]) -- mission FAILS when set
|
||||
- COMPLETE_CONDITION stage flag mask 0x100 (bit(s) [8])
|
||||
Objective 1 [briefing text id 35898] min difficulty: Agent
|
||||
- FAIL_CONDITION stage flag mask 0x20000 (bit(s) [17]) -- mission FAILS when set
|
||||
- FAIL_CONDITION stage flag mask 0x200000 (bit(s) [21]) -- mission FAILS when set
|
||||
- COMPLETE_CONDITION stage flag mask 0x400 (bit(s) [10])
|
||||
Objective 2 [briefing text id 35899] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x200 (bit(s) [9])
|
||||
- FAIL_CONDITION stage flag mask 0x200000 (bit(s) [21]) -- mission FAILS when set
|
||||
Objective 3 [briefing text id 35900] min difficulty: Agent
|
||||
- FAIL_CONDITION stage flag mask 0x400000 (bit(s) [22]) -- mission FAILS when set
|
||||
- FAIL_CONDITION stage flag mask 0x200000 (bit(s) [21]) -- mission FAILS when set
|
||||
- COMPLETE_CONDITION stage flag mask 0x800 (bit(s) [11])
|
||||
Objective 4 [briefing text id 35901] min difficulty: Agent
|
||||
- COLLECT_OBJECT tag 1 -> PROP
|
||||
- FAIL_CONDITION stage flag mask 0x200000 (bit(s) [21]) -- mission FAILS when set
|
||||
|
||||
=== level_23 (level 23, UsetupcontrolZ) ===
|
||||
Objective 0 [briefing text id 8233] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x1000 (bit(s) [12])
|
||||
- FAIL_CONDITION stage flag mask 0x2000 (bit(s) [13]) -- mission FAILS when set
|
||||
Objective 1 [briefing text id 8234] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x4000 (bit(s) [14])
|
||||
- FAIL_CONDITION stage flag mask 0x10000 (bit(s) [16]) -- mission FAILS when set
|
||||
Objective 2 [briefing text id 8235] min difficulty: Agent
|
||||
- DESTROY_OBJECT tag 21 -> PROP
|
||||
- DESTROY_OBJECT tag 22 -> PROP
|
||||
- DESTROY_OBJECT tag 23 -> PROP
|
||||
- DESTROY_OBJECT tag 24 -> PROP
|
||||
- DESTROY_OBJECT tag 25 -> PROP
|
||||
- DESTROY_OBJECT tag 26 -> PROP
|
||||
|
||||
=== level_24 (level 24, UsetuparchZ) ===
|
||||
Objective 0 [briefing text id 2089] min difficulty: Agent
|
||||
- ENTER_ROOM ref @4=152 (room); runtime word @8=0x0
|
||||
Objective 1 [briefing text id 2090] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x100 (bit(s) [8])
|
||||
Objective 2 [briefing text id 2091] min difficulty: Agent
|
||||
- COLLECT_OBJECT tag 2 -> PROP
|
||||
- FAIL_CONDITION stage flag mask 0x1000 (bit(s) [12]) -- mission FAILS when set
|
||||
Objective 3 [briefing text id 2092] min difficulty: Agent
|
||||
- ENTER_ROOM ref @4=208 (room); runtime word @8=0x0
|
||||
- COMPLETE_CONDITION stage flag mask 0x400 (bit(s) [10])
|
||||
- FAIL_CONDITION stage flag mask 0x200 (bit(s) [9]) -- mission FAILS when set
|
||||
|
||||
=== level_25 (level 25, UsetuptraZ) ===
|
||||
Objective 0 [briefing text id 36898] min difficulty: Agent
|
||||
- DESTROY_OBJECT tag 8 -> PROP
|
||||
- DESTROY_OBJECT tag 9 -> PROP
|
||||
- DESTROY_OBJECT tag 10 -> PROP
|
||||
- DESTROY_OBJECT tag 11 -> PROP
|
||||
- DESTROY_OBJECT tag 12 -> PROP
|
||||
- DESTROY_OBJECT tag 13 -> PROP
|
||||
Objective 1 [briefing text id 36899] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x8000 (bit(s) [15])
|
||||
- FAIL_CONDITION stage flag mask 0x4000 (bit(s) [14]) -- mission FAILS when set
|
||||
Objective 2 [briefing text id 36900] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x10000 (bit(s) [16])
|
||||
Objective 3 [briefing text id 36901] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x20000 (bit(s) [17])
|
||||
Objective 4 [briefing text id 36902] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x100000 (bit(s) [20])
|
||||
|
||||
=== level_26 (level 26, UsetupdestZ) ===
|
||||
Objective 0 [briefing text id 13332] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x100 (bit(s) [8])
|
||||
- COMPLETE_CONDITION stage flag mask 0x200 (bit(s) [9])
|
||||
- COMPLETE_CONDITION stage flag mask 0x400 (bit(s) [10])
|
||||
- COMPLETE_CONDITION stage flag mask 0x800 (bit(s) [11])
|
||||
- COMPLETE_CONDITION stage flag mask 0x1000 (bit(s) [12])
|
||||
- FAIL_CONDITION stage flag mask 0x100000 (bit(s) [20]) -- mission FAILS when set
|
||||
Objective 1 [briefing text id 13333] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x10000 (bit(s) [16])
|
||||
- FAIL_CONDITION stage flag mask 0x40000 (bit(s) [18]) -- mission FAILS when set
|
||||
Objective 2 [briefing text id 13334] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x20000 (bit(s) [17])
|
||||
- FAIL_CONDITION stage flag mask 0x80000 (bit(s) [19]) -- mission FAILS when set
|
||||
Objective 3 [briefing text id 13335] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x4000 (bit(s) [14])
|
||||
- FAIL_CONDITION stage flag mask 0x8000 (bit(s) [15]) -- mission FAILS when set
|
||||
|
||||
=== level_27 (level 27, UsetupsevbZ) ===
|
||||
Objective 0 [briefing text id 29743] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x200 (bit(s) [9])
|
||||
Objective 1 [briefing text id 29744] min difficulty: Agent
|
||||
- FAIL_CONDITION stage flag mask 0x1000 (bit(s) [12]) -- mission FAILS when set
|
||||
- COLLECT_OBJECT tag 2 -> PROP
|
||||
Objective 2 [briefing text id 29745] min difficulty: Agent
|
||||
- DESTROY_OBJECT tag 28 -> CCTV
|
||||
- DESTROY_OBJECT tag 29 -> CCTV
|
||||
- DESTROY_OBJECT tag 30 -> CCTV
|
||||
- DESTROY_OBJECT tag 31 -> CCTV
|
||||
- DESTROY_OBJECT tag 32 -> CCTV
|
||||
- DESTROY_OBJECT tag 33 -> CCTV
|
||||
Objective 3 [briefing text id 29746] min difficulty: Agent
|
||||
- COLLECT_OBJECT tag 34 -> PROP
|
||||
Objective 4 [briefing text id 29747] min difficulty: Agent
|
||||
- FAIL_CONDITION stage flag mask 0x100 (bit(s) [8]) -- mission FAILS when set
|
||||
- COMPLETE_CONDITION stage flag mask 0x800 (bit(s) [11])
|
||||
- ENTER_ROOM ref @4=51 (room); runtime word @8=0x0
|
||||
|
||||
=== level_28 (level 28, UsetupaztZ) ===
|
||||
Objective 0 [briefing text id 5141] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x1000 (bit(s) [12])
|
||||
Objective 1 [briefing text id 5142] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x8000 (bit(s) [15])
|
||||
|
||||
=== level_29 (level 29, UsetuppeteZ) ===
|
||||
Objective 0 [briefing text id 25627] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x20000 (bit(s) [17])
|
||||
- FAIL_CONDITION stage flag mask 0x40000 (bit(s) [18]) -- mission FAILS when set
|
||||
Objective 1 [briefing text id 25625] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x4000 (bit(s) [14])
|
||||
- FAIL_CONDITION stage flag mask 0x8000 (bit(s) [15]) -- mission FAILS when set
|
||||
Objective 2 [briefing text id 25626] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x4000 (bit(s) [14])
|
||||
- FAIL_CONDITION stage flag mask 0x1000 (bit(s) [12]) -- mission FAILS when set
|
||||
|
||||
=== level_30 (level 30, UsetupdepoZ) ===
|
||||
Objective 0 [briefing text id 12303] min difficulty: Agent
|
||||
- DESTROY_OBJECT tag 10 -> PROP
|
||||
- DESTROY_OBJECT tag 11 -> PROP
|
||||
- DESTROY_OBJECT tag 12 -> PROP
|
||||
- DESTROY_OBJECT tag 13 -> PROP
|
||||
- DESTROY_OBJECT tag 14 -> PROP
|
||||
- DESTROY_OBJECT tag 15 -> PROP
|
||||
- DESTROY_OBJECT tag 16 -> PROP
|
||||
- DESTROY_OBJECT tag 17 -> PROP
|
||||
- DESTROY_OBJECT tag 18 -> PROP
|
||||
- DESTROY_OBJECT tag 19 -> PROP
|
||||
- DESTROY_OBJECT tag 20 -> PROP
|
||||
- DESTROY_OBJECT tag 21 -> PROP
|
||||
- DESTROY_OBJECT tag 22 -> PROP
|
||||
- DESTROY_OBJECT tag 23 -> PROP
|
||||
- DESTROY_OBJECT tag 24 -> PROP
|
||||
- DESTROY_OBJECT tag 25 -> PROP
|
||||
- DESTROY_OBJECT tag 26 -> PROP
|
||||
Objective 1 [briefing text id 12304] min difficulty: Agent
|
||||
- DESTROY_OBJECT tag 1 -> MULTI_MONITOR
|
||||
- DESTROY_OBJECT tag 2 -> PROP
|
||||
- DESTROY_OBJECT tag 3 -> PROP
|
||||
Objective 2 [briefing text id 12305] min difficulty: Agent
|
||||
- COLLECT_OBJECT tag 0 -> KEY
|
||||
Objective 3 [briefing text id 12306] min difficulty: Agent
|
||||
- COLLECT_OBJECT tag 5 -> PROP
|
||||
Objective 4 [briefing text id 12307] min difficulty: Agent
|
||||
- COMPLETE_CONDITION stage flag mask 0x100 (bit(s) [8])
|
||||
UsetuprefZ not in filelist
|
||||
@@ -0,0 +1,430 @@
|
||||
# SMALL-FIXES — ready-to-execute fix notes for small / low-level open items
|
||||
|
||||
**Read-only document.** This file records *proposed fixes and verification
|
||||
procedures* for the small, low-level bugs and noted issues that are parked in
|
||||
`BACKLOG.md`, `GRAPHICS-BACKLOG.md`, `HANDOFF.md`, and the §F/§H finding log.
|
||||
No code was changed when writing it (M-28). Each item is self-contained enough
|
||||
to be picked up cold; items marked **VERIFY-ONLY** already have their fix in
|
||||
the tree and only need confirmation + commit hygiene.
|
||||
|
||||
Standing rules for every item below:
|
||||
|
||||
- Game-code edits must stay inside the narrow `#ifdef PORT` ABI/layout
|
||||
exception (AGENTS.md #2); port-layer work has no such limit. Anything
|
||||
bigger than described here → stop, write it up with a confidence rating.
|
||||
- New findings get the next free Dxx in `docs/PCPortResearch.md` §F/§H + an
|
||||
index row; recurring bug classes go to `docs/PORT-LEARNINGS.md`.
|
||||
- After any converter change: full regen chain
|
||||
`python tools_pc/d43_emit.py ntsc-final && python tools_pc/d69_emit.py
|
||||
ntsc-final && python tools_pc/d88_emit.py ntsc-final --regen`.
|
||||
- Verification anchors (quiet machine only — D153): target level boots to ≥1
|
||||
non-degenerate frame (`tools_pc/pixcount.py`), `-level_09` + `-level_20`
|
||||
unregressed (`tools_pc/framediff.py`, use `--mask` for the HUD).
|
||||
- Crashes: `tools_pc/debug.ps1` (gdb + FATAL line + backtrace → `gdb.txt`).
|
||||
|
||||
---
|
||||
|
||||
## A. VERIFY-ONLY — fix is in the tree, needs confirmation
|
||||
|
||||
### A1. D154 — `bg.c` room-geometry hit-test GBI parser (committed WIP `072b5c44`)
|
||||
|
||||
**Status:** written + builds green; **never exercised** (a no-input
|
||||
`GE_PCDUMP` capture fires no bullets, and the M-28 box was thrashed).
|
||||
|
||||
**What it is:** `bgTestRayIntersectionInRoom` (`src/game/bg.c:~3331`, called
|
||||
from `bgTestBulletHitBackground` on every shot that resolves against
|
||||
background geometry) was the D135 sibling: raw 8-byte-N64-`Gfx` byte/word
|
||||
indexing into the 16-byte PC room DL → desync → OOB `vtxbase[idx]` / walk-off
|
||||
AV the first time a wall or floor is shot. The `#ifdef PORT` port reads
|
||||
`gdl->words.w0/.w1` (G_TRI1 from `(w1>>16/8/0)&0xff/10`; G_TRI4 nibbles from
|
||||
`(u32)words.w0/.w1` with the byte-mapping table in the code comment); both
|
||||
`texturenum` recoveries return `-1` (generic impact decal/sound, parked w/ D77).
|
||||
|
||||
**Procedure:**
|
||||
1. Quiet machine. `./tools_pc/debug.ps1 -level_09`.
|
||||
2. Get into a firefight and **shoot walls/floor deliberately** (not just
|
||||
guards) for ~60 s — this is the only trigger.
|
||||
3. Pass = no AV in `bgTestRayIntersectionInRoom` / `bgTestBulletHitBackground`,
|
||||
bullets still resolve hits (decal/sound may be generic).
|
||||
4. `-level_09` + `-level_20` framediff unregressed.
|
||||
5. Then fix up the commit (`072b5c44` is labelled "WIP, unverified") and close
|
||||
the §F D154 row.
|
||||
|
||||
**Risk:** low — `#ifdef PORT` only, N64 path verbatim under `#else`. If a new
|
||||
fault appears in this function, the nibble-mapping table is the thing to
|
||||
re-derive against `rsp/graphics/gmain.s` (ground truth for GBI decoding).
|
||||
|
||||
### A2. D139 — stage-unload `cleanupObjects` type byte (fix committed M-23)
|
||||
|
||||
**Status:** fix in (`src/game/cleanup_objects.c`, `#ifdef PORT`
|
||||
`CLEANUP_PDTYPE(o)` = `((PropDefHeaderRecord*)o)->type` instead of `(u8)obj[0]`,
|
||||
which on LE reads `extrascale` and walks off the propDef blob). **Never
|
||||
verified** — the fast teardown test hit D140 first (since fixed), and M-27's
|
||||
level-exit loop was playtested through the *real* front-end flow, which does
|
||||
exercise `lvlUnloadStageTextData` → `cleanupObjects` on level exit.
|
||||
|
||||
**Procedure:**
|
||||
1. Re-check whether M-27's Dam→Facility playthrough already exercised it:
|
||||
if a real level exit completed without a teardown crash after the D139
|
||||
commit, this is done — just record "verified via M-27 exit loop" on the
|
||||
§F row and stop.
|
||||
2. Otherwise: `debug.ps1 -level_09`, complete/abort a real level exit (not
|
||||
pause-abort) and confirm no AV in `objFree` / `objFreeEmbedmentOrProjectile`.
|
||||
|
||||
**Risk:** low — one-line read-source change, every other propDef consumer
|
||||
already uses the struct member.
|
||||
|
||||
---
|
||||
|
||||
## B. Root-caused small fixes (not yet applied)
|
||||
|
||||
### B1. D74 — dead + OOB texture-wrap block in fast3d (`port/fast3d/gfx_pc.cpp:~1570`)
|
||||
|
||||
**Status:** latent. Harmless for CLAMP glyphs today; could corrupt wrapped
|
||||
textures on triangles (room panels, model skins).
|
||||
|
||||
**Root cause (confirmed against the tree):** two independent defects in the
|
||||
D74 pre-wrap block inside the per-vertex UV loop:
|
||||
1. **Always-false guard.** The codebase's `gbi.h` (`include/PR/gbi.h`, used by
|
||||
both game and fast3d via `port/shim/PR/gbi.h`) encodes wrap mode as a
|
||||
2-bit field: `G_TX_NOMIRROR|G_TX_WRAP = 0x0`, `G_TX_MIRROR = 0x1`,
|
||||
`G_TX_CLAMP = 0x2`. So `cms & G_TX_WRAP` is `& 0` — **always false**. The
|
||||
"is this tile in wrap mode" test must be **"clamp bit not set"**:
|
||||
`(cms & G_TX_CLAMP) == 0`. (Compare the correct switch at
|
||||
`gfx_opengl.cpp:706-712`, which keys on the four combinations.)
|
||||
2. **OOB index.** `tex_width2[i]` / `tex_height2[i]` are **per-texunit `[2]`
|
||||
arrays** (set up at `gfx_pc.cpp:~1471`), but the block is inside the
|
||||
**per-vertex** loop where `i` is 0..2 → read of slot `[2]` if the guard
|
||||
ever fires.
|
||||
|
||||
**Fix:**
|
||||
1. Hoist the tile-window computation out of the vertex loop: for each used
|
||||
texunit `t`, compute once — `tw = tex_width2[t]`, `th = tex_height2[t]`,
|
||||
`wrapS = (cms & G_TX_CLAMP) == 0 && tw < tex_width[t]`, same for T.
|
||||
2. In the vertex loop, apply `u = fmodf(u, tw); if (u<0) u += tw; u += uls/4`
|
||||
(and V) only when the precomputed flag is set.
|
||||
3. Delete nothing else — leave the surrounding D74 comment, extend it with a
|
||||
line noting the `G_TX_WRAP == 0` encoding trap (PORT-LEARNINGS §D class:
|
||||
"N64 hardware idioms fast3d does not emulate" — add the bit-encoding note).
|
||||
|
||||
**Verify:** build; `-level_09` + `-level_20` framediff; then a level with
|
||||
visible wrapped triangle textures — **Depot (`-level_30`)** is the known
|
||||
worst case (overlaps B2, but D74's block only fires for WRAP tiles). Check
|
||||
`tools_pc/golden/` dumps for no new garbage.
|
||||
|
||||
**Risk:** low-medium. The block has never run, so this is *new* behavior —
|
||||
if wrapped textures suddenly look wrong, the fmod window size (`tw` from
|
||||
`lrs-uls+4`/4) is the thing to validate against `rsp/graphics/gmain.s`'s
|
||||
tile-window handling before "fixing" it further.
|
||||
|
||||
### B2. D152 — find the leaking `osSetIntMask` site + proper narrow fix
|
||||
|
||||
**Status:** mitigated (self-healing lock in `port/src/libultra.c`, M-28) — a
|
||||
leaked section now costs ~2 s + a log line instead of a permanent black
|
||||
screen. The **root leak is still unidentified**.
|
||||
|
||||
**Procedure to root it:**
|
||||
1. Replay the trigger: Facility (`-level_34`), kill Trevelyan → mission-fail
|
||||
fade-out, under `tools_pc/debug.ps1`.
|
||||
2. When the log shows `D152: ... stealing from owner=...`, take the logged
|
||||
stale-owner address **and** the stealing caller's return address;
|
||||
`addr2line -e build-pc/ge007.x86_64.exe -f -C <both>` (image base
|
||||
`0x140000000`). The *owner* is the leak site.
|
||||
3. Audit target: libaudio's `alEvtqPostEvent` / `alEvtqNextEvent` /
|
||||
`sndSetSfxSlotVolume` paths (`src/libultra/audio/event.c`, `src/snd.c`) —
|
||||
look for early returns between an `osSetIntMask(OS_IM_NONE)` and its
|
||||
matching `OS_IM_ALL` (the D147 assumption that "the decomp never blocks /
|
||||
never early-returns while masked" is falsified on this path). Also check
|
||||
transient threads that acquire-and-exit (`gdb.txt` showed New Thread /
|
||||
exited churn every few k frames).
|
||||
|
||||
**Proper narrow fix (once the site is known), pick one:**
|
||||
- **(a) Preferred:** a dedicated `ALEventQueue` lock (mutex per queue or one
|
||||
global in the port layer) taken by `alEvtqPostEvent`/`alEvtqNextEvent`, so
|
||||
the audio event paths stop using the process-wide `osSetIntMask` section at
|
||||
all; then the self-healing steal becomes dead weight that can be reverted to
|
||||
the plain D147 recursive mutex.
|
||||
- **(b) Cheaper:** make the leak site balanced (the actual bug), keep the
|
||||
self-healing lock as belt-and-braces.
|
||||
|
||||
**Verify:** replay the mission-fail 3×: no `D152:` steal log lines, fade-out
|
||||
completes, screen returns to the mission-failed UI. `-level_09` framediff.
|
||||
|
||||
**Risk:** (a) is port-layer only if the lock lives in the shimmed
|
||||
`alEvtq*` wrappers — but note `event.c` is *compiled game-side libultra*, so
|
||||
the lock likely has to go inside `event.c` under `#ifdef PORT` (ABI-exception
|
||||
class, document as Dxx). Keep the self-healing lock either way until audio
|
||||
(Phase 3) lands.
|
||||
|
||||
### B3. D120 — blood-stain hang: extend `d43_emit.py` for opcode-0x18
|
||||
|
||||
**Status:** guarded, not fixed (`src/game/chr.c:~3322` caps the
|
||||
`PointUsage[]` / `CollisionVertices` walk at `numVertices+8` + bounds-checks
|
||||
`index`). Blood decals may be missing/wrong; the game survives.
|
||||
|
||||
**Root cause:** `tools_pc/d43_emit.py`'s opcode-0x18
|
||||
`ModelRoData_DisplayList_CollisionRecord` conversion is incomplete — 6 pointer
|
||||
fields widen the struct on PC, and the `PointUsage` / `CollisionVertices`
|
||||
sub-arrays' endianness + stride are not handled (only `CollisionRelatedNode`
|
||||
got the D43/D45 u32-vma treatment).
|
||||
|
||||
**Procedure:**
|
||||
1. Byte-spec the record: dump one converted guard model's opcode-0x18 region
|
||||
from a sidecar (`data/pccg-*`) and diff against the raw ROM bytes; write
|
||||
out the N64 layout (fields, sub-array strides, terminators) in the
|
||||
converter comment — same method as the D69/D88 audits.
|
||||
2. Extend `d43_emit.py`: emit the widened struct with the 6 pointers via
|
||||
`put_ptr`, and convert both sub-arrays (endianness + PC stride).
|
||||
3. Keep the `chr.c` guard until verification passes, then decide whether to
|
||||
keep it as a safety net (recommended — same philosophy as D85's bounds
|
||||
check) or revert.
|
||||
4. Regen full chain; verify: BUNKER1 (`-level_09`) guard firefight — blood
|
||||
decals now appear and no hang; framediff anchors.
|
||||
|
||||
**Risk:** medium — converter work with a runtime guard underneath, so a bad
|
||||
conversion degrades to today's behavior rather than crashing. Good subagent
|
||||
brief on its own (per HANDOFF-ARCHIVE M-9).
|
||||
|
||||
### B4. B3 — RMB aim too sensitive (`port/src/input.c`)
|
||||
|
||||
**Status:** user-reported (M-28 playtest). Port-only.
|
||||
|
||||
**Current math (aim mode, `input.c:~471`):**
|
||||
`m = 61 + |dx| * (MouseAimSpeed/100) * AIM_GAIN(4.0)`, clamped to
|
||||
`60 + AIM_BAND(20)` = 80. At the default `MouseAimSpeed=50`, **25 px/poll
|
||||
saturates the band** — a fast flick is "max stick" for many polls, and GE's
|
||||
aim curve `(stick-60)/10` then runs at full rate the whole time.
|
||||
|
||||
**Fix (in order of cheapness):**
|
||||
1. Lower the default: `mouseAimSpeed = 50 → 25` (`input.c:~128`). One line;
|
||||
users can still dial it back up in `ge007.ini`.
|
||||
2. Make the band itself tunable: register `Input.AimBand` (int, 5..40,
|
||||
default 20) and replace the `AIM_BAND` constant in the two clamp sites —
|
||||
a smaller band = finer control near the gate, a larger one = more headroom.
|
||||
3. Optional curve change if it still feels bad: make the mapping into the
|
||||
band sub-linear (`m = 61 + AIM_BAND * sqrt(gx / saturating_px)`) so small
|
||||
movements get proportionally less stick — but only after 1+2 are
|
||||
playtested; don't stack changes.
|
||||
|
||||
**Verify:** playtest-only (no headless signal): aim at a guard in BUNKER1,
|
||||
flick the mouse, confirm no overshoot orbiting the target; hipfire turn
|
||||
(`MouseTurnSpeed`) is untouched by this.
|
||||
|
||||
**Risk:** trivial — port-only, config-backed, default-reversible.
|
||||
|
||||
### B5. B4 — menu screens need an absolute pointer mode (`port/src/input.c`)
|
||||
|
||||
**Status:** user-reported (M-28). Port-only design item; **no code yet**.
|
||||
|
||||
**Problem:** front-end / watch menus with the crosshair cursor behave like
|
||||
an analog-stick game, not a PC pointer. Today all mouse input is *delta*
|
||||
based (turn/look), and menu navigation goes through `joyGetStickX/Y` level
|
||||
checks in `front.c` / `options.c` — synthetic stick deltas make menu cursors
|
||||
crawl or slam.
|
||||
|
||||
**Design (port-only, no `src/` change):**
|
||||
1. Add a **menu-pointer mode** to the input layer: when active, emit
|
||||
*absolute* stick values derived from the cursor position —
|
||||
`sx = (mouseX / windowW) * 2 - 1 → [-80..80]`, same for Y. GE's front-end
|
||||
maps stick deflection to on-screen cursor position, so absolute→absolute
|
||||
gives true pointer behavior with zero game-code changes.
|
||||
2. **Mode detection is the open question** — the port doesn't know when the
|
||||
game is in a menu. Options, cheapest first:
|
||||
- **(a)** Heuristic: mouse is *ungrabbed* (cursor visible) ⇒ menu mode.
|
||||
Pair with a rule that front-end screens release the grab and gameplay
|
||||
grabs it (the grab state already flips on focus loss; extend it to be
|
||||
driven by a key, e.g. hold-to-release, or auto-detect via the same
|
||||
signals D145 used for ESC-as-B). Simple, no game-state peeking.
|
||||
- **(b)** Peek a game-side "am I in the front end" flag through an existing
|
||||
exported symbol (check `front.c` / `lv.c` globals) and branch on it in
|
||||
`inputPoll`. Read-only access to a global is arguably inside the
|
||||
documented exception class, but **record a Dxx** if used.
|
||||
- **(c)** Explicit user toggle (config key + hotkey). Ugly but zero risk.
|
||||
3. While in menu mode: suppress aim/turn delta emission entirely; wheel and
|
||||
clicks map to A/B as today.
|
||||
|
||||
**Verify:** drive the mission-select / SELECT FILE screens with the mouse —
|
||||
cursor should track 1:1, click selects; in-game aim unaffected.
|
||||
|
||||
**Risk:** low (port-only) under option (a)/(c); (b) needs a Dxx write-up.
|
||||
Sequence *after* B4 so the gameplay feel is settled first.
|
||||
|
||||
### B6. D118d latent sibling — `front.c` menu-nav stick level checks
|
||||
|
||||
**Status:** latent, noted M-27. Same bug class as the fixed watch-menu
|
||||
over-scroll (`options.c`, `GE_WATCH_STICK_FAST{UP,DOWN}` → 0 under PORT).
|
||||
|
||||
**What to do:** find the `joyGetStick*InRange` / raw `joyGetStickY < -0x46`
|
||||
style *level* checks in `front.c` menu navigation (main menu, mode-select,
|
||||
mission-select paging) and apply the same treatment: under `#ifdef PORT`
|
||||
drop the stick fast-scroll term so keyboard/digital input goes through the
|
||||
latched single-step path only. Keep N64 verbatim under `#else`.
|
||||
|
||||
**Verify:** keyboard W/S (and D-pad) paging on every front-end menu = one
|
||||
step per press, no multi-skip; controller sticks still fast-scroll.
|
||||
|
||||
**Risk:** low — mechanical copy of an already-landed fix. Do it together with
|
||||
B5 so the menu-input area is touched once.
|
||||
|
||||
### B7. D118a residual — hipfire pitch is digital, yaw is analog
|
||||
|
||||
**Status:** parked minor feel issue. The *full* fix needs a game-side hook;
|
||||
the deferred design is:
|
||||
|
||||
An `#ifdef PORT` analog-aim hook in `bondview.c`/`bondview2.c` where GE reads
|
||||
digital C-up/C-down for hipfire pitch — replace the digital read with the
|
||||
analog stick-Y value (which our input layer can emit even in hipfire, since
|
||||
stick-Y is "move" only *when the game consumes it as such*; validate that
|
||||
emitting a small stick-Y does not also make Bond walk). This is the first
|
||||
*behavioral* `src/` edit of the aim path — it exceeds pure ABI/layout; **write
|
||||
it up with a confidence rating before implementing** (AGENTS.md #2: "stop and
|
||||
write it up"), and gate it behind a config key defaulting to *current*
|
||||
behavior.
|
||||
|
||||
**Cheaper alternative worth trying first:** raise `MOUSE_PITCH_THRESH`
|
||||
granularity by emitting the hipfire pitch as *repeated C-button pulses with
|
||||
dwell proportional to speed* — pure port-layer, approximates analog. Try this
|
||||
before touching `src/`.
|
||||
|
||||
---
|
||||
|
||||
## C. Cosmetic / graphics — investigation plans (root cause not fully known)
|
||||
|
||||
These are parked below crash/level work (`GRAPHICS-BACKLOG.md`). Each entry is
|
||||
the *next diagnostic step*, because the root cause is not yet pinned. Do not
|
||||
treat them as ready-to-apply fixes.
|
||||
|
||||
### C1. D114/D116 — HUD text + ammo digits X-mirrored per glyph
|
||||
|
||||
**Hard rule (PORT-LEARNINGS §D2): do NOT re-run the static trace** (four
|
||||
sessions did; every stage verified non-mirrored at the GL boundary) and do NOT
|
||||
apply a global texrect S-swap (mirrors the whole screen).
|
||||
|
||||
**Next attempt — pick one, in order:**
|
||||
1. **RenderDoc (or apitrace) capture of exactly one glyph's texrect draw.**
|
||||
The contradiction is "VBO verified non-mirrored but pixels mirrored" — a
|
||||
GPU-side capture resolves it in one shot: inspect the bound texture's
|
||||
upload bytes vs the `gDPLoadTextureBlock` source, and the final quad's UVs.
|
||||
2. **Asymmetric-1-texel experiment:** temporarily force one HUD glyph's image
|
||||
to a 2×1 or 3×1 *asymmetric* pattern (e.g. left half white) in the port
|
||||
layer; if it renders right-half-white, the flip is between upload and
|
||||
sample (texture origin / `glPixelStorei(GL_UNPACK_ROW_LENGTH)` / Y-flip);
|
||||
if it renders correctly, the mirror is applied *per-glyph placement*
|
||||
(texrect S-range inversion in `textRenderGlyph`'s GBI — check the
|
||||
`gDPTile`/`gDPSetTile` uls/lrs signs for the HUD tiles specifically).
|
||||
|
||||
Suspects to check with the capture in hand: 4-bit CI tile upload stride vs
|
||||
`G_IM_SIZ_4b` sampling, TLUT index endianness (wrong-but-symmetric TLUT would
|
||||
not mirror — deprioritize), and the `uls/lrs` sign convention for *rect*
|
||||
draws (D116 is a rect path; triangles were never suspected).
|
||||
|
||||
### C2. D75 — front-end 3D models misplaced / absent (logo, gun-barrel Bond, cast)
|
||||
|
||||
Triage per §F D75 — **distinguish (a) vs (b) before any fix:**
|
||||
1. For the Nintendo logo and one cast model, determine which matrix path
|
||||
builds their transforms: `guRotate`/`guLookAt` (→ candidate (a), D73
|
||||
scope gap in `guint.h` `DVAL()`) or the `animInit` + embedded raw-offsets
|
||||
into `struct player` path (→ candidate (b), independent model/RW-pool bug,
|
||||
cf. D56/D100/D102 — all now-fixed struct-pun classes; a *new* instance of
|
||||
the class is the leading hypothesis).
|
||||
2. Capture `GE_PCDUMP` frames across the logo transition and gun-barrel/cast
|
||||
segments to localize which frame first diverges.
|
||||
3. Files: `src/libultra/gu/guint.h`, `src/game/model.c`, `bondview2.c`
|
||||
(animInit / modelSetScale), `port/fast3d/gfx_pc.cpp`.
|
||||
|
||||
**Note:** D148/D149 below are the same family — fixing D75's root cause will
|
||||
likely take them with it. The D149 garbled geometry is also worth one check on
|
||||
the *converter* side: the malformed sub-DL (`seg5+0x9ee4`, unresolved matrix
|
||||
ptr + garbage opcodes) may be a `d43_emit.py` conversion defect rather than a
|
||||
runtime transform bug — dump that sub-DL's raw ROM bytes vs the sidecar before
|
||||
assuming it's runtime.
|
||||
|
||||
### C3. D76 — disclaimer screen only partially drawn
|
||||
|
||||
Likely the D68 image-table class, incomplete: some `sImageTableEntry`s the
|
||||
disclaimer references are missed by `port/src/gimgfixup.c`'s bswap/shadow-sync.
|
||||
|
||||
**Procedure:**
|
||||
1. Enumerate the image entries the disclaimer screen references (trace
|
||||
`textRender`/`display_image_at_position` calls in the legal-screen code, or
|
||||
log every `gDPLoadTextureBlock` source during PPM frames f20–f100).
|
||||
2. For each, confirm it is covered by the D68 fixup (bswapped IMAGESEG w1 +
|
||||
synced compiled globalDL shadow). The "only the classification line + one
|
||||
line below draw" symptom = exactly the covered subset rendering.
|
||||
3. Files: `port/src/gimgfixup.c`, `src/game/tex.c`, image-table consumers.
|
||||
|
||||
### C4. D148 / D149 — Dam exit cutscene absent; MISSION COMPLETE models garbled
|
||||
|
||||
Both are D75 family (see C2). Additional notes:
|
||||
- **D148:** the missing piece is a *scripted* sequence — cinematic camera +
|
||||
Bond rappel anim. Once D75's model path is fixed, verify the cutscene
|
||||
trigger still fires (check whether the exit trigger even dispatches the
|
||||
cinematic on PC — it may be gated on a front-end state flag our flow skips;
|
||||
`GE_STARTMENU` tooling can crash-test the screen in isolation).
|
||||
- **D149:** see the converter-side check in C2. D144/D146 already keep the
|
||||
game alive on the corrupt DL; the fix should make the model *correct*, then
|
||||
D146's "unknown opcode → end DL" guard can stay as a safety net.
|
||||
|
||||
### C5. D85 (residual) — room DL `texLoadFromGdl` decode
|
||||
|
||||
**Status:** OPEN but safety-netted; geometry renders, so this is below level
|
||||
work. Remaining: decode the room-specific opcodes / CC-RM-LUT markers that
|
||||
`texLoadFromGdl` still handles generically, and verify `csize_*_DL_binary`
|
||||
sizing. Only worth touching if a level shows placeholder/garbage textures on
|
||||
room geometry that can't be explained by B2 (Depot wrong-colors).
|
||||
|
||||
---
|
||||
|
||||
## D. Housekeeping / cleanup (no gameplay effect)
|
||||
|
||||
### D1. Stale §F index row — D132 says "proposed fix (not applied)"
|
||||
|
||||
The D132 fix **is applied** in the tree: `tools_pc/d88_propdefs.py`
|
||||
(`PROPDEF_PC_BYTES[14/19]=32, [44]=40`; the `(14,19,38,44)` handler emits each
|
||||
`Index{k}` into the low 4 B of the 8-aligned slot at PC `8+8*k`) and
|
||||
`src/game/loadobjectmodel.c sizepropdef()` (LINK/SWITCH/LOCK_DOOR → 8 words,
|
||||
SAFE_ITEM → 10). The §F index row still reads "proposed fix (not applied) —
|
||||
see below" while the subsection says "APPLIED (M-20, commit pending)".
|
||||
**Action:** update the index row to `resolved (M-20)` and confirm the M-20
|
||||
commit landed (git-blame both files). Docs-only.
|
||||
|
||||
### D2. Stale BACKLOG technical-cleanup items
|
||||
|
||||
`docs/BACKLOG.md` "Technical cleanup" lists:
|
||||
- ~~"Add a D24-implications bug class to PORT-LEARNINGS"~~ — **done M-19**
|
||||
(PORT-LEARNINGS §E, lines ~374).
|
||||
- ~~"Watch item: `osYieldThread` = `Sleep(0)`"~~ — **done M-19** (§E, line
|
||||
~407). Keep the watch item itself; it's still accurate (the one place we
|
||||
fake cooperative scheduling — first suspect for level-sweep hot-loop
|
||||
misbehavior under host load).
|
||||
- "Remove TEMP D63 debug scaffolding" — **partially done M-19** (`libultra.c`
|
||||
+ game-side probe blocks removed). Remaining: `port/fast3d/gfx_pc.cpp`
|
||||
trail code — the `/* TEMP D63 (strip before commit) */` block at ~2462, the
|
||||
`GE_D63`-gated dram-branch trace at ~2516-2531, and the unknown-opcode dump
|
||||
at ~2759-2765. All env-gated / inert; strip in one pass (keep the
|
||||
`cached = getenv("GE_D63")` pattern only if a probe is retained — recommend
|
||||
removing all of it).
|
||||
|
||||
**Action:** one docs+code cleanup commit: mark the two done items, strip the
|
||||
gfx_pc.cpp D63 trail, rebuild + `-level_09` framediff.
|
||||
|
||||
### D3. `osYieldThread = Sleep(0)` watch item (no action — recorded)
|
||||
|
||||
When a flaky timing bug appears (especially Phase 3 audio underruns): reach
|
||||
first for host thread priorities (`SetThreadPriority`: scheduler thread
|
||||
time-critical, tick normal) and the deferred `GE_DETERM` mode (D117) — not
|
||||
for kernel changes. See PORT-LEARNINGS §E.
|
||||
|
||||
---
|
||||
|
||||
## Suggested order if working through this list
|
||||
|
||||
1. **A1 + A2** (verify-only; closes two open rows, ~an hour on a quiet box).
|
||||
2. **B4** (one-line default + optional knob; immediate user-facing win).
|
||||
3. **D2** (cleanup commit; keeps the tree honest).
|
||||
4. **B1** (D74 — small, self-contained, un-lats a real OOB).
|
||||
5. **B6 + B5** (menu input, one pass over the area).
|
||||
6. **B2** (needs the user's Facility replay to get the log line).
|
||||
7. **B3** (converter work; good standalone subagent brief).
|
||||
8. **C1–C4** only after crashes/levels are done (parked by priority decision);
|
||||
C1 first — it's the most visible remaining cosmetic defect.
|
||||
Reference in new issue
Block a user