mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-23 15:52:42 +02:00
D478-D490: crash log, session start, performance, fidelity, regions
Crash-log snprintf (D478), start-mp folder (D479), shader pre-warm (D480), render worker thread (#92 low-end 45 to 60 fps, D481), lighter first-launch defaults (D482), sniper crouch/zoom fixes (D483-D485, #136), Rareware text TMEM swap (D75), D173 residual, turret/explosion fidelity (D486, #126), 21-level Windows golden rebase with a two-tier gate, PAL and JP convert/build/ boot (D258) with PAL frame height (D487) and osTvType (D488). Refs: D478 D479 D480 D481 D482 D483 D484 D485 D486 D75 D173 D258 D487 D488 #136 #126
This commit is contained in:
1 parent
88bcef32a3
commit
4b38c2566e
97 files changed
+8012
-441
No files matched your search
@@ -68,8 +68,8 @@ Two classes:
|
||||
| `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. **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) |
|
||||
| `GE_D288` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_tri1`, right after the D233 trivial-reject guard; cached `ge_d288`) | M-152 diag for the Silo intro stray triangle: frame-gated to `num_dls` 550–755 (M-155), now **skips `any_behind_camera==1` triangles** (M-155 follow-up — the x/w divide is meaningless for w≤0; that subset was pure false-lead noise) and logs the render-mode word (`oml`) so a live capture can ID the culprit asset against level tables. Logs any triangle whose clip-space NDC bbox covers >50% of the screen or goes non-finite, with each vertex's `w`. Bare `-level_20` re-run: 1965 sane hits in the window, still zero for the target color RGB(83,62,44) — consistent with M-155's working theory that the bare boot's scripted camera never reaches the bug state; **headless probing is at its limit, live capture is next**. | **dead** (D288 closed) — removed 2026-09-30 (ROADMAP §7 strip pass, branch `chore/strip-temp-probes`); tombstone — the generator flags it GONE |
|
||||
| `GE_D245V` | `src/game/sky.c:962` (`skyPortRenderPoly`-adjacent, `#ifdef PORT`) | D245 water-seam diag: per-call min/max of the recomputed S/T texture-coordinate ranges for the water fan vertices (rate-limited), to check whether the seam tracks a tc-shift boundary. Inert unset. | **live** (D245 OPEN) |
|
||||
| `GE_D245_FIXEDSHIFT` | `src/game/sky.c:946` (`#ifdef PORT`) | D245 test override: pins the water tc shift `k` to the given integer (0..SKY_TC_MAX_SHIFT) instead of the computed per-vertex value, to A/B the allowShift=TRUE behavior. Off by default (unset = current behavior). | **inert** (D245 fixed) — since the s16 sky path was removed 2026-09-30 nothing consumes the forced shift; strip candidate |
|
||||
| `GE_D245V` | `src/game/sky.c:962` (`skyPortRenderPoly`-adjacent, `#ifdef PORT`) | D245 water-seam diag: per-call min/max of the recomputed S/T texture-coordinate ranges for the water fan vertices (rate-limited), to check whether the seam tracks a tc-shift boundary. Inert unset. | **dead** (D245 fixed) — removed 2026-10-02 (strip batch 4, `chore/strip-batch4`); tombstone |
|
||||
| `GE_D245_FIXEDSHIFT` | `src/game/sky.c:946` (`#ifdef PORT`) | D245 test override: pins the water tc shift `k` to the given integer (0..SKY_TC_MAX_SHIFT) instead of the computed per-vertex value, to A/B the allowShift=TRUE behavior. Off by default (unset = current behavior). | **dead** (D245 fixed) — removed 2026-10-02 with the inert sky tile-capture/shift code (strip batch 4); tombstone |
|
||||
| `GE_D252` | `port/fast3d/gfx_pc.cpp:733` (`import_texture`, cached `s_d252`) | D252 rainbow-particle diag: one-shot per (address,size) import log of the tile state (tmem/fmt/siz/addr/size/line) for small textures so a suspect particle tile can be checked against its source bytes by hand. Remove once D252 is resolved. | **dead** (D252 closed) — removed 2026-09-30 (ROADMAP §7 strip pass, batch 1, branch `chore/scaffolding-removal-1`); tombstone — the generator flags it GONE |
|
||||
| `GE_D252POOL` | `src/game/image.c` (`texLoad`), `src/game/initmttex.c`, `port/src/gimgfixup.c` | **TEMP** (M-201): one line per new texture load (pool main/other, free bytes) + `POOL-FULL` events + stage pool range; `D252SYNC: N slots re-synced, M changed` per stage load (the D252 fix at work: M>0 from the 2nd stage on). Load-time only, no fps cost. | **dead** (D252 fixed) — removed 2026-09-30 (ROADMAP §7 strip pass, branch `chore/strip-temp-probes`); tombstone — the generator flags it GONE |
|
||||
| `GE_D245_OLDVTX=1` / `GE_D245_OLDTEX=1` | `src/game/sky.c` (`skyPortRenderPoly`) / `port/fast3d/gfx_pc.cpp` (CI8 import + tri-path dims) | **A/B** (M-201): restore the pre-fix s16 sky/water vertex path / the pre-fix 32x43, 16-wide water texture geometry. | **dead** (D245 fixed) — removed 2026-09-30 (ROADMAP §7 strip pass, branch `chore/strip-temp-probes`); tombstone — the generator flags it GONE. The pre-fix s16 sky/water vertex path (`skyPortRenderPoly` tail + `skyPortEmitTileShift`) went with it; the float path is now unconditional |
|
||||
@@ -79,12 +79,15 @@ Two classes:
|
||||
| `GE_INTROCAM=<n>` | `src/game/bondview_r.c` (`#ifdef PORT`, intro camera pick) | **TEMP/test-only**: force the level intro's first camera index (Dam truck shot = 5). The PRNG draw is still consumed. Logs `D306: intro camera index` when set (or with `GE_D306C`). | **TEMP** (useful repro tool) |
|
||||
| `GE_STARTWEAPON=<ITEM_IDS>` | `src/game/bondview_r.c` (`#ifdef PORT`, after the start-inventory setup) | **TEMP/test-only** (M-201, D303): adds the item + 400 rounds of its ammo and makes it the starting right-hand weapon (M16 = 13). Pair with `GE_INPUTSCRIPT` `ZHOLD`. | **TEMP** (useful repro tool) |
|
||||
| `GE_D235` | `src/boss.c` (per-stage `while (!done)` block, after `memaReset`; `#ifdef PORT`) | D235 evidence probe: logs how many GL-texture-cache entries the per-stage `videoResetTextureCache()` call purges at each level load. A cold boot into a level should log 0; a death/restart (or any transition from a level that uploaded textures) logs >0 — direct evidence of the stale-entry mechanism. The clear itself is **not** gated on this var (it's the fix, see below); the var only controls the log line. | **live** (D235) |
|
||||
| `GE_D235_OFF` | `src/boss.c` (same block) | **TEMP** A/B switch: skips the per-stage `videoResetTextureCache()`, i.e. reverts the D235 fix for one run. For comparing a restart with and without the fix. | **TEMP** (D235; remove when D235 closes) |
|
||||
| `GE_D235_RELOAD=<frames>` | `src/boss.c` (same block + `bossMainloop` retrace branch) | **TEMP** reload harness: once the title stage loads after the first level (e.g. after a scripted watch Abort), wait `<frames>` retraces, then re-enter that level via `bossSetLoadedStage()`, the front end's own mission-start call. A headless death/restart without the pointer-menu walk. Pair with `GE_INPUTSCRIPT="800:START;1200:A;1300:RIGHT;1400:A"` (abort) on `-level_XX`. | **TEMP** (D235; remove when D235 closes) |
|
||||
| `GE_D235_NEXT=<stage id>` | `src/boss.c` (with `GE_D235_RELOAD`) | **TEMP**: re-enter a *different* stage after the abort (level A → title → level B), used for D336 (Runway → Frigate). `GE_D85TEX=1` now also logs each stage's texture-pool size and address range (`src/game/initmttex.c`). | **TEMP** |
|
||||
| `GE_D235_OFF` | `src/boss.c` (same block) | **TEMP** A/B switch: skips the per-stage `videoResetTextureCache()`, i.e. reverts the D235 fix for one run. For comparing a restart with and without the fix. | **dead** (D235 closed) — removed 2026-10-02 (strip batch 4); tombstone. The D235 fix (`videoResetTextureCache()` per stage load) and the `GE_D235` log stay |
|
||||
| `GE_D235_RELOAD=<frames>` | `src/boss.c` (same block + `bossMainloop` retrace branch) | **TEMP** reload harness: once the title stage loads after the first level (e.g. after a scripted watch Abort), wait `<frames>` retraces, then re-enter that level via `bossSetLoadedStage()`, the front end's own mission-start call. A headless death/restart without the pointer-menu walk. Pair with `GE_INPUTSCRIPT="800:START;1200:A;1300:RIGHT;1400:A"` (abort) on `-level_XX`. | **dead** (D235 closed) — removed 2026-10-02 (strip batch 4); tombstone |
|
||||
| `GE_D235_NEXT=<stage id>` | `src/boss.c` (with `GE_D235_RELOAD`) | **TEMP**: re-enter a *different* stage after the abort (level A → title → level B), used for D336 (Runway → Frigate). `GE_D85TEX=1` now also logs each stage's texture-pool size and address range (`src/game/initmttex.c`). | **dead** (D235 closed) — removed 2026-10-02 (strip batch 4); tombstone |
|
||||
| `GE_D320R="tick:action;..."` | `port/src/d320repro.c` (hooked in `src/game/lv.c` per-tick PORT block) | **TEMP** forced-repro harness keyed on `g_GlobalTimer`. Actions: `roster`, `jump=<chr>,list=<hexid>,off=<n>`, `bit=<hex>`, `trace=<chr>`, `rename=<chr>,<num>`, `seed=<hi>,<lo>` (D320); `explode=<type>,<dist>` (explosionCreate ahead of Bond) and `cheat=gl|rl|inv|<id>` (game cheat activation) for D252 headless explosion/debris captures. | **dead** (D320 fixed via D329) — removed 2026-09-30 (ROADMAP §7 strip pass, branch `chore/strip-temp-probes`); tombstone — the generator flags it GONE; `port/src/d320repro.c` + its `lv.c` hook deleted |
|
||||
| `GE_AIMLOG=1` (+ `GE_INPUTSCRIPT` tokens `MDX<n>` `MDY<n>` `RHOLD` `LHOLD` `RREL`) | `port/src/input.c` | D337 aim-model harness: scripted sustained mouse deltas (treated as a grabbed mouse) and aim holds, plus a per-poll line with aim state, active device, the game's drawn crosshair x, `crosshair_x/y_pos` and camera angles. Measures range, edge-scroll and jitter headlessly. | **live** |
|
||||
| `GE_PERFSTAT=1` | `port/fast3d/gfx_pc.cpp` (`gfx_run`/`gfx_end_frame`), `port/fast3d/gfx_opengl.cpp` (start/end_frame), `src/boss.c` (main loop) | #92/D339 perf probe: per-300-frame averages of render phases (pre / display-list / post / swap+pace), tris and draw batches per frame, game-thread busy time per scheduler message, and a GL_TIME_ELAPSED GPU timer (meaningful only when GPU-bound). 2026-10-01: also prints `PERFSTAT spikes: max=..ms at frame N (dl= run=) >20ms= >33ms=` per 300-frame window (hitches hide in the averages). | **live** |
|
||||
| `GE_NOSHADERWARM=1` | `port/fast3d/gfx_pc.cpp` (`gfx_shader_list_open`) | D480 falsifier: disables the shader pre-warm (no read or write of `$S/ge007.shaders`), so first-seen combiners compile mid-frame as before. | **live** (D480) |
|
||||
| `GE_RENDERINLINE=1` | `port/src/libultra.c` (`portRenderWorkerOn`) | D481 falsifier: run gfx tasks inline on the scheduler thread (the pre-D481 path) instead of on the render worker. `GE_DETERM` forces this too. | **live** (D481) |
|
||||
| `GE_FAKE_LOWEND=1/0` | `port/src/video.c` (`videoApplyLowEndDefaults`) | D482: force the low-end GPU classification on (1) or off (0) for the one-time first-launch defaults check (`Video.LowEndConsidered`). Testing only, like `GE_FAKE_DECK`. | **live** (D482) |
|
||||
| `GE_D75V` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_vertex`, end of the copy/transform loop; cached env check) | D75 (Nintendo logo) per-batch vertex probe: for each `G_VTX` batch in frames `lo..hi`, logs frame#, source pointer, n_vertices, original dest_index, raw ob[0..2] of the first vertex, min/max of the transformed x/y/z/w across the batch, and the MP translation row. Used to prove the transform is correct (M-195) and that every batch encodes v0=0 (slot-overwrite hypothesis). Value form `"lo-hi"` (e.g. `300-900` = the logo screen); unset = off. **D473 (2026-10-01): the cache was broken (re-read on every G_VTX when unset); now a real one-shot cache.** | **live** (D75) |
|
||||
| `GE_D75D` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_vertex` G_VTX case + `gfx_sp_tri1` rejection/emit gates; cached env check) | D75 (Nintendo logo) per-frame draw-path probe (M-196): for frames `lo..hi`, logs one line/frame with the V/T token stream (`seq`), max triangle vertex index, cumulative vertices loaded, and per-triangle accounting — trivial-reject / backface-cull counts, how many tris reach GL, how many of those are on-screen (NDC bbox ⊂ [−1,1]²), invisible-flag count, vertex-color min/max, first oml word, depth z/w range + NaN count, texture-sampler presence (`utex` = GL shader, `cutex` = combiner), combine mode, prim/env color. Used to REFUTE the slot-overwrite hypothesis (DL is streamed, max index 15) and narrow the bug to a texture-modulate material rendering black. M-197 added logo-mode `lg_*` fields (sampled only on tris with the logo combine word — the frame-level `utex`/`cutex` are first-tri-wins and were the source of the "combiner claims 0" false anomaly): `lg_utex`/`lg_tex` + tile-0 descriptor + texture buffer address/id/size for the logo material. Value form `"lo-hi"` (logo window ≈ `244-320` on a bare front-end boot); unset = off. Companion one-shot `D75DCC:` log in `gfx_generate_cc` fires when `combine_mode==0x009ffe4f19ffe4f1` to decode the logo's combine slots. | **live** (D75) |
|
||||
|
||||
@@ -96,6 +99,10 @@ Two classes:
|
||||
| `GE_PADFAMILY=xbox\|ps\|nintendo` | `port/src/input.c` (`inputPadFamily`) | D471 dev override: force the controller family used for button names in the PC menus (headless screenshots without the hardware). Cached. | **live** (D471 dev knob) |
|
||||
| `GE_D466_FORCE=1` | `port/src/drawdistgameplay.c` (`portD466Active`) | D466: run the authored-distance split even at DrawDistance 100% (identity test: authored set must equal extended). Cached. | **live** (D466 test hook) |
|
||||
| `GE_D466_OFF=1` | `port/src/drawdistgameplay.c` (`portD466Active`) | D466 kill switch: disable the gameplay/render split (every gameplay reader falls back to the extended-range state, i.e. the pre-fix #125 behaviour). For A/B only. Cached. | **live** (D466 kill switch) |
|
||||
| `GE_INPUTSCRIPT_P1` | `port/src/input.c:1216` | Split-screen test harness: scripted input for player slot 1, same entry syntax as `GE_INPUTSCRIPT` (slot 0); mouse tokens and `CHOLD`/`CREL` ignored with one warning. Used with `GE_STARTMP` + `GE_MPVIRT`. | **live** (test harness) |
|
||||
| `GE_INPUTSCRIPT_P2` | `port/src/input.c:1217` | As `GE_INPUTSCRIPT_P1`, for player slot 2. | **live** (test harness) |
|
||||
| `GE_INPUTSCRIPT_P3` | `port/src/input.c:1217` | As `GE_INPUTSCRIPT_P1`, for player slot 3. | **live** (test harness) |
|
||||
| `GE_MEMPREDZONE=1` | `src/memp.c:340`, `src/memp.c:369` (+ periodic check in `port/src/video.c`) | D464 red-zone checker: pads every stage-bank allocation with a 64-byte pattern trailer and logs `MEMPREDZONE: HIT ...` at level unload, every 60 frames and at quit. Unset = one cached branch per allocation. | **live** (D464 dev tool) |
|
||||
| `GE_CRASHTEST` | `port/src/crash.c` (`crashTestMaybe`, called at the end of `crashInit`) | Deliberate-crash hook for verifying the crash handler: `GE_CRASHTEST=<N>` recurses N frames deep (capped at 64) right after the handler is installed and faults with a null write. `ge007.crash.log` should then show `#00`..`#N-1` in `crashTestRecurse`, then `SDL_main`, CRT and system frames; each frame line carries `va=0x…` for `addr2line -f -p -e build-pc/ge007.x86_64.exe <va>`. Unset/0 = no effect. | **live** (harness) |
|
||||
| `GE_ROOMPOOL` | `port/src/video.c:371` (`portRoomPoolScale`) | Scales the port's room pool by a float factor — before/after captures, and a user opt-out if the larger pool ever causes trouble on a level. | **live** |
|
||||
| `GE_BINDPROBE` | `port/src/input.c:885` | One-shot dump of the resolved PC bindings after setup (D383 bind-capture verification). | **live** (diagnostic) |
|
||||
|
||||
@@ -7,7 +7,7 @@ D43-D50,RESOLVED,"model-file loading → offline sidecar ""Plan B"" (d43_emit.py
|
||||
D51-D58,RESOLVED,"font pixeldata, osGetCount tick rate, model RW pools, cseq BE header, synth slots, RLE menu bg, watch raw offsets, gun-barrel DL",resolved
|
||||
D59-D68,RESOLVED,"intro render: blood-RLE clobber, DMA validate, OSMesgQueue, HEADS/BODIES sentinels, romCopy width, image_entry, Globalimagetable BE→LE",resolved
|
||||
D70-D74,RESOLVED,"intro-logo pixels: C-array bswap, UV path, sinf/cosf DVAL(), texture-import truncation",resolved
|
||||
D75,PARTIAL,front-end 3D model transforms (D77 audio split out below —,PARTIAL (M-197 fixed the logo texture blobs; v0.4.0 campaign 2026-09-28:
|
||||
D75,FIXED,front-end 3D model transforms (D77 audio split out below —,FIXED 2026-10-02 (branch fix/fidelity-gaps; by-eye check owed):
|
||||
D76 · D164,FIXED,Legal screen only draws line 1 —,"FIXED — this row and the ## D164 entry below were stale; the proposed one-line #ifdef PORT fix was actually committed 8dfc578f4 (2026-08-31), well before this correction (2026-09-18)."
|
||||
D159,FIXED,"front-end wallet-Bond photo ""interlaced""/combed (RC1 / D149) —",FIXED (#ifdef PORT no-op the swap in image.c)
|
||||
D161,FIXED,Depot (-level_30) ceiling = bright-blue speckle + radial rays (B2).,FIXED (port/fast3d/gfx_pc.cpp)
|
||||
@@ -337,4 +337,12 @@ D474,FIXED,fast3d micro-optimisations:,FIXED 2026-10-01 (local); output-identica
|
||||
D475,OPEN,Statue/Cradle hitches diagnosed:,OPEN (diagnosed 2026-10-01):
|
||||
D476,FIXED,CI4 textures keyed on their own 16-entry palette bank instead of the whole 256-entry table (D217 hash):,FIXED 2026-10-02 (local); Statue sweep CI4 cache misses 189 -> 36
|
||||
D477,?,CI8 palette re-imports measured and ruled out as a CPU-side hitch cause:,MEASURED 2026-10-02 (local); premise of the D475/D476 follow-up refuted on the dev box; GPU/driver-side cost unmeasured
|
||||
D478,FIXED,"D441 read-side census re-run (319 warnings, 129 outside the chr.c D34 initialisers) triaged against the current tree:","FIXED 2026-10-02 (local; snprintf bound); census triage recorded, Stage B items listed"
|
||||
D479,FIXED,Split-screen GE_STARTMP harness hid the ammo counter:,"FIXED 2026-10-02 (local); harness-only, 2P frame shows the counter in both halves"
|
||||
D480,FIXED,"Shader pre-warm: every created shader pair (shader_id0, shader_id1) is remembered in $S/ge007.shaders and compiled at the first frame of the next session, so a…","FIXED 2026-10-02 (local, perf/shader-prewarm); output-identical (menu frame exact), 0 in-play compiles on the warm run"
|
||||
D481,FIXED,Low-end ~45/30 fps (#92):,"FIXED 2026-10-02 (local, perf/render-worker); X220 (HD 3000) 44-46 -> 60.0 fps, GE_RENDERINLINE=1 falsifier restores 45.9"
|
||||
D482,FIXED,Lighter first-launch defaults on Atom/Celeron-class GPUs (#92 follow-up):,"FIXED 2026-10-02 (local, perf/lowend-defaults); X220 forced-path:"
|
||||
D483,FIXED,#136: the PC crouch key stood Bond up when he switched to the sniper rifle; the N64 keeps the stance (the weapon's DISABLE_CROUCH flag blocks both crouch-down…,"FIXED 2026-10-02 (local, fix/d483-sniper); scripted crouch -> switch -> release keeps SQUAT"
|
||||
D484,FIXED,"#136: portScaleFovY's 20-degree floor raised the game's own zoom FOVs, capping sniper/camera magnification at 20 degrees instead of the N64's 7 (about a third…","FIXED 2026-10-02 (local, fix/d483-sniper, maintainer-approved 2026-10-02); full zoom renders at 7 degrees, normal views pixel-identical"
|
||||
D485,FIXED,"#136: sniper/camera zoom stepped once per game frame with no tick scaling, so at the port's 1 tick/frame it zoomed 2x faster in real time than the N64 at 2 tic…","FIXED 2026-10-02 (local, fix/d483-sniper); 15 -> 7 takes 16 frames at 60 fps (was 8) = the N64 at 30 fps"
|
||||
D450,FIXED,"Unplugging and replugging a controller was not picked up (user, GameSir-G7 Pro; 2026-09-29).","FIXED — port-only: the render thread's gfx_sdl_handle_events drains the same SDL queue as video.c's host pump but had no case for SDL_CONTROLLERDEVICEADDED/REMOVED, so it usually swallowed them and i…"
|
||||
|
+174
-3
@@ -391,7 +391,7 @@ 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 | front-end 3D model transforms (D77 audio split out below — resolved, unrelated cause) | **PARTIAL (M-197 fixed the logo texture blobs; v0.4.0 campaign 2026-09-28: Nintendo + legal pages confirmed clean, but the Rareware logo still shows a minor texture-filtering artifact -- re-opened as a cosmetic known issue, README-disclosed): the D50 sidecar converter dropped the models' embedded texture blobs (`PnintendologoZ`/`PgoldeneyelogoZ`/`PlegalpageZ`) — `d43_emit.py` now raw-copies + round-trip-validates them; NTSC sidecars regenerated. Remaining: pal/jpn regen on ROM-holding machines.** Historical trail — 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 (upright captures, D168 fixed): "Bug 1 = D114/D116 mirror" retracted; **gun-barrel Bond RENDERS fine** (walk + fire, upright) — the "absent" reports were the flipped capture; **Nintendo logo genuinely broken** (renders as 2 white blobs, shifted left — a real `logoinst` transform/geometry bug, not a flip); cast roll not re-captured. See §F "D75 Bug 2 — M-33 UPDATE". **v0.2.0-pre playtest (user):** Nintendo logo still broken; Rareware logo nearly right but its texture filtering looks off; MISSION COMPLETE / mode-select screens have no 3D models at all, so the earlier "absent mode-select model" framing was inaccurate.** |
|
||||
| D75 | front-end 3D model transforms (D77 audio split out below — resolved, unrelated cause) | **FIXED 2026-10-02 (branch fix/fidelity-gaps; by-eye check owed): the Rareware logo's "texture-filtering artifact" was the RAREWARE text strip scrambled by a missing RDP odd-row TMEM swap, not filtering; see `### D75 round 2`.** Earlier: PARTIAL (M-197 fixed the logo texture blobs; v0.4.0 campaign 2026-09-28: Nintendo + legal pages confirmed clean, but the Rareware logo still shows a minor texture-filtering artifact -- re-opened as a cosmetic known issue, README-disclosed): the D50 sidecar converter dropped the models' embedded texture blobs (`PnintendologoZ`/`PgoldeneyelogoZ`/`PlegalpageZ`) — `d43_emit.py` now raw-copies + round-trip-validates them; NTSC sidecars regenerated. Remaining: pal/jpn regen on ROM-holding machines.** Historical trail — 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 (upright captures, D168 fixed): "Bug 1 = D114/D116 mirror" retracted; **gun-barrel Bond RENDERS fine** (walk + fire, upright) — the "absent" reports were the flipped capture; **Nintendo logo genuinely broken** (renders as 2 white blobs, shifted left — a real `logoinst` transform/geometry bug, not a flip); cast roll not re-captured. See §F "D75 Bug 2 — M-33 UPDATE". **v0.2.0-pre playtest (user):** Nintendo logo still broken; Rareware logo nearly right but its texture filtering looks off; MISSION COMPLETE / mode-select screens have no 3D models at all, so the earlier "absent mode-select model" framing was inaccurate.** |
|
||||
| D76 · D164 | **Legal screen only draws line 1 — root-caused (M-31)**: linker-adjacency assumption broken by mingw. — full `## D164` entry at file tail | **FIXED** — this row and the `## D164` entry below were stale; the proposed one-line `#ifdef PORT` fix was actually committed `8dfc578f4` (2026-08-31), well before this correction (2026-09-18). Docs simply never caught up to the merge. |
|
||||
| D159 | front-end wallet-Bond photo "interlaced"/combed (RC1 / D149) — `texSwapAltRowBytes` odd-row 8-byte pre-swap (N64 RDP odd-line TMEM XOR compensation) not reversed by fast3d | FIXED (`#ifdef PORT` no-op the swap in `image.c`) |
|
||||
| D161 | Depot (`-level_30`) ceiling = bright-blue speckle + radial rays (B2). A CI8 tile drawn with `gsDPSetTextureLUT(G_TT_NONE)` was decoded against the stale `rdp.palette` → garbage. Fix: `#ifdef`-free narrow route CI→I when `palette_fmt == G_TT_NONE` in `gfx_pc.cpp import_texture()`. | FIXED (`port/fast3d/gfx_pc.cpp`) |
|
||||
@@ -437,7 +437,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
||||
| D210 | **`chrToPatrol()` leaves `act_patrol.lastvisible60` uninitialised on PC — a raw-byte union alias (`act_init.padding[0x13]`) that shifts when `act_patrol` widens…** — full `## D210` entry at file tail | **FIXED (`chraction.c:3872`, `#ifdef PORT` → `self->act_patrol.lastvisible60 = -1;`, N64 line under `#else`).… |
|
||||
| D205 | **PC plays wrong/extra SFX for gameplay events (gunfire slap layer, explosion→scream, armour pickup, melee→Klobb idx 106) — user A/B vs N64 (M-71→M-78).** — full `## D205` entry at file tail | CLOSED (M-78) — root cause was D206; all four symptoms resolved + user-confirmed by ear. |
|
||||
| D152+ | **D152 addendum (M-31):** — full `## D152+` entry at file tail | FIXED (`src/snd.c`, `port/src/libultra.c`, `#ifdef PORT`) — fade-out repro playtest-gated, not headless-verified |
|
||||
| RC3 · D167 | **Non-power-of-two texture wrap period (`docs/dev/TEXTURE-GLITCH-ANALYSIS.md` §6 RC3 — "textures repeat oddly", residual Depot-ceiling noise after D161).** The N64 RDP masks the texel coordinate of a wrapping render tile at `1<<mask`, and GE sets `mask = texDimensionToMask(dim) = ceil(log2(dim))` (`src/game/tex.c:361`), so a non-PoT tile (Depot's 65×65 / 96×48 / 56×56 room surfaces) repeats at the **next power of two**, not at its image size the way GL `GL_REPEAT` does → the pattern is squashed/stretched and the seam lands in the wrong place. fast3d never stored `masks`/`maskt` at all (`gfx_dp_set_tile` dropped them) and wrapped purely at the uploaded image dimension. **Fix (`port/fast3d/gfx_pc.cpp`, behind the existing `Video.WrapFix` knob, default OFF):** store `masks`/`maskt` on the tile; in the hoisted per-texunit pre-wrap block in `gfx_sp_tri1` (D74 block — already lifted out of the vertex loop, indexed by texunit `t` not vertex `i`), when the tile is WRAP (no CLAMP/MIRROR bit) and `1<<mask != tex_width`, fold the UV at the N64 period `1<<mask` and clamp the `[dim, 1<<mask)` overflow band (which is a TMEM smear on console, no real texels) to the last texel so it reads as an edge streak instead of a bogus early image restart. `GE_WRAPFIX=0/1` env override added (env wins over the ini, matching `Debug.FrameDump`). **Per-level captures (`-level_09`/`-30`/`-34`/`-20`, WrapFix OFF vs ON, `GE_PCDUMP` 6-frame windows):** no crashes, 6/6 frames each; on settled/comparable frames Silo is ~pixel-identical (phash 0–11), Facility 180–260 pixel-identical, Depot shows small localized texel changes on ceiling/wall cells (dmean 5–9, no structural break); the large per-run deltas are all the D117 intro-camera-pan nondeterminism, not the fix. Default kept **OFF** — no regression, but a headless structural diff can't confirm the Depot ceiling actually looks *better*; needs a human eyeball with `Video.WrapFix=1`. Default-off is byte-identical to golden (all new behaviour is inside `if (g_wrap_fix)`). D74's dead in-vertex-loop wrap block was already reworked/hoisted at M-30; this only adds the mask-period trigger. Confidence: **medium** (mechanism correct; overflow-band handling is an approximation, not exact TMEM-smear emulation; visual win unconfirmed). porting-notes.md §D. | KNOB ADDED, default OFF (`port/fast3d/gfx_pc.cpp`). Needs user visual check on Depot. |
|
||||
| RC3 · D167 | **Non-power-of-two texture wrap period (`docs/dev/TEXTURE-GLITCH-ANALYSIS.md` §6 RC3 — "textures repeat oddly", residual Depot-ceiling noise after D161).** The N64 RDP masks the texel coordinate of a wrapping render tile at `1<<mask`, and GE sets `mask = texDimensionToMask(dim) = ceil(log2(dim))` (`src/game/tex.c:361`), so a non-PoT tile (Depot's 65×65 / 96×48 / 56×56 room surfaces) repeats at the **next power of two**, not at its image size the way GL `GL_REPEAT` does → the pattern is squashed/stretched and the seam lands in the wrong place. fast3d never stored `masks`/`maskt` at all (`gfx_dp_set_tile` dropped them) and wrapped purely at the uploaded image dimension. **Fix (`port/fast3d/gfx_pc.cpp`, behind the existing `Video.WrapFix` knob, default OFF):** store `masks`/`maskt` on the tile; in the hoisted per-texunit pre-wrap block in `gfx_sp_tri1` (D74 block — already lifted out of the vertex loop, indexed by texunit `t` not vertex `i`), when the tile is WRAP (no CLAMP/MIRROR bit) and `1<<mask != tex_width`, fold the UV at the N64 period `1<<mask` and clamp the `[dim, 1<<mask)` overflow band (which is a TMEM smear on console, no real texels) to the last texel so it reads as an edge streak instead of a bogus early image restart. `GE_WRAPFIX=0/1` env override added (env wins over the ini, matching `Debug.FrameDump`). **Per-level captures (`-level_09`/`-30`/`-34`/`-20`, WrapFix OFF vs ON, `GE_PCDUMP` 6-frame windows):** no crashes, 6/6 frames each; on settled/comparable frames Silo is ~pixel-identical (phash 0–11), Facility 180–260 pixel-identical, Depot shows small localized texel changes on ceiling/wall cells (dmean 5–9, no structural break); the large per-run deltas are all the D117 intro-camera-pan nondeterminism, not the fix. Default kept **OFF** — no regression, but a headless structural diff can't confirm the Depot ceiling actually looks *better*; needs a human eyeball with `Video.WrapFix=1`. Default-off is byte-identical to golden (all new behaviour is inside `if (g_wrap_fix)`). D74's dead in-vertex-loop wrap block was already reworked/hoisted at M-30; this only adds the mask-period trigger. Confidence: **medium** (mechanism correct; overflow-band handling is an approximation, not exact TMEM-smear emulation; visual win unconfirmed). porting-notes.md §D. | KNOB ADDED, default OFF (`port/fast3d/gfx_pc.cpp`). Needs user visual check on Depot. **Maintainer check 2026-10-02:** Depot building ceilings with the default WrapFix=0 match 1964 by eye; default stays OFF, item closed. |
|
||||
| D168 | **`GE_PCDUMP` / F12 PPM captures were vertically flipped — the entire source of the bogus D114/D116 "HUD/text X-mirror" (M-33, developer-confirmed).** — full `## D168` entry at file tail | FIXED (`port/fast3d/gfx_opengl.cpp`); D114 / D116 reclassified as capture-orientation artifacts (below). |
|
||||
| D169 | **Front-end mouse pointer can't reach the outer cells of the mission/level-select grid — only an inner ~3×3 selectable (M-33, developer bug report).** — full `## D169` entry at file tail | **FIXED (M-33, `port/src/input.c`, port-only, no `#ifdef PORT`).** The `menuMode` pointer branch now derives its clamp bounds from the live virtual screen —… |
|
||||
| D178 | **Pre-mission briefing screen: objectives blank / missing, briefing pages blank (also the D143 "briefing text blank" side effect) — FIXED (M-36).** — full `## D178` entry at file tail | **FIXED (M-36)** — high confidence |
|
||||
@@ -745,6 +745,14 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
||||
| D475 | **Statue/Cradle hitches diagnosed: low-CPU ~33 ms frames are the game's own frame limiter skipping a retrace (`boss.c` tick-interval check) after a 13-16 ms gfx task; CPU-side bursts are CI-palette re-imports (D217 keys CI textures on palette content), not first-sight loads. Room-texture pre-warm was built but gives ~no gain (kept on unmerged branch `perf/prewarm`, default off)** — full `## D475` entry at file tail | OPEN (diagnosed 2026-10-01): next levers = palette-as-texture/LRU for CI re-imports; frame-limiter interplay needs a design look |
|
||||
| D476 | **CI4 textures keyed on their own 16-entry palette bank instead of the whole 256-entry table (D217 hash): unrelated TLUT loads no longer re-import unchanged CI4 textures** — full `## D476` entry at file tail | FIXED 2026-10-02 (local); Statue sweep CI4 cache misses 189 -> 36 |
|
||||
| D477 | **CI8 palette re-imports measured and ruled out as a CPU-side hitch cause: decode+upload averages ~0.008 ms/frame (worst frame 0.44 ms); the cache already holds the animated palettes (1314 misses, 10 re-misses); one CI8 texture is drawn with several palettes per frame, so an in-place update path would thrash** — full `## D477` entry at file tail | MEASURED 2026-10-02 (local); premise of the D475/D476 follow-up refuted on the dev box; GPU/driver-side cost unmeasured |
|
||||
| D478 | **D441 read-side census re-run (319 warnings, 129 outside the `chr.c` D34 initialisers) triaged against the current tree: no live 32-bit truncation of a non-arena pointer found; one real port bug fixed (`libultra.c` crash-log `snprintf` passed a pointer as its size bound)** — full `## D478` entry at file tail | FIXED 2026-10-02 (local; snprintf bound); census triage recorded, Stage B items listed |
|
||||
| D479 | **Split-screen `GE_STARTMP` harness hid the ammo counter: it skipped the file-select screen's `selected_folder_num = -1`, so the stage load applied a blank folder-1 record (options 0: ammo, sight, auto-aim off); harness now sets -1 like a real MP launch** — full `## D479` entry at file tail | FIXED 2026-10-02 (local); harness-only, 2P frame shows the counter in both halves |
|
||||
| D480 | **Shader pre-warm: every created shader pair `(shader_id0, shader_id1)` is remembered in `$S/ge007.shaders` and compiled at the first frame of the next session, so a first-seen combiner no longer compiles mid-play (the one in-play stutter in the 2026-10-02 play capture: 7 compiles = 18.7 ms in one frame)** — full `## D480` entry at file tail | FIXED 2026-10-02 (local, `perf/shader-prewarm`); output-identical (menu frame exact), 0 in-play compiles on the warm run |
|
||||
| D481 | **Low-end ~45/30 fps (#92): the port ran fast3d inline on the N64 scheduler thread, so a VI retrace arriving mid-render reached the game late and `boss.c`'s half-frame tick gate skipped the next one (33 ms frames whenever renders take ~8+ ms); gfx tasks now run on a render worker that posts SP/DP done like the hardware** — full `## D481` entry at file tail | FIXED 2026-10-02 (local, `perf/render-worker`); X220 (HD 3000) 44-46 -> 60.0 fps, `GE_RENDERINLINE=1` falsifier restores 45.9 |
|
||||
| D482 | **Lighter first-launch defaults on Atom/Celeron-class GPUs (#92 follow-up): after D481 the remaining gap to the PD port is workload, not code (fast3d is as cheap per batch/triangle as PD's); on Intel HD 400-605 / UHD 600-605 / Bay Trail-class and software renderers, DrawDistance/LodDistance 250 -> 100 and MSAA 2 -> 1, only where still at the port default, once per ini** — full `## D482` entry at file tail | FIXED 2026-10-02 (local, `perf/lowend-defaults`); X220 forced-path: Cradle batches 482 -> 146, render CPU 8.2 -> 5.2 ms, GPU 12.7 -> 10.6 ms |
|
||||
| D483 | **#136: the PC crouch key stood Bond up when he switched to the sniper rifle; the N64 keeps the stance (the weapon's DISABLE_CROUCH flag blocks both crouch-down and crouch-up)** — full `## D483` entry at file tail | FIXED 2026-10-02 (local, `fix/d483-sniper`); scripted crouch -> switch -> release keeps SQUAT |
|
||||
| D484 | **#136: `portScaleFovY`'s 20-degree floor raised the game's own zoom FOVs, capping sniper/camera magnification at 20 degrees instead of the N64's 7 (about a third of the zoom)** — full `## D484` entry at file tail | FIXED 2026-10-02 (local, `fix/d483-sniper`, maintainer-approved 2026-10-02); full zoom renders at 7 degrees, normal views pixel-identical |
|
||||
| D485 | **#136: sniper/camera zoom stepped once per game frame with no tick scaling, so at the port's 1 tick/frame it zoomed 2x faster in real time than the N64 at 2 ticks/frame; steps now scale to base^(ticks/2) (RULE-2, maintainer-approved)** — full `## D485` entry at file tail | FIXED 2026-10-02 (local, `fix/d483-sniper`); 15 -> 7 takes 16 frames at 60 fps (was 8) = the N64 at 30 fps |
|
||||
| D407 | **Tanks cannot be boarded/exited on PC — the v0.4.0 use/reload split (D378/D393) removed the B-button tap the engine's tank handlers in `bondview2.c` consume (user report 2026-09-28, Runway/Streets)** — full `## D407` entry at file tail | PARTIAL (fix landed in v0.4.0, port-only): present `GE_CONT_B` on the use (E / pad A) rising edge only while `g_PlayerIsInTank == 1` or `g_BondCanEnterTank != 0`; E keeps its D378 no-reload-fallback semantics elsewhere, N64 layout unaffected. D407(b) same cycle: front PC Options page-edge highlight clamp + mouse-wheel / W-S paging (wheel queue consumed on the menu, D223); its wheel mapping shipped inverted and was fixed to match W/S (wheel up = step up). D407(c): board-animation lockout, menu-accept B gate in tank states, in-tank aim routed through the legacy velocity stick + `Input.TankAimScale` knob. RESOLVED 2026-09-28: all port-only (src/game zero-diff), 10 TANKDBG probes stripped, release binary verified clean, user live tank drive signed off (board + exit OK, aim feel good). |
|
||||
| D408 | **`Game.SkipIntro` skips the post-mission failure dossier: with it on, entering a level then aborting (watch Z+A) or dying (KIA) returns straight to the menus — no REPORT / "Mission status: KILLED IN ACTION / ABORTED" screen** (user report 2026-09-28; save/AllUnlocked ruled out) — full `## D408` entry at file tail | OPEN (cosmetic, not root-caused past the handoff site). v0.4.0 decision: SkipIntro stays EXPERIMENTAL / not recommended for regular users — F10 row relabelled "Skip intro (EXPERIMENTAL)". |
|
||||
| D450 | **Unplugging and replugging a controller was not picked up (user, GameSir-G7 Pro; 2026-09-29).** — full `## D450` entry at file tail | FIXED — port-only: the render thread's `gfx_sdl_handle_events` drains the same SDL queue as video.c's host pump but had no case for `SDL_CONTROLLERDEVICEADDED/REMOVED`, so it usually swallowed them and `inputRescanPads` never ran. Both pumps now post a request; the rescan runs at the top of `inputUpdate`, on the thread that reads the pads. |
|
||||
@@ -10661,6 +10669,18 @@ This entry is long and multi-pass (M-87 through M-176, spanning several sessions
|
||||
|
||||
D243 (cutscene shake/duplication, its own CONSOLIDATED NEXT STEPS block appears earlier in this file) and D292 (death sinking) are cross-referenced as the same general "third-person world model transform" family, though this session's D173 work found its specific root cause (a likely asset-decode issue in one named clip) rather than a shared code-level mechanism — don't assume D243/D292 share this exact cause without separately verifying each.
|
||||
|
||||
### D173 residual, 2026-10-02 probe pass (branch fix/fidelity-gaps; temporary `GE_D173P` probe in `chrTick`, removed)
|
||||
|
||||
Probe: the intro puppet (`model == g_CurrentPlayer->bodyModel`) logged `prop->pos`, tickamount, `actiontype`, anim index and frame after the position update, every tick, on `-level_33` (Dam) to frame 1000. 16 free-seed boots (8 at `Video.FpsCap` 60 = 1 tick/frame, 8 at 30 = 2 ticks/frame) plus 9 boots with `GE_RSEED` pinned (6 at 60, 3 at 30).
|
||||
|
||||
1. **No float, no per-tick drift.** `pos.y` is 2.48-2.59 (grounded) for the whole stand phase, identical across all boots and both tick rates; x/z constant. The D173 bulk (324 units) is gone. The phase 61 (`extending_left_hand`) to 106 transition is deterministic (T=102 at 60 fps, 104 at 30, in puppet ticks).
|
||||
2. **The residual is the idle-to-walk transition time.** The 106 (`ACT_STAND`) to 99 (walk) switch lands at T = 364..382 across free-seed boots at both rates. `chrlvTickStand` (`chraction.c` ~5133) draws `sleep = rand%5+14` per run and decrements `wallcount` (initialised `rand%120+180`, `chraction.c:508`) by it, so the transition depends on the PRNG state and on how many stand-ticks ran. With the seed pinned it is no longer random but still tick-structure dependent: 60 fps with no 2-tick frames gives T=374 (3 of 3 boots identical, bit-identical positions); one 2-tick burst gives 370-372; all-2-tick (30 fps) gives 368-370. Bond then walks (about 2 units/tick root motion), so the position at a fixed time differs by up to ~40 units mid-walk (x 20177..20195 at T=400) and converges to within ~4 units by T=460 (x 20158.5-20160.5, z 16905-16909). That matches the size and character of the reported "off by a small amount".
|
||||
3. **One deterministic one-frame transient:** the very first puppet tick reports `pos.y = -45.71` (-44.72 at 30 fps) before settling to 2.48 on the next tick, in every boot. Not the user's 1-in-10, and one frame; mechanism not traced (first `chrUpdateAnim` root-motion read at animation frame 95.0 before the first full tick).
|
||||
|
||||
Interpretation (bucket): the variance is PRNG- and tick-count-driven AI idle timing, the D13/D329 class (1 tick/frame reaches states the N64's 2-3 ticks/frame did not), and the N64 is equally random from its own seed and frame-time jitter. No port defect, ABI/layout misread or stale-animation attach was found on the intro-puppet path, and nothing here justifies a game-logic change: **no Rule-2 sign-off is requested.** The user's residual may also live elsewhere (a cutscene body model: Cradle/Dam end, Cuba credits), which this intro-only probe cannot reach; the maintainer noting WHERE it appears is the next step.
|
||||
|
||||
**Status:** residual not reproduced as a defect on the level-start puppet; explained there as faithful tick/PRNG-dependent idle timing. Confidence: medium that this accounts for "placed slightly differently"; low that it is the user's exact 1-in-10 case. Probe removed; tree clean.
|
||||
|
||||
## D179 — Packaged build crashes after the logos — the D43/D69 model + bg sidecars are ROM-derived and absent from any build that has no ROM (M-34, alpha-release QA).
|
||||
|
||||
**Packaged build crashes after the logos — the D43/D69 model + bg sidecars are ROM-derived and absent from any build that has no ROM (M-34, alpha-release QA).** The first `goldeneye-pc-port-*-win64.zip` from CI (bundle-win.sh) shows the Rare/Nintendo logos (compiled-in `assets/rarewarelogo.c`) then AVs at `0x6b157a88`/`0x70157a88` — symbolised: `modelPromoteNodeOffsetsToPointers` (`model.c`) ← `load_object_fill_header` (`objecthandler_2.c`) ← first prop/item model load. Root cause is **not** a code regression: the port loads PC-layout model geometry from `data/pcmodels-<region>/{pcmodels.bin,manifest.csv}` and stage bg/stan from `data/pccg-<region>/{pccg.bin,manifest.csv}`, both produced offline by `tools_pc/d43_emit.py` / `d69_emit.py` from the ROM (see `port/src/pcmodels.c` / `pccg.c`; `pcmodelsReserveSize` logs *"pcmodels.bin not found — model loads will fail"* and returns 0). CI has no ROM so it never runs the emit scripts, and the two `data/` dirs are (correctly) gitignored ROM-derived game data — **cannot be committed or shipped** (converted Nintendo/Rare geometry, DLs, collision, stan nav; = distributing assets). A local build with those dirs present runs clean (verified M-34: 1741 frames, `romdataInit ... mapped at 0x10000000`, `pcmodels: 512 sidecars`, `pccg: 73 sidecars`, no crash — so `0x10000000` is *not* the problem here). The `docs/building.md` sidecar-gen step was also missing entirely. **Fix = release-side, backlogged** (`docs/BACKLOG.md` → "Alpha release"): bundle the emit scripts + their committed inputs as a pure-stdlib-Python asset-prep tool the user runs once against their own ROM (~5 MB output, no MIPS toolchain). Secondary/latent: the fixed-address `VirtualAlloc((LPVOID)0x10000000)` in `romdata.c` has no working fallback (`"using heap copy — direct ROM reads will fail"` then limps into the same crash) — didn't bite M-34 but will on a machine where something occupies `0x10000000`; harden separately (reserve earliest in `main`, or retry low bases and derive all segment math from the base obtained).
|
||||
@@ -11868,6 +11888,25 @@ Superseded history below (the original OPEN investigation, root cause not found
|
||||
|
||||
**Status (from §F table):** DEFERRED (user call 2026-09-28: v0.4.0 ships NTSC-only, README-disclosed; the verified repair path is post-v0.4 backlog). Superseded: OPEN — repair path verified, not applied (out of v0.2.0 scope).
|
||||
|
||||
**2026-10-02: FIXED for both regions, verified against real PAL and JP ROMs** (maintainer-supplied PAL dump `167c3c43...` and the JP dump `2a5dade3...`, both = the decomp's `.sha1`). Every repaired row was byte-compared with the real ROM, not inferred.
|
||||
- **JP** (`filelist.j.csv`): the one font row renamed `fontZurichBold` -> `fontBankGothic_fontchartable` (its 8716 bytes at 3044276 equal the US BankGothic chartable).
|
||||
- **PAL** (`filelist.e.csv`) had more than the 31 labels found statically: (a) **47** truncated model basenames (`Pbridge_console` x6, `Pchrgrenadelauncher`, ...) renamed to the table symbols, each 47/47 byte-identical to the US file of that name; (b) **96 missing rows** reconstructed: the whole pre-bg block (jfont/efont, animation tables, Globalimagetable, Rareware logo, the usedby7F008DE4 blob, fonts, sfx/instrument banks, music.sbk + all 63 music tracks) located at constant shifts (-77968 / -39872) with byte checks, the region-specific `jfont_chardata` by the gap, and the 14 PAL-specific **ramrom** demos by walking the block with each header's `filesize` field (offset 0x80, padded to 16), which lands exactly on the font block and matches every stage id; (c) the `ob__ob_end.seg` terminator row (16 bytes at 9402416, identical to US); (d) two **swapped label pairs** at shared offsets: `bg_len`/`bg_lip` and `bg_rit`/`bg_oat` (the non-empty file proven by bytes). No N64-build file reads `filelist.e/j.csv` (only `.u`), so these are port-tooling data fixes.
|
||||
- The emitters (`d43/d69/d88_emit.py`) always read `filelist.u.csv`: now the region's own list. `port/src/romdata.c` knew only PAL vs "else NTSC": JP now expects country 'J' and `ge007.jpn-final.z64`/`baserom.j.z64`. `gen_romassets.py e|j` -> `port/src/romassets_e.s` (1773 symbols: US's 1685 + the 44 PAL-only `L*P` text banks with end markers) / `romassets_j.s` (1685).
|
||||
- PAL also failed to link: port code referenced debug-menu globals that exist only with `LEFTOVERDEBUG` (US/JP, not PAL): the D243 `GE_DEBUGMENU` branch in `src/boss.c` and the `All unlocked` debug-flag seeding in `port/src/main.c`; both now `#if defined(LEFTOVERDEBUG)` (US/JP compile unchanged; D442's query-time hooks cover All unlocked on every region).
|
||||
- **Verified (Windows, from the repo root with region-suffixed sidecars):** all six emitter runs "ALL CHECKS PASSED"; `BUILD_DIR=build-jpn ./build-pc.sh jpn-final` and `build-pal ... pal-final` link with 0 errors; both boot through legal / Nintendo / Rareware / gun barrel and play Dam (`-level_33`) with no crash; NTSC-U `verify.sh dam` still PASS.
|
||||
- **Open:** PAL ammo counter missing (D487); PAL legal-screen bottom line partly cut (compare with 1964); full-level/menu/audio/save runtime passes for both; packaging (per-region exe names, CI matrix, drop-in converter region gate).
|
||||
**Status:** FIXED (asset conversion + build + boot, PAL and JP).
|
||||
|
||||
## D487 — PAL: the in-level ammo counter is not drawn (only a small dot where "7 | 93" should be)
|
||||
|
||||
Found by the D258 PAL bring-up (2026-10-02): `-level_33` Dam on the PAL build (`build-pal`) shows the gun and gameplay normally, but the HUD ammo counter at bottom right renders as a single small yellow dot; the JP and NTSC-U builds draw "7 | 93" in the same capture. PAL takes EU-only HUD/text paths (`VERSION_EU` / `LANG_EU`) that have never run on PC, so a port-side ABI or font-table issue on an EU-only path was the first suspect.
|
||||
**Root cause (2026-10-02, maintainer pointed at TCRF: "the European version renders at 320x269@50Hz with no borders"; the whole PAL bottom was cut, not just the HUD):** `port/src/libultra.c` `osViSetMode` recovers the CFB height from `yScale` assuming the NTSC encoding `bufy * 0x800 / 480` (`src/fr.c` non-EU `video_related_8`). The EU build compiles a different `video_related_8` (`fr.c` ~478, `#ifdef VERSION_EU`) that encodes `bufy * 0x800 / (0x220 + (bufy == 330 ? 28 : 0))`, i.e. against 544 (572) lines. A temp log showed the PAL mode arriving with `yScale 0x400` (= 272 lines in the EU encoding) decoded as 240, so fast3d's native viewport was 32 lines short and the bottom ~12% of every PAL frame (HUD ammo counter, legal-screen last line) fell off. The `OS_VI_BIT_PAL` test in `osViSetMode` never fired: the EU game runs with `osTvType = MPAL` (see below) and its modes don't carry that bit.
|
||||
**Fix (port-only, `#ifdef VERSION_EU`):** decode with the EU rule (smallest height whose EU encoding reproduces `yScale`; fallback 272 = `fr.h` `SCREEN_HEIGHT_272`), keyed on the build. `video.c` `GE_NATIVE_H` for PAL (pre-VI seed only) corrected 400 -> 538 with the TCRF facts. NTSC-U/JP code unchanged.
|
||||
**Verified:** PAL Dam frame 1500 shows the full "7 | 93" ammo counter; the PAL legal screen shows its last line ("Used by permission of EMI Unart Catalog Inc."); NTSC-U `verify.sh dam` PASS; JP build/run unchanged.
|
||||
**Open question (resolved by D488, 2026-10-02):** the port set `osTvType = OS_TV_MPAL` for EU (libultra.c, comment says "PAL consoles report MPAL"). A real PAL N64 reports `OS_TV_PAL` (0). GE's `sched.c:287` and `init.c:237` test only for MPAL (else NTSC LAN1), so on real PAL hardware those take the NTSC branch while the port takes MPAL; the EU `video_related_8` then installs its own PAL-table modes (`osViModeTable[16]/[20]`), so the visible effect may be nil. Needs a 1964 PAL comparison before changing (it would also move the port's 50 Hz tick selection, which keys on MPAL||PAL).
|
||||
**Status:** FIXED (maintainer by-eye check on PAL owed).
|
||||
|
||||
|
||||
## D279 — Backfilled — this label was consumed by an undocumented partial session (2026-09-14 ~23:00) that left artifacts in `scratch/` (`d279_run.log`…
|
||||
|
||||
**Backfilled — this label was consumed by an undocumented partial session (2026-09-14 ~23:00) that left artifacts in `scratch/` (`d279_run.log`, `d279_imgmap_run.log`, `d279_g110.png`, `d279_g220*.png`, `d279_gray_1500.png`) but no written conclusion; the logs carry no D279-tagged lines, so its exact probe/finding cannot be reconstructed from the tree.** Next available label after it: D280.
|
||||
@@ -12019,6 +12058,8 @@ Superseded history below (the original OPEN investigation, root cause not found
|
||||
|
||||
**2026-09-30 (fix/d294-room-pool-cap): cap raised 2.0 -> 2.5, residual corner flicker fix landed (port-only, awaiting by-eye check).** The 3x 'regression' above was D336 (uninitialised head-anim placeholder fields), not pool size, so the cap is revisitable. `portRoomPoolScale()` (`port/src/video.c`): final cap 2.0 -> 2.5 (and the `GE_ROOMPOOL` clamp 2.0 -> 2.5); DD/Lod terms changed from `sqrtf` to `powf(x,0.66)` (4^0.66 = 2.497) because with sqrt the max-DD term is exactly 2.0 and a raised cap alone changes nothing; result is rounded UP to a quarter step because `boss.c` truncates `(s64)(scale*4)` (raw 2.497 truncated to 2.25 -> ma=506880, only 4% over Statue's 487 KB set). Statue at FovScale 150 / DD 400 now gets `ma` = 2.5 x 225280 = 563200 (was 450560), i.e. 1.16x the full 27-room set (measured sum of `D104 LOAD used` over 27 rooms = 498752). Worst case `-ma350` row = 875 KB, well inside the STAGE bank (~3.8 MB free per D336; `boss.c` still clamps to bank-left minus 128 KB). Identity at N64-faithful settings unchanged. Headless check (max FOV, DD 400, scripted walk, `GE_D294`/`GE_D104`): no `FAIL alloc` before or after -- the scripted walk cannot reproduce the fast-strafe churn, so the exhaustion counter is NOT falsified either way; maintainer to check Statue corners at max FOV by eye. No `src/game` change.
|
||||
|
||||
**Maintainer check 2026-10-02:** max FOV + max draw distance, fast strafe past the Statue plaza/park corners: no flicker. VERIFIED, closed.
|
||||
|
||||
## D211 — PC FOV slider — `Video.FovScale` (M-83 QoL wave, port-only, `WIDESCREEN-FOV-PLAN.md` Phase 4).
|
||||
|
||||
**PC FOV slider — `Video.FovScale` (M-83 QoL wave, port-only, `WIDESCREEN-FOV-PLAN.md` Phase 4).** fast3d applies a multiplicative half-angle scale to every *perspective* projection matrix as it is loaded (`gfx_apply_fov_scale()` in `gfx_pc.cpp gfx_sp_matrix`, on the `G_MTX_PROJECTION | G_MTX_LOAD` path only — GE always reloads its view projection via `guPerspectiveF` at `fr.c:725`, so a relative `G_MTX_MUL` onto an already-scaled matrix cannot compound). Columns 0 and 1 are scaled by the same factor `tan(half_orig)/tan(half_adj)` → H and V FOV widen uniformly, aspect ratio exactly preserved (no stretch), and because the game rewrites fovy every frame for aim-zoom the scale composes with zoom for free. Ortho matrices (`P[3][3]==1`) are left untouched so HUD/menu 2D is unaffected. `Video.FovScale` = percent of the original vertical FOV, 50–150, **default 100 ⇒ `g_fov_scale == 1.0f` ⇒ guarded early-return, zero FP ops on the matrix ⇒ golden dumps byte-identical**. F10 overlay row "FOV scale %". Math checked offline (`s=1.25 → fovy 60°→75°`, aspect held at 1.3333).
|
||||
@@ -12484,7 +12525,7 @@ Adversarial pre-ship review of the whole v0.3.0 window (147 commits since v0.2.2
|
||||
|
||||
**User report (2026-09-18):** crash on Cavern, early in the level, on the local v0.3.0 pre-release build this session deployed to the Deck (`~/Downloads/goldeneye-pc-port-0.3.0-deck-playtest/`). User's own hypothesis: likely related to `Game.AllUnlocked` (the F10 "unlock everything" toggle, D257), which had been previously enabled on that session/save.
|
||||
|
||||
**Fetched `ge007.crash.log` via SSH from the Deck (the LAN address) and symbolized it against the exact deployed binary** (confirmed identical build via matching ELF BuildID `d6ec0681d2e82a656c7a099f66264d6bfa4480b7` between the Deck's copy and this session's local `build-linux/ge007.x86_64` — the SHA256 differs only because `bundle`-style `patchelf --set-rpath` modifies the file after linking, not because the code differs):
|
||||
**Fetched `ge007.crash.log` via SSH from the Deck and symbolized it against the exact deployed binary** (confirmed identical build via matching ELF BuildID `d6ec0681d2e82a656c7a099f66264d6bfa4480b7` between the Deck's copy and this session's local `build-linux/ge007.x86_64` — the SHA256 differs only because `bundle`-style `patchelf --set-rpath` modifies the file after linking, not because the code differs):
|
||||
|
||||
```
|
||||
SIGNAL: 11 FAULT ADDR: 0x62
|
||||
@@ -13003,6 +13044,20 @@ Full campaign (or at minimum Silo → Caverns/Cradle) with **`GE_D322=1 GE_D204=
|
||||
|
||||
**Status:** **FIXED, user-confirmed live (2026-09-21)** — "the images and legal page and Nintendo logo are good now." Tooling fix + ntsc-final sidecars regenerated; headless-verified before the live check. Remaining: `pal-final`/`jpn-final` sidecar regeneration on machines holding those ROMs (`python tools_pc/d43_emit.py <region>` — the fix is in the tool, so any future regen is correct), and D75's last unverified scope: cast-roll models (status unknown since M-33; recheck with `GE_PCDUMP="1400-2200:25"`).
|
||||
|
||||
### D75 round 2 (2026-10-02): the Rareware logo "texture-filtering artifact" is the RAREWARE text strip, scrambled by a missing RDP odd-row TMEM swap
|
||||
|
||||
**Symptom.** Maintainer reference (1964/GEPD, face-on logo): the RAREWARE lettering under the R is clean gold glyphs. Port: the same strip is interleaved/combed garbage (alternate texel rows horizontally shifted by 2 texels), identical at `Video.TextureFilter` 0, 1 and 2 (so not a filtering/clamp/wrap issue; the seam/half-texel suspects in the triage were wrong). The R, border and panel (different textures, `D_02004FE8`/`D_02005FF0`) are fine. Colour/brightness differences vs 1964 are emulator/plugin differences and were ruled out of scope by the maintainer.
|
||||
|
||||
**Root cause.** `DL_RAREWARETEXT` (`assets/rarewarelogo.c:1153`) draws the four text pieces (`imgRAre_0x0020`, `img_raRE_0x0AE0`, `imgWAre_0x15A0`, `imgwaRE_0x2060`; each 32x32 RGBA16 plus mip chain, 2744 B) with `gsDPLoadBlock(7, 0, 0, 1371, 0)`, i.e. **dxt = 0**. On the RDP, LoadBlock only swaps odd rows' 32-bit halves on the way into TMEM when its dxt row counter advances; TMEM reads always swap odd rows (address bit 2). With dxt = 0 the load-side swap never happens, so the sampled image is the memory image with the two 32-bit halves of every 8-byte group swapped on odd rows. Rare's asset data is pre-swapped for exactly that. Decoding `imgRAre` offline both ways (scratch script, not kept): linear = garbled, odd-row 32-bit-pair swapped = clean lettering. fast3d ignored `dxt` (`gfx_dp_load_block`) and uploaded the memory image linearly. The same dxt = 0 loads are used by `tex.c` for the whole runtime texture pipeline (measured: ~580 imports in `-level_09`/`-level_33` boots, mostly CI8), but there the compensating pre-swap (`texSwapAltRowBytes`) is no-op'd by D159, so those images are already linear and must NOT be swapped again.
|
||||
|
||||
**Fix (port-only, `port/fast3d/gfx_pc.cpp`, commit a2e812c3).** `gfx_dp_load_block` records `dxt == 0` on the `LoadedTexture` (`gfx_dp_load_tile` clears it); `import_texture` emulates the read swap (swap the two 4-byte halves of each 8-byte group on odd tile rows, after destride) only when the load was dxt = 0, the format is not 32b, the tile line is a whole number of qwords with more than one row, **and the source is a compiled-in exe-image C array** (`gfx_tex_source_is_c_array`, the D71 distinction: raw 0x70xxxxxx/cart sources are the D159-linearised runtime pipeline). The swap flag is folded into the texture-cache key (`palette_hash ^= 0xD75D75D7`). No game code touched.
|
||||
|
||||
**Verification.** A temporary `GE_D75SW` log (removed): the first, ungated version fired ~260-330 times per level (the tex.c textures) and was caught and gated before commit; with the gate, over a full 3700-frame boot through the cast roll the swap fires exactly 4 times (the 4 text imports, `size=2048 line=64`), and in `-level_09` and `-level_33` (300 frames) 0 times, so non-logo output is unchanged by construction. Before/after captures at 1280x960, frame 950, filters 1 and 2: lettering garbled -> clean, crisp RAREWARE.
|
||||
|
||||
**Cast roll (checked).** `GE_PCDUMP="0-3600:60"` over the whole intro. At the real 60 fps the D70 frame numbers are about 2x later: legal ~60-240, Nintendo ~300-720, Rare ~780-1000, gun barrel ~1000-1700, GoldenEye title ~1800-1900, cast roll ~1980-3600. Every cast card from "Starring 007 James Bond" through "Guest Star Mayday" renders a textured, lit, posed character model with its caption, placed left/centre and fading in/out. Seen at contact-sheet resolution only (1/4 scale); fine detail (head/body seams, hats) was not inspected. Verdict: renders, no gross placement/texture failure; maintainer by-eye pass owed for detail.
|
||||
|
||||
**Status:** FIXED (port-only), headless-verified; by-eye confirmation owed. Confidence high that the strip defect is this swap (controlled before/after plus the offline decode agree) and that nothing outside the logo changed (0 swaps in two levels and the rest of the intro).
|
||||
|
||||
**2026-09-25 capture (user, Windows, ~46 min, Silo → Caverns, `GE_D322=1 GE_D204=1`, build `7baf4900`; log `scratch/d322_session.log`).** User: "sound gets glitchy… gunshots from your gun and other sounds start to drop out as things stack up"; "audio did get garbled" (times not noted). No crash.
|
||||
|
||||
- **Pools: no leak.** SFX voices (`sfx`) fall back to 0–2 in quiet stretches; physical voices never exhausted (min free 4/24, `lame` always 0); event queue max 28/64. The voice-pool-exhaustion/counter-drift hypothesis (ranked #1 above) is **refuted** for this session length.
|
||||
@@ -13030,6 +13085,8 @@ The same device at the same moment drains at exactly real time through DirectSou
|
||||
|
||||
**Fix:** `audioInit()` sets `SDL_HINT_AUDIODRIVER = "directsound,wasapi"` on Windows before `SDL_InitSubSystem(SDL_INIT_AUDIO)` (the only audio init in the port); `SDL_AUDIODRIVER` in the environment still overrides it (normal-priority hint). The init log line now names the driver. **Verified:** golden gate 3/3; Bunker 1 60 s with `GE_D204=1`: driver `directsound`, rt 0.997–0.999, q 240–624, one drop during the level load (pre-existing) and none after. **Owed:** a long play session on the maintainer's machine (the bursts came every ~5 min), expect `drop=` flat after the load. Then re-assess the firefight SFX drop-outs; D207's measurements found no sounds actually dropped by the 8-voice cap, so they were most likely this same block loss.
|
||||
|
||||
**Maintainer check 2026-10-02:** RAREWARE text clean in a live boot; the cast roll (all 9 cards, Bond to Mayday) renders correctly by eye, so D75 has no remaining scope (after refreshing the stale `build-pc/data` sidecars, which had shown the pre-M-197 Nintendo logo). VERIFIED.
|
||||
|
||||
## D323 — G_TL_LOD bindings of explicit-LOD textures: PC samples driver mips from LOD0; N64 samples the authored mip chain (static census, 2026-09-21)
|
||||
|
||||
**Context.** D107 established that GE's room DLs emit `G_TL_LOD` and that the port folds every LOD draw to tile 0 (driver-generated GL mips from the finest level). D236 then found + fixed the one case where that folding was *visibly* wrong for a type-1 (DETAIL) binding of an RGBA5551 cut-out. This entry closes the loop on the rest of the surface: how often do LOD-on draw classes bind textures that actually carry **authored** mip levels, and does the N64 hardware behavior differ from the port's?
|
||||
@@ -15628,6 +15685,8 @@ included; D281 keeps the shipped mitigation + disclosure, zero-saves root cause
|
||||
|
||||
**Status:** OPEN — cosmetic (no crash, no data loss; the mission still records as failed/aborted and the menus are fully functional). Root-causing needs a side-by-side of front-end state (`current_menu`, `g_CurrentStageToLoad`, `mission_failed_or_aborted`, `g_isBondKIA`) on the fail return with SkipIntro off vs on — candidate for v0.5.0 alongside the D216 audio half.
|
||||
|
||||
**Maintainer check 2026-10-02:** SkipIntro on, aborted mission shows the failure screen; no menu music glitch on return. VERIFIED, closed.
|
||||
|
||||
|
||||
## D415 — Shooting a light fixture does not "kill" its flickering light (Bunker, Caverns) (community report, 2026-09-28)
|
||||
|
||||
@@ -16224,6 +16283,8 @@ Whichever loop runs `SDL_PollEvent` first gets the event, and the render loop us
|
||||
|
||||
## D466 — #125: extended draw distance leaked into gameplay visibility (Xenia's boss fight triggers early)
|
||||
|
||||
**Credit:** MST246's investigation in #125 (developer handoff with a locally tested correction: the extended draw-distance setting also extends the room admission gameplay uses, so Xenia's original on-screen condition fires early) was used to remediate this; credited in the issue close-out.
|
||||
|
||||
**RULE-2-SIGNOFF (port-feature leak fix; approval delegated by the maintainer to a design review, 2026-10-01).** Touches `src/game` behaviour only to undo a port feature's leak into gameplay: every edit is `#ifdef PORT`, every `#else` arm is the original code, and every hook is a no-op at a draw-distance multiplier of 1 (unless `GE_D466_FORCE=1`).
|
||||
|
||||
**Problem.** `Video.DrawDistance` (default 250%) scales the level's authored `FarFog` into `viSetZRange` (`bgfog.c`, D218). That one value fed both rendering and gameplay: (1) room admission (`bgIsRoomOnScreen`, `sub_GAME_7F0B5528` reject rooms beyond `zfar`), so more rooms became `room_rendered`; (2) the fog tests (`fogPositionIsVisibleThroughFog`, `g_ScaledFarFogIntensity`, and the D222/D218 `/ multiplier` hooks in `sub_GAME_7F054C58`). Guards 2.5x farther away were therefore "on screen" / "seen" to AI scripts, `chrCheckTargetInSight` and spawn-out-of-view picks, which the N64 never did. Measured on Jungle (static camera, DD 250): 8 chrs ONSCREEN, 3 gameplay-on-screen; the 3 equals the DD 100 count.
|
||||
@@ -16327,6 +16388,8 @@ Whichever loop runs `SDL_PollEvent` first gets the event, and the render loop us
|
||||
**Probes left on the branch (TEMP, `GE_D433LOG`, cached getenv):** `D433P` macro + SHOT/FIRESND/DRYCLICK/STATS hooks in `src/game/gunfire.c`. Strip before merge.
|
||||
**Confidence:** high for the code path and the log; medium for "the reporter's trigger-hold explains it" (unmeasured).
|
||||
|
||||
**Maintainer check 2026-10-02:** taps never click; a held trigger dry-clicks until release, same as the other guns and the N64. #130 closes (the report predated the D427/D432 fire-rate fixes). CLOSED faithful.
|
||||
|
||||
## D469
|
||||
|
||||
**Status: DONE (port-only; live pad test owed).** Branch `feat/pad-presets-rebind` (cut from `release/v0.4.1`). No `src/game` edits.
|
||||
@@ -16439,3 +16502,111 @@ Whichever loop runs `SDL_PollEvent` first gets the event, and the render loop us
|
||||
- Spikes: 3 frames > 33 ms in 2420 (frames 3, 459, 762), each with render-thread `dl`/`run` 0.2-0.6 ms; none coincides with the worst CI8 frame (577).
|
||||
**Not measured:** driver-deferred work (first-draw allocation of a new texture, `glGenerateMipmap` forced by the three-point filter on every upload) shows up neither in these CPU timers nor in the averaged GPU timer. The D475 association between the bursts and palette re-imports was temporal, not a measured cost.
|
||||
**Conclusion:** GPU palette lookup / in-place update are not justified by this data. Next step if the hitches still matter: a per-frame timeline (game-thread build + limiter decision, render-thread misses/new textures/shader compiles, per-frame GPU timestamps, swap time) over >=5 runs, PresentMon for what reaches the screen, and a disable-falsifier per suspect. Low-end hardware may weigh these costs differently (a `GE_PERFSTAT` run there is still owed, ROADMAP §4).
|
||||
|
||||
## D478 — D441 census re-run triaged; crash-log `snprintf` size bound fixed
|
||||
|
||||
**Census:** `scratch/d441p2/run.py` (`-fsyntax-only -Wint-conversion -Wint-to-pointer-cast` over every compiled `.c`) on `release/v0.4.1` after strip batch 4: **319** warnings (320 on the previous run). 190 are the intentional `(s32)&ANIM_DATA_*` offset initialisers in `chr.c` (D34). The other 129 were read site by site (triage list in the local `scratch/d441p2/triage-2026-10-02.txt`).
|
||||
**Rule used:** on the current layout a 32-bit truncation only breaks if the pointer is outside the DRAM arena (the 0x70000000 window): the exe image (ImageBase 0x140000000), the C heap, or a stack above 2 GiB. An arena pointer survives a `(s32)` round trip today (positive, below 2 GiB) and only becomes a hazard when Stage B moves the arena (`GE_HIGHARENA` flavour). A K0-mirror (0x80000000+) value held in an `s32` would sign-extend; none found on a live path.
|
||||
**Fixed (port code, no game-code change):** `port/src/libultra.c` romCopy-bad-destination crash log passed `win + sizeof(win) - (wp - win)` (a pointer) as `snprintf`'s size, so the bound was meaningless; now `sizeof(win) - (size_t)(wp - win)`. Latent only (32 x `" %p"` fits in the 1200-byte buffer, and the path runs just before a FATAL).
|
||||
**Not live today, by class:**
|
||||
- *Arena pointers in `s32`/`u32` slots* (Stage B list): `stan.c` `stan_prefix.stanfile`, `bondview2.c` start-pad `stan`, `title.c` `dword_CODE_bss_8006958C`/`_80069588`/`barrelDisplayListPtr`, `initmenus.c` `ptr_menu_videobuffer`, `viewport.c` `z_buffer`, `rsp.c` RDP output buffer, `cleanup_objects.c` propDefs, `objecthandler_2.c` file arithmetic, `chr_b.c` head opcode, `initactorpropstuff.c`/`initanitable.c` animation table (the planned slot model), `mema.c` (planned `s32`+sentinel widening), `mpmenu.c` `q` = `langGet` result (bank in the arena; the neighbouring `h2` already has a PORT pointer type, `q` could follow), `audi.c` audio output buffer (`alHeapAlloc` on the music heap, `mempAlloc`ed).
|
||||
- *Stack buffers through `s32`*: `image.c` texLoad's `compbuffer[4000]` reaches `texSetBitstring(s32)` and `rzipGetSomething()` (returns `s32`). Safe because game-thread stacks are forced low on purpose (`portAllocLowStack`, `MAP_32BIT` on Linux; the Windows exe has no `DYNAMIC_BASE`/`HIGH_ENTROPY_VA`, so its stacks are allocated low). **Hazard for macOS/ARM (no `MAP_32BIT`) and for any build that turns ASLR on** — belongs to the #95 `port_addr` / Stage B work.
|
||||
- *Exe pointer truncated but never dereferenced on PC*: `fr.c` `cfb = cfb_16[0]` (exe bss) -> `sched.c` `osViSwapBuffer` (shim ignores it) and `CheckDisplayErrorBuffer` -> `crashRenderFrame` (stubbed in `n64stubs.c`).
|
||||
- *Consistent truncation of both sides of a compare*: `blood_decrypt.c` `bloodImgTranspose` (`t1`, `var_t2`, `rowend` vs `(u32)var_v1`); breaks only if a buffer straddles a 4 GiB boundary.
|
||||
- *Integers in mistyped pointer slots / prototypes* (no address involved): `bg.c` room-list `.next` flags, `stan.c` locus `rooms = 1`, `propobj.c` `save_img_index_to_obj_ani_slot(screen, m->time)`, `chraction.c` `set_actor_on_path(self, pathid)`, `audi.c` `osScAddClient(..., 1)`, `gunfire.c` fog args, `chrai.c` `ok = hatCreateForChr(...)` (truthiness).
|
||||
- *Already full-width, warning only*: `lightfixture.c:218` (`gfx->dma.addr` is `uintptr_t`; the real hazard at the next line was fixed by #107).
|
||||
- *N64-only or debug paths*: `init.c` boot decompression, `ramrom.c`/`ramromreplay.c`, `indy_*`, `othermodemicrocode.c`, `lv.c` `bgDebugRemoved7F0B9DE4`, `music.c`/`language.c`/`image.c` ROM offsets passed to `romCopy` (ROM addresses, not host pointers).
|
||||
**Verified:** build clean; the `snprintf` warning is gone. No runtime path change outside the FATAL branch, so no frame diff.
|
||||
|
||||
## D479 — `GE_STARTMP` harness: ammo counter missing (skipped file-select side effect)
|
||||
|
||||
**Symptom:** in headless split-screen runs launched with `GE_STARTMP` the ammo counter never showed (seen 2026-10-01 with both players firing AK47s); the maintainer sees it in every menu-launched match.
|
||||
**Probe** (`GE_AMMOPROBE`, TEMP, branch `probe/ammo-startmp`, never merge): in the harness match both players had `gunammooff = 0x1` (`GUNAMMOREASON_OPTION`) and `cur_player_get_ammo_onscreen_setting() == 0`. The stage load's `fileLoadSettingsForFolder(selected_folder_num)` (`options.c` -> `file.c` -> `file2.c`) ran with folder 0 and found a record with `options = 0x0000`; the solo `-level_26` shortcut, by contrast, found the real file 1 (`options = 0x013a`, ammo bit set) in a different `saves[]` slot.
|
||||
**Cause:** porting-notes D10 class. A real MP launch passes the file-select screen, whose setup sets `selected_folder_num = -1` (`front.c`), so `fileGetSaveForFoldernum(-1)` returns NULL and no Bond file's options are applied in MP (the defaults stay: ammo on). The harness skipped that screen and kept the boot default `FOLDER1`. The record it then matched is very likely an all-zero `saves[]` entry (the MP harness also skips the legal screen's `fileValidateSaves()`, the only EEPROM -> `saves[]` load, see D299 and porting-notes D10); not separately verified.
|
||||
**Fix (`src/game/lv.c`, inside the `#ifdef PORT` `GE_STARTMP` harness block only):** set `selected_folder_num = -1` before `init_mp_options_for_scenario`. No game-logic or menu-path change.
|
||||
**Verified:** `GE_STARTMP=2 GE_MPVIRT=1 GE_STARTWEAPON=8`, pinned 1280x720 ini, frame 600: both halves draw "30 | 370" with the ammo icon; rc=0; ini and eep unchanged.
|
||||
**Note:** other harness-only differences may remain because `saves[]` is unvalidated in `GE_STARTMP` runs (anything that reads save data during an MP match, e.g. unlock checks). Not observed so far.
|
||||
|
||||
## D480 — Shader pre-warm (`$S/ge007.shaders`)
|
||||
|
||||
**Symptom:** the ROADMAP §4 Statue/Cradle hitch row. The 2026-10-02 play capture (`GE_HITCHTL`, ~2 min; the maintainer felt no hitching, only the FPS counter dipping to 58/59 as a level starts) had one in-play stutter cluster, and it was shader compiles: frame 1076 spent 18.7 of its 19.2 ms display-list time compiling 7 GL programs, frame 1083 16.1 ms on 3.
|
||||
**Cause:** `gfx_lookup_or_create_shader_program` (`port/fast3d/gfx_pc.cpp`) compiles a program the first time a combiner pair is drawn. The pool lives for the session (level changes don't clear it; only a texture-filter change does, via `reset_texture_state`), so the cost comes back every session. The GLSL from `gfx_opengl_create_and_load_new_shader` is a pure function of `(shader_id0, shader_id1)` and the GL version, so ahead-of-time compiling is exact. The draw path already passes the clamp-folded `id1` that `lookup_shader` keys on, so recording inside that function captures the right pairs.
|
||||
**Fix (port layer only, `gfx_pc.cpp`):** each newly created pair is appended to `$S/ge007.shaders` (text: header `ge007-shaders v1`, then `%016llx %08x` per line; cap 1024; appended as created, so a crash loses nothing). At the top of `gfx_run` (render thread, GL context current; after the startup `videoApplyTexFilter` clear), a pending flag compiles every listed pair the pool misses, unloads it, and clears `rendering_state.shader_program` so the next draw rebinds as usual. `reset_texture_state` re-arms the flag. Logs `SHADERWARM: compiled N of M in X ms`. A missing, foreign or corrupt header rewrites the file; malformed lines are skipped; an unreadable/unwritable `$S` is skipped silently. `GE_NOSHADERWARM=1` disables reading and writing (falsifier).
|
||||
**Not changed — the level-start frame:** the 59 ms first frame of a level (316 new GL textures, `gpu_ms` 58.99, CPU texture code 0.29 ms) is the D235 stage-change texture-cache clear (needed for correctness) re-uploading and mip-generating (`glGenerateMipmap` under three-point filtering) every visible texture. The previous screen is static, so it only lengthens the load by ~0.06 s. No output-identical cheap fix: spreading uploads draws missing textures, skipping mipmaps changes filtering, and the dry-run pre-warm (`perf/prewarm`) measured ~no gain. Left as is.
|
||||
**Trade-offs:** the batch moves to frame 1 of a session: 22-28 pairs on Dam took 5.5-6.5 ms with the driver's disk cache warm, 56 ms once cold; the per-compile range in the capture is 2.5-11.5 ms, so the full-game list (unknown until it fills; capped at 1024) costs at most a few seconds once, at boot. After an F10 texture-filter change the pool is cleared and re-warmed in one stall on the next frame instead of a few compiles at a time as combiners reappear (expected; not a new hitch class). The driver's own disk cache already cuts repeat-session compiles to ~0.4 ms each (no-prewarm second run: 10 compiles, 3.6 ms total), so the gain is largest on the first session after a new exe or driver.
|
||||
**Verified 2026-10-02** (pinned 1280x720, ini/eep backed up and restored, `ge007.shaders` deleted between runs): build clean. **Output:** held mission-select menu (`GE_STARTMENU=7`, frame 300): base vs base dmean 0.000, base vs pre-warmed 0.000. Dam frame 400 differs by camera position only, and base vs base varies just as much (0.56 and 9.5; D449 bimodal intro), with one pre-warmed run at 1.13 from a base run, so no rendering change. **Effect** (temporary merge with `probe/hitch-timeline`, Dam 1500 frames): fresh list = 8 in-play compiles in 6 frames (39 ms); warm = 0 in-play compiles (`SHADERWARM: compiled 27 of 27 in 6.2 ms`); `GE_NOSHADERWARM=1` = 10 in-play compiles. **Robustness:** corrupt header (rewritten), truncated/garbage lines (skipped), read-only file (pre-warms, appends fail silently), read-only + corrupt (silent, no warm): all rc=0, no crash log. **Smoke:** 60 s `-level_09` with `GE_MEMPREDZONE=1`: rc=0, 0 hits, no crash log.
|
||||
**Owed (maintainer):** by eye, play a level you've already visited and watch the FPS counter in the first minute; one F10 texture-filter change (expect a single short stall).
|
||||
|
||||
## D481 — Render worker: the scheduler thread no longer blocks on fast3d (#92 low-end frame rate)
|
||||
|
||||
**Symptom:** #92: ~30 fps on a Celeron N3060 / Intel HD 400 where the Perfect Dark port holds 60. Reproduced on a Lenovo X220 (i7-2640M, Intel HD 3000, Mesa 23.0.3, Fedora 37): GE 44-49 fps on Dam/Statue/Cradle at EVERY setting (MSAA 1-8, anisotropy 1-16, draw distance 100-400, 640x480, vsync and FpsCap on/off), while PD built from the same toolchain held 59.4-60.0 fps on Defection/Investigation/Villa with as much or more work per frame (13-16 ms vs GE's 9-13 ms render thread; GE game thread < 0.5 ms/message; GPU 4-14 ms).
|
||||
**Probe** (`probe/hitch-timeline`, `GE_HITCHTL`, X220, Dam 1200 frames): 312 frames were 33 ms; 97% failed `boss.c`'s tick gate (`mainTickElapsed < MAIN_LOOP_TICK_INTERVAL`, about half a frame since the previous frame START); `skip_pend` ~0. Not render cost, not pending-gfx.
|
||||
**Cause (port architecture):** `osSpTaskStartGo` (`port/src/libultra.c`) ran the whole software RSP + GL present inline on the scheduler thread (`__scMain`). That thread also forwards each VI retrace to the game (`__scHandleRetrace`). A retrace arriving mid-render waited for the render to finish, so the game frame started late; the next retrace then came less than half a frame after that start and the tick gate skipped it. On the N64 the RSP/RDP are separate processors and the scheduler is never blocked; the PD port has no scheduler thread. With 1-3 ms renders (fast PCs) the delay stays under half a frame, which is why it was invisible on the dev box; at 8-12 ms (HD 3000) about one frame in four is lost, and slower machines fall to 30. D186's note in `gfx_sdl2.cpp` ("the pacing wait runs inline on the scheduler thread ... until pacing is moved off that thread") was the same mechanism seen from the FpsCap side. See porting-notes D20.
|
||||
**Fix (port layer only; no game code):** gfx tasks go to a render worker thread (one-slot mailbox, mutex + condvar) that runs the unchanged frame body (`videoStartFrame`, `videoSyncSplitScreen`, `gfx_run`, `videoEndFrame`) and then posts `OS_EVENT_SP` + `OS_EVENT_DP` via `portPostEventForce`, exactly as the inline path did; `osSpTaskStartGo` returns at once. Audio tasks stay inline (no-op). The worker gets the same low stack as the game threads (`portAllocLowStack`).
|
||||
- **Ordering:** the game submits frame N+1 only after frame N's done has round-tripped (worker -> interruptQ -> `__scTaskComplete` -> client DONE -> `boss.c`), so the slot is always empty at hand-off (a broken invariant is logged as an error; never seen). `__scExec` sets `curRSPTask` after `osSpTaskStartGo` returns and before the scheduler can read the done event, since it is the queue's only consumer. The VI framebuffer is a single constant in this port, so `__scTaskReady` is unaffected. Yield: `osSpTaskYielded` returns 0, so a yield-flagged gfx task completes normally when the worker posts SP done.
|
||||
- **New overlap and its lock:** `optionsOverlayHandleInput` (scheduler thread, controller poll) and `optionsOverlayEmit` (render) both rebuild the overlay's visible-row state; they shared a thread before, now they share an `SDL_SpinLock`. The SDL event pump was already drained from two threads (host + render, D450), so a different render thread changes nothing there.
|
||||
- **Quit (D344):** a running frame parks at its boundary as before. An idle worker that sees a quit request stops taking frames and never touches GL again. The host only waits while the renderer is inside a frame, so idle is already a safe exit point; adding a park + `glFinish` there would put a GL call where the host may be exiting.
|
||||
- **Fallbacks:** `GE_DETERM` keeps the inline path (its retrace synthesis in `osRecvMesg` is call-sequenced on synchronous task execution); `GE_RENDERINLINE=1` restores the inline path as a falsifier.
|
||||
**Verified 2026-10-02:** X220, 25 s runs, `GE_PERFSTAT`: Dam/Statue/Cradle at defaults 45.9/45.0/43.8 -> **60.0/60.0/60.0 fps**; at the maintainer's settings (MSAA 8, aniso 16, DD 400) also 60.0; 0 frames > 33 ms in all six runs. `GE_RENDERINLINE=1` reproduces 45.9 (Dam) and 43.8 (Cradle). Sim rate (`GE_D193`) 60-61 ticks per wall second. Windows (dev box): build clean; held mission-select menu frame, worker vs inline, dmean 0.000; Dam frame 400 differs by intro-camera timing only (same variance as base vs base); sim 60-61 ticks/s on both paths; `-level_09` with `GE_MEMPREDZONE=1` rc=0, 0 hits; 2P `GE_STARTMP` frame correct (both halves, HUD, ammo); `GE_DETERM` takes the inline path and runs; 10 launch/quit cycles at different frames and a quit during the briefing -> level load all exit cleanly, no crash log.
|
||||
**Maintainer-verified 2026-10-02** (dev box, 120 Hz, ~9.5 min): play felt good with high performance, including rocket fights at turbo speed; F10 overlay with mouse and pad fine; real quit via the overlay's Quit to desktop parked and exited cleanly. Window close button not separately exercised. Not yet measured on N3060-class hardware; deciding CPU vs GPU margin there is the follow-up if that class still drops.
|
||||
|
||||
## D482 — Lighter first-launch defaults on Atom/Celeron-class GPUs (#92 follow-up to D481)
|
||||
|
||||
**Why:** D481 removed the scheduler stall; an Intel HD 3000 (Lenovo X220) now holds 60 at the port defaults. The #92 reporter's Celeron N3060 / Intel HD 400 is roughly half the X220's single-thread speed and GPU clock. Measured on the X220 against the Perfect Dark port built from the same toolchain (local PD build with a draw-batch counter, never committed): GE's fast3d costs no more per unit than PD's (GE ~31-44 us/batch and ~3.1 us/triangle at PD-like settings; PD ~40-68 us/batch, ~4.8 us/triangle). The gap is workload and defaults: PD ships the authored draw distance, no MSAA and a 640x480 window; our `DrawDistance`/`LodDistance` 250 send 2.5-3x the draw batches (Cradle 482 vs 146) and MSAA 2x adds ~25-30% GPU time. Anisotropy 4x measured no cost. Scaled to the N3060, Cradle's render CPU at our defaults (~8.2 ms on the X220) is ~17 ms, at the 16.7 ms budget; at the authored distance ~10 ms.
|
||||
**Change (port layer):** `gfx_opengl.cpp` captures `GL_RENDERER` into a static buffer at init (`gfx_opengl_renderer_string()`; "" if the driver returns NULL; note `gfx_opengl_log_info()` only runs under `--debug-gl`, so the capture is separate). `video.c` `videoApplyLowEndDefaults()` runs in `videoInit` right after `gfx_init`, once per ini (`Video.LowEndConsidered`, written on every machine). On a low-end renderer it lowers `DrawDistance` 250 -> 100, `LodDistance` 250 -> 100 and `MSAA` 2 -> 1 (also `gfx_msaa_level`, which `videoInit` derived before `gfx_init`; the game framebuffer gets its storage per frame, so no restart), each only if still at the port default. Players raise them in F10; the D440 "Port defaults" preset restores 250/250. Not touched: window size (auto-fit stays; its GPU cost is similar to MSAA's and a small window is a visible UX change), anisotropy, FpsCap, texture filter.
|
||||
**Classifier (`videoIsLowEndRenderer`, pure):** Mesa tags `(BYT)` `(BSW)` `(CHV)` `(APL)` `(GLK)`, older Mesa names Bay Trail / Braswell / Cherryview / Apollo Lake / Gemini Lake, software/virtual renderers (llvmpipe, softpipe, SVGA3D, Microsoft Basic Render, GDI Generic); otherwise the maximal digit run after `HD Graphics` (also inside `UHD Graphics`) must be one of 400, 405, 500, 505, 600, 605 (so 4000/5000/5500/6000 and 520/620 do not match). A numberless `Intel(R) HD Graphics` counts as low-end ON PURPOSE: Bay Trail and Celeron/Pentium parts report it, no Core-series part does, and a miss costs one reversible lightening. Do not tighten without measurements. `GE_FAKE_LOWEND=1/0` forces the decision (testing).
|
||||
**Ordering (do not move):** this cannot sit next to `videoApplySteamDeckPreset` in `main.c`: the Deck preset runs before any GL context exists, and this needs `GL_RENDERER`. The two touch disjoint keys (Deck: window/fullscreen; this: DD/LOD/MSAA), so `GE_FAKE_DECK=1` + `GE_FAKE_LOWEND=1` compose.
|
||||
**Verified 2026-10-02:** classifier compiled standalone against 21 real renderer strings, 0 mismatches (low-end: HD 400/405, Mesa HD 400 (BSW), numberless HD Graphics, UHD 600/605, Mesa HD 500 (APL 2), Mesa UHD 605 (GLK 3), Bay Trail, llvmpipe; not: HD 3000/4000/4600/5000/6000/520, UHD 620, Iris Xe, RTX 5090, Radeon, ""). Windows (RTX 5090): "not low-end", held menu frame identical to the D481 build (dmean 0.000), `-level_09` with `GE_MEMPREDZONE=1` rc=0, 0 hits. X220 (real string "Mesa Intel(R) HD Graphics 3000 (SNB GT2)"): fresh ini not low-end with the flag written; forced on: 100/100/1 written; second launch no re-apply; a pre-set DrawDistance=300 kept; flag already set: no change; forced on then user raises DD to 300: stays 300 on relaunch; forced off: not low-end, flag written. Evidence, forced path vs defaults (1224x918, 20 s, `GE_PERFSTAT`): Dam batches 105 -> 99, GPU 13.1 -> 9.6 ms; Statue batches 292 -> 171, render CPU 6.4 -> 5.1 ms, GPU 12.8 -> 8.0 ms; Cradle batches 482 -> 146, triangles 4562 -> 1650, render CPU 8.2 -> 5.2 ms, GPU 12.7 -> 10.6 ms; 60 fps in both (GPU-timer values are rough with vsync; relative only). Not verified: a real N3060.
|
||||
|
||||
## D483 — #136: switching to the sniper rifle stood a crouched Bond up (PC crouch key)
|
||||
|
||||
**Report (#136, outside):** "crouching with sniper rifle is not possible".
|
||||
**Original code:** the sniper rifle's stats carry `WEAPONSTATBITFLAG_DISABLE_CROUCH` (`assets/obseg/gun/gunWeaponStats.inc.c`). `bondview2.c` (both control paths, ~5177-5209 and ~5448-5468) then turns aim-mode C-up/C-down (or stick Y) into zoom and leaves `crouchDown`/`crouchUp` false. So on the N64 you cannot change stance while holding the sniper; you keep the stance you had when you switched to it. The report is therefore partly faithful.
|
||||
**Port divergence:** the PC crouch key (`port/src/input.c`, the free-crouch block) treated a disable-crouch weapon like "crouch not allowed" and called `inputDropCrouch()`, which stood a port-crouched Bond up as soon as he switched to the sniper.
|
||||
**Fix (port layer):** while a disable-crouch weapon is out, the free-crouch block does nothing: no new crouch, no stand-up, and no injected C-down (which zooms out with this weapon). Switching back resumes the normal rules. `GE_INPUTLOG` logs `free crouch frozen (weapon N disables crouch, crouchpos=, key=)` once per entry.
|
||||
**Verified 2026-10-02 (Dam, `GE_STARTWEAPON=17`, scripted CHOLD / weapon switches / CREL, `GE_INPUTLOG`):** crouch key held with the sniper out while standing: frozen, no crouch (`crouchpos=2`); switched to another weapon: crouch applied; switched back to the sniper: frozen while crouched (`crouchpos=0`, SQUAT); key released: no stand-up. Matches the N64 rules above.
|
||||
|
||||
## D484 — #136: the port's FOV floor capped sniper/camera zoom at 20 degrees
|
||||
|
||||
**Report (#136, outside):** "zoom rate is too low when using sniper rifle".
|
||||
**Original code:** the sniper and camera zoom is a FOV in degrees, `sniper_zoom` 60 -> 7 (`gun.c` `camera_sniper_zoom_in/out`), fed to the projection through `g_ViBackData->fovy`.
|
||||
**Port divergence:** `portScaleFovY` (`port/src/video.c`), applied to every in-level projection at `fr.c:735` since the widescreen-FOV work (`643785c2`), clamped the result to [20, 160] degrees even at default settings. The full sniper zoom therefore rendered at 20 degrees instead of 7: about a third of the N64's magnification. The same floor affected any game FOV below 20 (camera gadget, watch zoom).
|
||||
**Fix (port layer):** the floor is now min(20, the game's own fovy): it still guards the port's widening (FovScale / widescreen boost) but never narrows the game's view less than the game asked for. The 160-degree ceiling is unchanged.
|
||||
**Verified 2026-10-02:** scripted aim + C-up with the sniper on Dam: the full-zoom frame now matches a probe build with no floor (dmean 7.8, aim-timing variance) instead of the 20-degree frame (dmean 18.2); the gun model drops out of view at full zoom as on the N64; held mission-select menu frame pixel-identical (dmean 0.000), since normal FOVs never reach the floor. Mouse-aim sensitivity while zoomed now scales with the real zoom (input.c uses the same function).
|
||||
**Related (fixed separately as D485):** the zoom *speed*. `camera_sniper_zoom_in/out` step once per game frame with no tick scaling; measured at `clk=1` (one tick per frame) on the port, so the full 60 -> 7 range takes ~22 frames = ~0.4 s at 60 fps against ~0.75-1.1 s on an N64 running 20-30 fps. Only the sniper and camera use this per-frame stepping; every other zoom (weapon aim zoom, watch zoom) advances by elapsed ticks (`zoomintime += speedgraphframes`, `bondview2.c` ~3151) and is already frame-rate independent. Same class as D13/D329.
|
||||
|
||||
## D485 — #136: sniper/camera zoom speed follows game ticks (RULE-2)
|
||||
|
||||
**RULE-2-SIGNOFF:** maintainer, 2026-10-02: "I approve the rule 2 change to match the N64 real time speed and be more accurate here."
|
||||
**Divergence (class D13/D329):** `camera_sniper_zoom_in/out` (`src/game/gun.c`) multiply or divide the zoom FOV by `1 + rate*0.1` once per game frame, with no `g_ClockTimer` scaling. The N64 ran these frames at ~2 ticks each (30 fps; 3 at 20 fps); the port runs 1 tick per frame at 60 fps. Measured on the port before the fix: 15 -> 7 degrees in 8 frames, every call at `clk=1`; the full 60 -> 7 range ~22 frames (~0.4 s) against ~0.75 s on an N64 at 30 fps. Only the sniper and the camera gadget use this per-frame step; all other zooms advance by elapsed ticks (`zoomintime += speedgraphframes`, `bondview2.c`) and were already frame-rate independent.
|
||||
**Fix (`#ifdef PORT`; the N64 build expands to the original expression):** the four call sites use `GUN_ZOOM_STEP(zoom)`. On PORT it is `sqrt(1 + rate*0.1)` multiplied once per tick in this frame, i.e. `base^(ticks/2)`: an N64-style frame of 2 ticks gets exactly the original factor, a 1-tick port frame gets its square root, and `g_ClockTimer == 0` (paused/locked) gives no step. Same "N64 frame = 2 ticks" convention as the D427 fire-gate sign-off. Behaviour at other port frame rates follows automatically (a 30 fps port frame has 2 ticks).
|
||||
**Verified 2026-10-02 (Dam, `GE_STARTWEAPON=17`, scripted aim + C-up, temporary `SNIPERZOOM` log, removed before commit):** 15 -> 7 in 16 frames (947 -> 962) at `clk=1`, exactly twice the pre-fix 8 frames, i.e. the N64's real-time speed at 30 fps. Build clean; `-level_09` with `GE_MEMPREDZONE=1` rc=0, 0 hits, no crash log.
|
||||
**Owed (maintainer):** feel check against 1964 GEPD (zoom level per D484 and speed per D485).
|
||||
|
||||
## D486 — Explosions dropped in heavy firefights (#126; turrets on Cradle/Egypt/Aztec): the 6-slot explosion buffer fills with the player's own bullet-impact puffs
|
||||
|
||||
**Report:** a remote mine in Control's mainframe room did nothing (#126, outside); the maintainer then reproduced missing or "bad" turret explosions on Cradle, Egypt and Aztec during heavy fire. One 1964 mine attempt did not reproduce.
|
||||
**Mechanism (code + maintainer play log, temp `GE_D486` probe, since removed):** `explosionCreate` (`explosion.c` ~265) takes a free slot in `g_ExplosionBuffer[EXPLOSION_BUFFER_LEN = 6]` or silently does nothing (no visual, sound or damage). Nothing evicts a slot; `g_NumExplosionEntries` is only the screen-shake counter. Every player bullet that hits the background spawns a type-1 impact puff through the same call (`chrprop.c` ~1202, `chraiDefaultWeaponFireHandler`, player-only; NPC and autogun hits use the separate `explosionCreateBulletImpact` pool). A puff holds its slot exactly 46 ticks (duration 30 + 16 x flareanimspeed, tick-counted). Aztec log: 389 explosions created, 170 `NO FREE SLOT` (164 puffs, 3 x type 16, 3 x type 13, 1 turret type 8 at the moment slots 0-5 were all puffs aged 16-36, the player firing a puff every 4 ticks). No stuck slot, no `chrpropAllocate` failure, no missing stan (the first suspect, ruled out on Cradle).
|
||||
**Faithfulness:** the buffer size, puff spawning, lifetime and no-eviction are all decomp code, so the N64 drops explosions the same way. One PC/N64 difference in *incidence*: the D427/D451 auto-fire gate matches the N64 at 2 ticks/frame (a shot per 4 ticks for rate-2 weapons = 11.5 puffs wanted vs 6 slots); when the N64 itself slowed to 3 ticks/frame in a firefight it fired every 6 ticks (7.7 puffs wanted), so it dropped fewer. Not a port bug by itself; the remaining question is a 1964 A/B.
|
||||
**Related observations (same path):**
|
||||
- (a) **Explosion parts spawn per frame** (`explosionTick`, `propagationrate*age/duration + 1` per call) into a 40-part pool while parts age per tick. Simulation for type 8 (dur 120): 77 parts and pool full by age 47 at 1 tick/frame, vs 58 / age 74 at 2 and 52 / never at 3. PC fireballs are ~1.3-1.5x denser and saturate early. D13 class; a fix is game logic (Rule-2).
|
||||
- (b) **Stale parts on slot reuse:** parts are freed only in `explosionRenderPropExplosion`, and `explosionCreate` initialises only `parts[0]`, so an explosion that was offscreen for part of its life leaves up to 40 stale parts that the slot's next explosion inherits (seen: puffs starting with 22-40 parts). Same code on the N64; a plausible source of "bad looking" explosions.
|
||||
- (c) **Autogun fire is per frame** (`propobj.c` ~6347 `autogun->unkAC++`, shot on even values): 2-3x the N64's shots, tracers (`unkAC & 3`), second-muzzle shots (`unkAC & 7`), sparks and impact sounds at 60 fps. Damage per second already matches (`unkD4 += 0.16 * g_GlobalTimerDelta` per shot) and autogun hits use no explosion slots. D13 class; a fix needs Rule-2.
|
||||
- (d) Screen-shake decay (`g_NumExplosionEntries--` per call) runs per frame, so shake is 2-3x shorter in real time. D13 class, cosmetic.
|
||||
- (e) `g_Textures[besttexture]` with `besttexture == -1` (`chrprop.c` ~1195, stan-hit case) reads before the array on both platforms; what it reads differs, so whether a puff spawns there is undefined. Low confidence it matters.
|
||||
**1964 A/B (maintainer, 2026-10-02):** 1964/GEPD shows the exact same dropped explosions under heavy fire. The slot-buffer drop is **faithful** and stays; (b) stale parts is the same N64 code and stays.
|
||||
**RULE-2-SIGNOFF (maintainer 2026-10-02, "Continue with your verified code fixes", replying to the request to tick-scale the turret fire rate, explosion-part spawning and shake decay):** scope = (a), (c), (d) only.
|
||||
**Fix (`#ifdef PORT`, N64 branches byte-identical):** new `port/include/porttick.h` `portN64FrameStep()` (true on every call at >= 2 ticks/frame, every second tick below; stateless, from `g_GlobalTimer` crossing an even boundary). (c) `propobj.c` autogun fire block: `unkAC` (shot, tracer, second-muzzle cadence) advances only on a step; the per-shot damage charge uses a 2-tick frame's delta (`2.0f`) below 2 ticks/frame so damage per second is unchanged. (a) `explosion.c` `explosionTick`: no parts spawned on a non-step call (`sp9C = 0`); everything else in the block (room list, damage, gas-barrel rise) untouched. (d) `explosionScreenShake`: `g_NumSmokeEntries--` / `g_NumExplosionEntries--` only on a step; magnitude and sign logic untouched.
|
||||
**Verified (headless, Cradle `-level_41`, turrets force-destroyed by a temp probe, since removed):** type-8 parts at age 40/60/100 = 17/27/40 at FpsCap 60 AND FpsCap 30 (before: pool full at age 47 at 60 fps; now 73 vs 74); shake 5 -> 0 over 9 vs 10 ticks. Autogun cadence not exercised headlessly (no turret tracks the player at spawn): by construction it is one `unkAC` step per 2 ticks = the N64's 2-ticks/frame rate. Clean build; Cradle smoke run exit 0.
|
||||
**Status:** FIXED (a)(c)(d); drops + (b) faithful, closed. **Owed (maintainer):** a turret fight (Cradle/Aztec) at 60 fps: fire rate, tracer density and sound should feel like 1964, damage taken unchanged; explosion fireballs a little less dense; shake length like 1964.
|
||||
|
||||
**D486 note 2026-10-02:** the maintainer confirmed the dropped explosions happen the same way in 1964/GEPD (faithful). The 60 fps turret-fight feel check of the fix is still owed.
|
||||
**D433/D451 maintainer decision 2026-10-02:** auto-fire timing and the residual gunshot-sound difference accepted as good for now; no further work planned (reopen on a concrete report).
|
||||
|
||||
## D488 — PAL build reported the Brazilian MPAL TV type instead of PAL (`osTvType`)
|
||||
|
||||
`port/src/libultra.c` hard-codes `osTvType` (no hardware to read). Since the first scaffolding commit (`cd6cc158`, 2026-08-17) the PAL build set `OS_TV_MPAL` (2), with the comment "PAL consoles report MPAL" -- an inference from GE's shared code, which only ever tests for MPAL (`sched.c:287`, `init.c:237`, `fr.c` non-EU: MPAL LAN1 mode, else NTSC LAN1). Not copied from the PD reference, which hard-codes `OS_TV_NTSC` for every build and whose own game code tests `OS_TV_PAL` separately. A European N64 reports `OS_TV_PAL` (0); MPAL is Brazil's 60 Hz PAL-M console. So on real PAL hardware GE starts the scheduler in NTSC LAN1 and the EU `video_related_8` (`fr.c`, `#ifdef VERSION_EU`) then installs the PAL-table modes; the port took the Brazil-only MPAL branches instead. Never noticed because the PAL build was first compiled on 2026-10-02 (D258).
|
||||
**Fix (port-only, maintainer go-ahead 2026-10-02):** `osTvType = OS_TV_PAL` for `REFRESH_PAL`, comment corrected; the port's 50 Hz tick now keys on `OS_TV_PAL` only (MPAL is 60 Hz; no build sets it). Not game logic (the value a console reports), so no Rule-2.
|
||||
**Verified (headless):** PAL Dam frames 900/1200/1500 identical to the pre-change build (3/3); the boot sequence (legal, Nintendo, Rareware, gun barrel) identical to the post-D487 capture on every shared frame, timing unchanged; front-end mode select and mission select render, mode select identical to NTSC-U (the stamp running off the bottom is the shared 440x325 menu layout). US/JP unaffected (the change is under `REFRESH_PAL`).
|
||||
**Owed:** a PAL 50 Hz feel check against 1964 when the maintainer plays PAL.
|
||||
**Status:** FIXED.
|
||||
Reference in new issue
Block a user