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
No files matched your search
@@ -106,11 +106,11 @@ below).
|
|||||||
| Multiplayer isn't available yet | Missing feature | **Fixed on `release/v0.4.1`** (2–4 player split-screen, merged 2026-10-01); delete this row when it ships | #99, §1 |
|
| Multiplayer isn't available yet | Missing feature | **Fixed on `release/v0.4.1`** (2–4 player split-screen, merged 2026-10-01); delete this row when it ships | #99, §1 |
|
||||||
| PAL and JP ROMs aren't supported | NTSC-U only | Use an NTSC-U ROM | D258, §1 |
|
| PAL and JP ROMs aren't supported | NTSC-U only | Use an NTSC-U ROM | D258, §1 |
|
||||||
| `All unlocked` may already have written fake unlocks into a save from an older build | Save corruption | **Fixed on `release/v0.4.1`**: `All unlocked` is now a RAM-only override (D442) and never writes the save. Fakes an older build already wrote are not repaired; back up `data/ge007.eep`. Delete this row when it ships. | D442, D387, §5a |
|
| `All unlocked` may already have written fake unlocks into a save from an older build | Save corruption | **Fixed on `release/v0.4.1`**: `All unlocked` is now a RAM-only override (D442) and never writes the save. Fakes an older build already wrote are not repaired; back up `data/ge007.eep`. Delete this row when it ships. | D442, D387, §5a |
|
||||||
| `Game.SkipIntro` also skips the post-mission failure dossier | Minor | Leave SkipIntro off | D408, §2 |
|
| `Game.SkipIntro` also skips the post-mission failure dossier | Minor | **Fixed on `release/v0.4.1`** (D408, maintainer-verified 2026-10-02); delete this row when it ships | D408 |
|
||||||
| F10 overlay stretches in native widescreen (menus pillarbox correctly) | Cosmetic | **Fixed on `release/v0.4.1`** (D472 pillarboxes the overlay card); delete this row when it ships | D335b, D472, §5a |
|
| F10 overlay stretches in native widescreen (menus pillarbox correctly) | Cosmetic | **Fixed on `release/v0.4.1`** (D472 pillarboxes the overlay card); delete this row when it ships | D335b, D472, §5a |
|
||||||
| Rareware logo shows a subtle texture-filtering artifact | Cosmetic | — | D75, §2 |
|
| Rareware logo shows a subtle texture-filtering artifact | Cosmetic | **Fixed on `release/v0.4.1`** (D75: the RAREWARE text was scrambled by a missing RDP odd-row TMEM swap; maintainer-verified 2026-10-02); delete this row when it ships | D75 |
|
||||||
| Steam Deck: a first launch in Desktop Mode skips the Deck preset | Settings | **Fixed on `release/v0.4.1`** (real-Deck check owed); delete this row when it ships. Until then: first launch in Game Mode, or delete `ge007.ini` and relaunch | D283, §4 |
|
| Steam Deck: a first launch in Desktop Mode skips the Deck preset | Settings | **Fixed on `release/v0.4.1`** (real-Deck check owed); delete this row when it ships. Until then: first launch in Game Mode, or delete `ge007.ini` and relaunch | D283, §4 |
|
||||||
| ~30 fps on very low-end GPUs (e.g. Intel HD 400) | Performance | Set `Video.LowEndMode=1` in `ge007.ini` | D339, #92, §4 |
|
| ~30 fps on very low-end GPUs (e.g. Intel HD 400) | Performance | **Fixed on `release/v0.4.1`** (D481 render worker; an Intel HD 3000 laptop went from ~45 to 60 fps; D482 lowers draw/LOD distance and MSAA once on Atom/Celeron-class GPUs). Not yet confirmed on N3060-class hardware. Delete this row when it ships | D339, D481, D482, #92, §4 |
|
||||||
| No macOS or ARM builds | Platform | — | #88, #95, #101, §4 |
|
| No macOS or ARM builds | Platform | — | #88, #95, #101, §4 |
|
||||||
| Controller buttons can't be rebound in-game (keyboard/mouse can) | Missing feature | **Fixed on `release/v0.4.1`** (D469, F10 → Input → Controller); delete this row when it ships | D469, #109, §5a |
|
| Controller buttons can't be rebound in-game (keyboard/mouse can) | Missing feature | **Fixed on `release/v0.4.1`** (D469, F10 → Input → Controller); delete this row when it ships | D469, #109, §5a |
|
||||||
|
|
||||||
@@ -120,8 +120,8 @@ below).
|
|||||||
|
|
||||||
| Item | Status | Refs | Notes |
|
| Item | Status | Refs | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| **2–4 player split-screen multiplayer** | verify | #99, D416–D423, merged into `release/v0.4.1` 2026-10-01 (from `mp/virtual-seats`) | Merged 2026-10-01. D417 Rule-2 approved. Maintainer live test: every MP map in 2P (2–4 kills per map, each player scored), 4P Temple, two Xbox pads mapped P1/P2. Headless 2P/3P/4P pause, watch-menu pages, exit-confirm and F10-overlay runs clean. D420 fixed (Scores overrun), D421 closed (moot: no MP gadgets), D423 closed (faithful shadow). Still owed: 3P/4P with real pads, pads-only mode, MP audio, the full N64 MP setup screens (scenarios, handicaps, teams), D422 centred aim live on P2+ (`AimMode = 1`), D428 sway on P2+, and whether the `GE_STARTMP` harness skips something the ammo counter needs (seen only in harness launches). Per-pad seats (§5) done (D448/D450). |
|
| **2–4 player split-screen multiplayer** | verify | #99, D416–D423, merged into `release/v0.4.1` 2026-10-01 (from `mp/virtual-seats`) | Merged 2026-10-01. D417 Rule-2 approved. Maintainer live test: every MP map in 2P (2–4 kills per map, each player scored), 4P Temple, two Xbox pads mapped P1/P2. Headless 2P/3P/4P pause, watch-menu pages, exit-confirm and F10-overlay runs clean. D420 fixed (Scores overrun), D421 closed (moot: no MP gadgets), D423 closed (faithful shadow). Still owed: 3P/4P with real pads, pads-only mode, MP audio, the full N64 MP setup screens (scenarios, handicaps, teams), D422 centred aim live on P2+ (`AimMode = 1`), D428 sway on P2+; the harness-only missing ammo counter was the `GE_STARTMP` shortcut skipping file-select's `selected_folder_num = -1` (fixed, D479). Per-pad seats (§5) done (D448/D450). |
|
||||||
| **PAL and JP ROM support** | open | D258, D75 (pal/jpn sidecar regen), #85 | **Scheduled for Stage B (decided 2026-09-30):** done just before 1.0 so that region-specific issues can be unwound in isolation. Conversion is broken at the source-data level (filelist naming). A repair path has been verified but not applied. Needs PAL/JP ROMs to test. #85 was closed as "retired for now", not won't-do, so public users currently have no tracker. Reopen it or file a fresh one when work starts. |
|
| **PAL and JP ROM support** | partial | D258, D487, #85 | **2026-10-02: both regions convert, build and boot** (D258 fixed and byte-verified against real PAL and JP ROMs; region-aware emitters and ROM detection; PAL link fix). Both play Dam with no crash; NTSC-U unchanged (`verify.sh dam` PASS). Build: `BUILD_DIR=build-pal ./build-pc.sh pal-final` / `build-jpn ... jpn-final`, with `data/ge007.<region>.z64` and the emitters run for that region. **D487 fixed 2026-10-02** (PAL frame height decoded with the NTSC rule cut the bottom ~12% of every PAL frame). **D488 fixed 2026-10-02** (PAL build now reports the PAL TV type, not Brazil's MPAL). **Open:** full runtime passes per region (all levels, menus, audio, saves; PAL 50 Hz feel incl. the D427/D451/D486 2-ticks/frame gates; EU-only text paths); packaging (per-region exe names, CI matrix, drop-in converter region gate, #6). ROMs: `ge-port-reference/1964_GEPD_Edition/ROMs` (JP .n64 = v64, PAL `.z64`, both hash-verified). #85 needs reopening or a fresh tracker when this ships. |
|
||||||
| Drop-in ROM converter, remaining parts | decision | #6, [`dev/release-dropin-rom-plan.md`](dev/release-dropin-rom-plan.md) | NTSC-U drop-in shipped. Left: per-region output names and re-exec guard (with PAL/JP), native C emitters to replace the PyInstaller converter. |
|
| Drop-in ROM converter, remaining parts | decision | #6, [`dev/release-dropin-rom-plan.md`](dev/release-dropin-rom-plan.md) | NTSC-U drop-in shipped. Left: per-region output names and re-exec guard (with PAL/JP), native C emitters to replace the PyInstaller converter. |
|
||||||
| Cut content (TCRF weapons/items) | decision | [`dev/CUT-CONTENT-BACKLOG.md`](dev/CUT-CONTENT-BACKLOG.md) | Not part of the shipped game. Tier 0 already works via the debug/cheat menus. Tier 1 (config-toggle grants) is small and port-only. Tier 2/3 (placing props, re-enabling unused MP maps) need game data changes and are out of scope for this repo. |
|
| Cut content (TCRF weapons/items) | decision | [`dev/CUT-CONTENT-BACKLOG.md`](dev/CUT-CONTENT-BACKLOG.md) | Not part of the shipped game. Tier 0 already works via the debug/cheat menus. Tier 1 (config-toggle grants) is small and port-only. Tier 2/3 (placing props, re-enabling unused MP maps) need game data changes and are out of scope for this repo. |
|
||||||
|
|
||||||
@@ -129,19 +129,15 @@ below).
|
|||||||
|
|
||||||
| Item | Status | Refs | Notes |
|
| Item | Status | Refs | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Auto-fire gunshot sound slightly differs from 1964/GEPD | open | D433, #114 | Cadence matches the N64 rules exactly. The residual is subtle differences in the click/empty-magazine sounds between shots during continuous AK47 fire. **Maintainer-owned (2026-09-30):** possibly already fixed; it needs a recorded A/B against 1964/GEPD, which isn't suited to agents. Done together with the WrapFix check below. **Decided 2026-10-01:** code-side audit of the fire/empty-click sound path first (with #130), before any by-ear A/B. |
|
| Third-person Bond offset residual | partial; intro puppet explained (2026-10-02) | D173, D292 | Mostly fixed. On the level-start puppet the variance is tick/PRNG-dependent idle timing (D13 class, faithful, no sign-off requested); one 1-frame first-tick Y transient is untraced. If the residual is seen elsewhere (cutscene body model), the maintainer should note where. |
|
||||||
| Auto-fire gate at unstable 40–55 fps | partial | D427 | Exact at a steady 60 or 30 fps. At fluctuating rates it can flip between 1× and 2×, because `field_88C` is never rescaled. |
|
| Turret fire rate, explosion density and shake at 60 fps | verify | #126, D486 | **Fixed 2026-10-02 (D486, RULE-2):** autogun shots/tracers, explosion-part spawning and shake decay were counted per frame (2-3x the N64 at 60 fps); now gated to the N64 2-ticks/frame pace (`portN64FrameStep`), damage per second unchanged; headless 60-vs-30 fps A/B identical. The dropped explosions themselves are faithful (1964/GEPD drops them the same way), so #126 closes as matching the N64. **Owed:** a turret fight at 60 fps (Cradle/Aztec) feels like 1964: fire rate, tracers, sound, shake; damage taken unchanged. |
|
||||||
| Third-person Bond offset residual | partial | D173, D292 | Mostly fixed. A small drift remains about 1 time in 10. |
|
| Dam ending cutscene: Bond placement; railing transparency | open | D173, D292; maintainer 2026-10-02 | **Maintainer to re-check the end of Dam** (the last place a Bond offset was seen; the D173 level-start probe found no defect). Also seen in the Dam cutscene: an edge railing texture (a 2D element, maintainer believes) with odd transparency; possibly already fixed by the alpha pass. Capture a screenshot of each if still present. |
|
||||||
| Statue corner flicker at max FOV | verify | D294 | Room-pool cap raised 2.0 -> 2.5 (2026-09-30; Statue needs about 2.17x). It is unproven: the exhaustion log never fired at spawn. **Owed:** strafe fast past the Statue corners at max FOV and max draw distance. |
|
|
||||||
| Gameplay visibility vs modern view options (FOV, ultrawide) | verify | D466, D468, D334, D222; maintainer's local policy note | **Policy approved 2026-10-01 and implemented:** AI awareness may see what an N64 player could have seen in some configuration the cartridge supports. D466: the AI uses the authored draw distance (the player can still shoot, use and photograph whatever is drawn). Widescreen up to 16:9 is faithful (the cartridge's own Ratio 16:9 mode). **D468 (merged 2026-10-01):** under ultrawide or `Video.FovScale` > 100 the AI's on-screen view is clamped to the 16:9 / game-FOV view; option `Game.AIWideView` (F10 Gameplay, default off) gives the AI the full view. **Owed:** by-eye check on an ultrawide window or at a high FOV. D466 follow-up (2026-10-01, maintainer decision): movement/locomotion LOD follows the render, only awareness uses the N64 distance. |
|
| Gameplay visibility vs modern view options (FOV, ultrawide) | verify | D466, D468, D334, D222; maintainer's local policy note | **Policy approved 2026-10-01 and implemented:** AI awareness may see what an N64 player could have seen in some configuration the cartridge supports. D466: the AI uses the authored draw distance (the player can still shoot, use and photograph whatever is drawn). Widescreen up to 16:9 is faithful (the cartridge's own Ratio 16:9 mode). **D468 (merged 2026-10-01):** under ultrawide or `Video.FovScale` > 100 the AI's on-screen view is clamped to the 16:9 / game-FOV view; option `Game.AIWideView` (F10 Gameplay, default off) gives the AI the full view. **Owed:** by-eye check on an ultrawide window or at a high FOV. D466 follow-up (2026-10-01, maintainer decision): movement/locomotion LOD follows the render, only awareness uses the N64 distance. |
|
||||||
| Golden Gun plays the empty click with each shot | open | #130 | Outside report: the out-of-ammo sound plays alongside the firing sound, unlike 1964. Check by ear together with the D433 auto-fire A/B (maintainer-owned). |
|
| Sniper rifle: can't crouch while using it; zoom feels slow | verify | #136, D483, D484, D485 | **Fixed 2026-10-02 on `release/v0.4.1`.** Crouch: partly faithful (the sniper's DISABLE_CROUCH flag blocks stance changes on the N64); the port's divergence (the PC crouch key stood you up on switching to the sniper) is fixed by **D483**. Zoom level: the port's FOV floor capped full zoom at 20 degrees instead of 7, fixed by **D484**. Zoom speed: per-frame stepping made the port zoom 2x faster than the N64; now tick-scaled (**D485**, Rule-2 approved). Owed: 1964 A/B of zoom level/speed and crouch-then-switch. Keep #136 open until the maintainer's 1964 check. |
|
||||||
| A mine in Control's mainframe room didn't destroy anything | open | #126 | Outside report; the reporter is unsure. Sanity-check on 1964 first (the objects may be indestructible there). |
|
|
||||||
| Crash on save slots 1/3/4, plus a host BSOD (fltmgr.sys) | decided | #94 | Needs a repro. Two maintainer retest requests (v0.3.0, v0.4.0) got no reply. Likely D299/D301 (save-slot crash, fixed) and D344 (BSOD on exit, fixed). **Decided 2026-10-01: close as stale when the release ships**, pointing at D299/D301/D344; no GitHub activity before then. |
|
| Crash on save slots 1/3/4, plus a host BSOD (fltmgr.sys) | decided | #94 | Needs a repro. Two maintainer retest requests (v0.3.0, v0.4.0) got no reply. Likely D299/D301 (save-slot crash, fixed) and D344 (BSOD on exit, fixed). **Decided 2026-10-01: close as stale when the release ships**, pointing at D299/D301/D344; no GitHub activity before then. |
|
||||||
| SkipIntro skips the post-mission failure dossier | verify | D408, D216 | Fixed 2026-09-30: the hook is gated to first boot. **Owed:** fail or abort a mission with SkipIntro on and check the dossier shows; also listen for whether D216's audio break is gone. |
|
|
||||||
| Front-end: Rareware logo filtering, cast-roll models | partial | D75 | Cosmetic. The other front-end logos are fixed. The cast-roll character models have never been verified. |
|
|
||||||
| Authored mip chains not sampled for explicit-LOD textures | post-1.0 | D323 | Distant textures use driver mips instead of Rare's hand-authored chain (heaviest on Statue/Aztec). Parked as a fidelity enhancement. **Decided 2026-10-01:** after 1.0, together with the LOD-fraction approximation as one "real RDP LOD" project. |
|
| Authored mip chains not sampled for explicit-LOD textures | post-1.0 | D323 | Distant textures use driver mips instead of Rare's hand-authored chain (heaviest on Statue/Aztec). Parked as a fidelity enhancement. **Decided 2026-10-01:** after 1.0, together with the LOD-fraction approximation as one "real RDP LOD" project. |
|
||||||
| Approximations in the software RSP | open | `gfx_pc.cpp` `G_CCMUX_LOD_FRACTION`; `Video.WrapFix` (§F row `RC3 · D167`, D74 family) | The LOD-fraction CC input is an eyeballed approximation. The non-power-of-two wrap fix is still opt-in (default off), with unknown visible impact. Both need a comparison against 1964. **2026-09-30 triage** (`docs/dev/notes/FIDELITY-TRIAGE.md`): the LOD-fraction input is bundled with D323 as one "real RDP LOD" project (fix later, large). **WrapFix is maintainer-owned:** a 1964 Depot-ceiling screenshot vs the port with WrapFix on/off decides the default. |
|
| Approximations in the software RSP | post-1.0 | `gfx_pc.cpp` `G_CCMUX_LOD_FRACTION`; D323 | The LOD-fraction CC input is an eyeballed approximation, bundled with D323 as one "real RDP LOD" project after 1.0 (2026-09-30 triage). **WrapFix settled 2026-10-02:** the maintainer checked the Depot building ceilings against 1964 with the default `Video.WrapFix = 0` and they look right, so the default stays off (the opt-in knob remains; RC3/D167). |
|
||||||
| Latent 64-bit ABI hazards | partial | D441, #107, #108; ~396 `-Wpointer-to-int-cast` sites at the start of the sweep (185 remain in the 17 touched files; many are intentional `(s32)&ANIM_DATA_*` offsets, D34); AUDIT-M6 `struct player`/`struct hand` raw offsets (porting-notes §A1) | Part 1 landed (D441): 17 files widened to `uintptr_t` under `#ifdef PORT`, build and Bunker/Silo runs verified. **Still owed:** the full census with per-site provenance (H/R/I/?, table not yet written) plus a **store-to-u32 vs arithmetic-only** tag per site (a `uintptr_t` widening is enough only for arithmetic; sites that store back into a u32 field need `portHostToN64` under the macOS port_addr model), an env-gated high-arena test mode, and a check under an arena above 4 GB. Harmless today because the arena sits low. They would bite on macOS/ARM/ASLR. **#108 concern:** widening the `sndPlaySfx` bound disables the guard on every platform; prefer a range check. **#107 will now conflict** with D441 in `bg.c` (`bgRoomCalcBB`), so rebase or close it during the outside-PR review. A read-only provenance pass over the remaining sites is a good local-Qwen delegate task. **2026-10-01:** D457(c)(d) merged (player matrix pointers, Model/PROMOTE/vtxstore pointer width); #107's two sites fixed (D441); read-side census part 2 (403 `-Wint-conversion`/`-Wint-to-pointer-cast` warnings, triaged by file) in the local note `ABI-CENSUS-D441-PART2.md`; model.c rows need a re-run after D457(d). **Stage B plan (2026-10-01, maintainer's local design note):** (1) centralise the hard-coded arena address windows: **done 2026-10-02** (`port/include/portaddr.h`: `PORT_DRAM_V1_BASE` / `PORT_DRAM_K0_BASE` / `PORT_DRAM_SIZE`, used by `OS_K0_TO_PHYSICAL`, `dram.c`, `libultra.c` bases, `n64stubs.c`, the fast3d window checks; compile-time constants, no codegen change; menu / Bunker / Statue smoke clean). Left: the three link-time symbols in `port/src/dram_syms.s` (needs a preprocessed `.S` or `--defsym`), and a runtime-derived base for macOS; (2) widen `mema.c` (`memaspace.addr` s32 with `0xffffffff` sentinels, `memaRealloc`/`_memaFree` s32 params; the deferred D453 item); (3) build the high-arena test flavour (`GE_HIGHARENA_BASE`, tier 1 at 0x90000000, tier 2 above 4 GiB) and iterate on the first failures; (4) the heap-backed animation-table slots (`initanitable.c`); converges with #95's port_addr chokepoint. |
|
| Latent 64-bit ABI hazards | partial | D441, #107, #108; ~396 `-Wpointer-to-int-cast` sites at the start of the sweep (185 remain in the 17 touched files; many are intentional `(s32)&ANIM_DATA_*` offsets, D34); AUDIT-M6 `struct player`/`struct hand` raw offsets (porting-notes §A1) | Part 1 landed (D441): 17 files widened to `uintptr_t` under `#ifdef PORT`, build and Bunker/Silo runs verified. **Still owed:** the full census with per-site provenance (H/R/I/?, table not yet written) plus a **store-to-u32 vs arithmetic-only** tag per site (a `uintptr_t` widening is enough only for arithmetic; sites that store back into a u32 field need `portHostToN64` under the macOS port_addr model), an env-gated high-arena test mode, and a check under an arena above 4 GB. Harmless today because the arena sits low. They would bite on macOS/ARM/ASLR. **#108 concern:** widening the `sndPlaySfx` bound disables the guard on every platform; prefer a range check. **#107 will now conflict** with D441 in `bg.c` (`bgRoomCalcBB`), so rebase or close it during the outside-PR review. A read-only provenance pass over the remaining sites is a good local-Qwen delegate task. **2026-10-01:** D457(c)(d) merged (player matrix pointers, Model/PROMOTE/vtxstore pointer width); #107's two sites fixed (D441); read-side census part 2 (403 `-Wint-conversion`/`-Wint-to-pointer-cast` warnings, triaged by file) in the local note `ABI-CENSUS-D441-PART2.md`; model.c rows need a re-run after D457(d). **Stage B plan (2026-10-01, maintainer's local design note):** (1) centralise the hard-coded arena address windows: **done 2026-10-02** (`port/include/portaddr.h`: `PORT_DRAM_V1_BASE` / `PORT_DRAM_K0_BASE` / `PORT_DRAM_SIZE`, used by `OS_K0_TO_PHYSICAL`, `dram.c`, `libultra.c` bases, `n64stubs.c`, the fast3d window checks; compile-time constants, no codegen change; menu / Bunker / Statue smoke clean). Left: the three link-time symbols in `port/src/dram_syms.s` (needs a preprocessed `.S` or `--defsym`), and a runtime-derived base for macOS; (2) widen `mema.c` (`memaspace.addr` s32 with `0xffffffff` sentinels, `memaRealloc`/`_memaFree` s32 params; the deferred D453 item); (3) build the high-arena test flavour (`GE_HIGHARENA_BASE`, tier 1 at 0x90000000, tier 2 above 4 GiB) and iterate on the first failures; (4) the heap-backed animation-table slots (`initanitable.c`); converges with #95's port_addr chokepoint. **2026-10-02 (D478):** read-side census re-run (319; 129 outside `chr.c` D34) triaged site by site: nothing live on the current layout; the remaining items are arena pointers in 32-bit slots and stack buffers through `s32` (safe only while stacks are low: `MAP_32BIT`, no ASLR on Windows), which is the Stage B / macOS list in the D478 entry. |
|
||||||
|
|
||||||
## 3. Verification debt (fixed; a live check is owed)
|
## 3. Verification debt (fixed; a live check is owed)
|
||||||
|
|
||||||
@@ -168,12 +164,12 @@ A single campaign pass with these on the checklist would clear most of them.
|
|||||||
|
|
||||||
| Item | Status | Refs | Notes |
|
| Item | Status | Refs | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| macOS (Intel + Apple Silicon) / ARM64 Linux | open | PRs #88, #95; #101 | Outside drafts, parked for `main` until after 1.0. **Address model decided (2026-09-30): port_addr (#95)**, i.e. a single `portN64ToHost`/`PORT_N64PTR` chokepoint with a `PORT_ADDR_STRICT` validator, over the dram-scoped alternative. The `macos` integration branch **exists on origin** (2026-09-30). It was cut from `origin/main` `0e8c2ce2`, and it takes #88 (merged as `c4153157`, using #95's conflict-resolved copy `ae1884cc`), #132 (Linux build fix, `39d4f8d0`: #88's `romdata.c` included `<errno.h>` before `ultra64.h`, and glibc's `errno` macro broke the `errno` fields in `PR/os.h`; #95 needs it on rebase) and #95 (retargeted to it, still open). italoarruda's work also goes there. Nothing merges from it to `main` before the post-1.0 pass. **Open against `macos`:** #131, the fork-friendly self-hosted sweep. It adds `platform` and `runner_labels` inputs, a macOS job running `level_sweep_mac.sh` with a JSON verdict, seed-dir-only ROM staging, and `docs/selfhosted-sweep.md`. It is a scaffold that has not been run on a Mac, and it needs #95 in `macos` first. **When `macos` is next rebased onto `main`** (after the v0.4.1 push), `selfhosted.yml` will conflict with the v0.4.1 ROM-cache fix. Take #131's version, which already includes that fix. The crouched-rifle table fix (`chr.c`), found three times (#95, italoarruda, #120), landed on `release/v0.4.1` via #120 (D445) on 2026-09-30. #120's shape is the correct one (it restores the N64 table length of 2; #95's terminator-only version leaves length 1), so **drop #95's `crouched_rifle` hunk** when `macos` is rebased. #95's finding labels (D29x, then D405–D421) clash with ours, so relabel on merge. The maintainer tests firsthand before merging. Needs the ABI-hazard sweep in §2 (store-to-u32 tagging). |
|
| macOS (Intel + Apple Silicon) / ARM64 Linux | open (target 0.9.0/1.0, maintainer 2026-10-02) | PRs #88, #95; #101 | Outside drafts, parked for `main` until after 1.0. **Address model decided (2026-09-30): port_addr (#95)**, i.e. a single `portN64ToHost`/`PORT_N64PTR` chokepoint with a `PORT_ADDR_STRICT` validator, over the dram-scoped alternative. The `macos` integration branch **exists on origin** (2026-09-30). It was cut from `origin/main` `0e8c2ce2`, and it takes #88 (merged as `c4153157`, using #95's conflict-resolved copy `ae1884cc`), #132 (Linux build fix, `39d4f8d0`: #88's `romdata.c` included `<errno.h>` before `ultra64.h`, and glibc's `errno` macro broke the `errno` fields in `PR/os.h`; #95 needs it on rebase) and #95 (retargeted to it, still open). italoarruda's work also goes there. Nothing merges from it to `main` before the post-1.0 pass. **Open against `macos`:** #131, the fork-friendly self-hosted sweep. It adds `platform` and `runner_labels` inputs, a macOS job running `level_sweep_mac.sh` with a JSON verdict, seed-dir-only ROM staging, and `docs/selfhosted-sweep.md`. It is a scaffold that has not been run on a Mac, and it needs #95 in `macos` first. **When `macos` is next rebased onto `main`** (after the v0.4.1 push), `selfhosted.yml` will conflict with the v0.4.1 ROM-cache fix. Take #131's version, which already includes that fix. The crouched-rifle table fix (`chr.c`), found three times (#95, italoarruda, #120), landed on `release/v0.4.1` via #120 (D445) on 2026-09-30. #120's shape is the correct one (it restores the N64 table length of 2; #95's terminator-only version leaves length 1), so **drop #95's `crouched_rifle` hunk** when `macos` is rebased. #95's finding labels (D29x, then D405–D421) clash with ours, so relabel on merge. The maintainer tests firsthand before merging. Needs the ABI-hazard sweep in §2 (store-to-u32 tagging). |
|
||||||
| Platform parity gate (Linux co-equal with Windows) | open | — | **Decided 2026-09-30:** yes. Every released platform carries the same gate: a 21-level sweep plus a campaign pass. Needs the golden baselines on both platforms first (§7). The last known Linux crash (D190) was closed as not-a-bug. |
|
| Platform parity gate (Linux co-equal with Windows) | open | — | **Decided 2026-09-30:** yes. Every released platform carries the same gate: a 21-level sweep plus a campaign pass. Needs the golden baselines on both platforms first (§7). The last known Linux crash (D190) was closed as not-a-bug. |
|
||||||
| Low-end GPUs (~30 fps on Celeron/HD 400) | open | D339, #92 | **Direction (maintainer, 2026-10-01): real optimizations, not a preset.** **2026-10-01 profile** (all-thread sampler, local note `PERF-HOTSPOTS.md`, probes on branch `perf/profile-hotspots`): the top render-thread hotspot was an uncached getenv per vertex batch, fixed as **D473** (Statue display-list CPU about -55%). After it, whole-process CPU is ~1.5-2 ms/frame on a fast machine (~8-10 ms scaled 5x), so the N3060 / HD 400 is probably GPU- or driver-bound (per-draw cost, MSAA, fill). **Next:** one `GE_PERFSTAT=1` run on that class of hardware (compare `gpu=` vs `run=` vs `interval=`), then target what dominates: draw-call/state-change reduction (needs a flush-reason histogram on the target), texture-bind lookups, per-triangle debug hooks, combiner lookup (ranked in the note). The D372 preset gets no further effort. |
|
| Low-end GPUs (~30 fps on Celeron/HD 400) | fixed, verify on N3060 class | D339, D481, D482, #92 | **D482 (2026-10-02):** measured against the PD port on the X220, fast3d is as cheap per batch as PD's; the remaining gap is our heavier defaults. Atom/Celeron-class GPUs (HD 400-605, UHD 600/605, Bay Trail class, software renderers) now get DrawDistance/LodDistance 100 and MSAA 1 once, where still at the defaults (Cradle: 482 -> 146 batches, render CPU 8.2 -> 5.2 ms). **D481 (2026-10-02):** the root cause was architectural, not render cost: fast3d ran inline on the scheduler thread, delaying retrace delivery, so the game's tick gate skipped frames. With the render worker a Lenovo X220 (HD 3000) went from 44-46 to 60.0 fps at every setting; the PD port measured 60 on the same box. **Next only if N3060-class hardware still drops:** decide CPU vs GPU from `GE_PERFSTAT` there (`gpu=` vs `run=`), then either a renderer-keyed lighter default (GPU) or fast3d per-batch cuts diffed against PD's hot loop (CPU). **Earlier direction (maintainer, 2026-10-01): real optimizations, not a preset.** **2026-10-01 profile** (all-thread sampler, local note `PERF-HOTSPOTS.md`, probes on branch `perf/profile-hotspots`): the top render-thread hotspot was an uncached getenv per vertex batch, fixed as **D473** (Statue display-list CPU about -55%). After it, whole-process CPU is ~1.5-2 ms/frame on a fast machine (~8-10 ms scaled 5x), so the N3060 / HD 400 is probably GPU- or driver-bound (per-draw cost, MSAA, fill). **Next:** one `GE_PERFSTAT=1` run on that class of hardware (compare `gpu=` vs `run=` vs `interval=`), then target what dominates: draw-call/state-change reduction (needs a flush-reason histogram on the target), texture-bind lookups, per-triangle debug hooks, combiner lookup (ranked in the note). The D372 preset gets no further effort. |
|
||||||
| Statue/Cradle hitching (maintainer, 2026-09-30) | open | `GE_PERFSTAT` spike counters (2026-10-01) | **Diagnosed 2026-10-01 (D475):** no sustained load. The low-CPU ~33 ms frames are the game's own frame limiter skipping a retrace after a slightly late gfx task (`boss.c` tick-interval check; a D13-class timing interplay, needs a design look and possibly Rule-2). The CPU-side bursts are CI-palette re-imports (one texture re-imported with 51 palettes in ~8 frames), not first-sight loads; room-texture pre-warm was built and measured, with ~no gain (unmerged branch `perf/prewarm`). **CI8 palette re-imports ruled out as a CPU cost (D477, 2026-10-02):** decode+upload ~0.008 ms/frame, worst frame 0.44 ms; the cache already holds the animated palettes (10 re-misses in 1314); the same texture is drawn with several palettes per frame, so an in-place update path would thrash. GPU palette lookup is not justified by this data. CI4 part done (D476). Frame-limiter design look done 2026-10-02: no independent pacing bug; the skips follow long gfx tasks. **Next (if the hitches still matter):** a per-frame timeline probe (game-thread build + limiter decision, render-thread misses/new textures/shader compiles, per-frame GPU timestamps, swap time) over >=5 runs, PresentMon during real play at a spot that hitches, then a disable-falsifier per suspect (driver-deferred texture allocation, `glGenerateMipmap` under three-point filtering, shader compiles). Needs the maintainer's description of where the hitch shows. |
|
| Statue/Cradle hitching (maintainer, 2026-09-30) | parked (not player-visible, 2026-10-02) | `GE_PERFSTAT` spike counters (2026-10-01) | **Diagnosed 2026-10-01 (D475):** no sustained load. The low-CPU ~33 ms frames are the game's own frame limiter skipping a retrace after a slightly late gfx task (`boss.c` tick-interval check; a D13-class timing interplay, needs a design look and possibly Rule-2). The CPU-side bursts are CI-palette re-imports (one texture re-imported with 51 palettes in ~8 frames), not first-sight loads; room-texture pre-warm was built and measured, with ~no gain (unmerged branch `perf/prewarm`). **CI8 palette re-imports ruled out as a CPU cost (D477, 2026-10-02):** decode+upload ~0.008 ms/frame, worst frame 0.44 ms; the cache already holds the animated palettes (10 re-misses in 1314); the same texture is drawn with several palettes per frame, so an in-place update path would thrash. GPU palette lookup is not justified by this data. CI4 part done (D476). Frame-limiter design look done 2026-10-02: no independent pacing bug; the skips follow long gfx tasks. **Next (if the hitches still matter):** a per-frame timeline probe (game-thread build + limiter decision, render-thread misses/new textures/shader compiles, per-frame GPU timestamps, swap time) over >=5 runs, PresentMon during real play at a spot that hitches, then a disable-falsifier per suspect (driver-deferred texture allocation, `glGenerateMipmap` under three-point filtering, shader compiles). Needs the maintainer's description of where the hitch shows. **Parked 2026-10-02:** the timeline probe was built (`probe/hitch-timeline`, `GE_HITCHTL`, never merge) and run during a ~2 min maintainer play session. The maintainer felt no hitching at their settings, only the FPS counter dipping to 58/59 as a level starts. The capture agrees: the long frames sit at level load and transitions (one 59 ms frame with 316 new textures + 4 shader compiles) plus one ~20 ms in-play cluster with 3-7 shader compiles and new GL textures (first-draw cost). Reopen only if a visible hitch is reported (e.g. on low-end hardware); the cheapest lever then is shader/texture pre-warm at level load. **D480 (2026-10-02, `perf/shader-prewarm`):** the in-play cluster was shader compiles; created shaders are now remembered in `ge007.shaders` and compiled at the first frame of the next session (0 in-play compiles on a warm run, output-identical). The level-start texture frame is left as is (see D480). |
|
||||||
| Steam Deck preset skipped when first launched from Desktop Mode | verify | D283 | Fixed on `release/v0.4.1`: the Deck is detected by DMI hardware id (Valve Jupiter/Galileo; `STEAMOS` kept as a second signal) and the preset applies once per ini, only while Fullscreen/Maximized are untouched. Only the Windows build and a Linux `-fsyntax-only` check ran. **Owed: one run on a real Deck** (Desktop-Mode-created ini, then Game Mode; expect fullscreen 1280x800 and the log line `video: Steam Deck (DMI Valve ...)`). The Known-issues row above goes when this ships. |
|
| Steam Deck preset skipped when first launched from Desktop Mode | verify | D283 | Fixed on `release/v0.4.1`: the Deck is detected by DMI hardware id (Valve Jupiter/Galileo; `STEAMOS` kept as a second signal) and the preset applies once per ini, only while Fullscreen/Maximized are untouched. Only the Windows build and a Linux `-fsyntax-only` check ran. **Owed: one run on a real Deck** (Desktop-Mode-created ini, then Game Mode; expect fullscreen 1280x800 and the log line `video: Steam Deck (DMI Valve ...)`). The Known-issues row above goes when this ships. |
|
||||||
| Windows 7 | decision | #98 | Recommend won't-fix (toolchain). |
|
| Windows 7 | open | #98 | **Re-checked 2026-10-02: likely feasible around 1.0.** The exe imports no Windows 8+ functions; the bundled SDL2 2.32.10 (still supports Windows 7) and winpthreads only load newer APIs dynamically with fallbacks; msvcrt runtime; OpenGL 3.0 drivers exist on Windows 7. The real blocker is the first-run asset converter `ge007-convert.exe` (PyInstaller on Python 3.9+, which does not run on Windows 7): the native C converter (#6 Part B) removes it, or a separate Python 3.8 build. Work: one Windows 7 VM test, then either converter route; CI cannot test Windows 7, so it is a manual per-release check. Supersedes the earlier 'won't-fix (toolchain)' recommendation. |
|
||||||
|
|
||||||
## 5. Modern-remaster layer (port features beyond the N64)
|
## 5. Modern-remaster layer (port features beyond the N64)
|
||||||
|
|
||||||
@@ -203,7 +199,7 @@ Background research lives in the maintainer's local notes (gitignored):
|
|||||||
| Settings units and values cleanup | verify | D357, D443 | **Landed 2026-09-30 (D443):** mouse ×, whole-% deadzone and volume, FOV in horizontal degrees, MSAA 16×. FPS presets above 60 are deferred to the frame-rate-above-60 work (the sim ticks at 60 Hz). **Owed:** the new values in both settings UIs. |
|
| Settings units and values cleanup | verify | D357, D443 | **Landed 2026-09-30 (D443):** mouse ×, whole-% deadzone and volume, FOV in horizontal degrees, MSAA 16×. FPS presets above 60 are deferred to the frame-rate-above-60 work (the sim ticks at 60 Hz). **Owed:** the new values in both settings UIs. |
|
||||||
| Restart button in the settings menus | done | D443 | **Landed 2026-09-30 (D443):** a "Restart game" row (orderly D344 quit, then relaunch). **Owed:** click it once on Windows (no BSOD); the Linux relaunch path has never been compiled. |
|
| Restart button in the settings menus | done | D443 | **Landed 2026-09-30 (D443):** a "Restart game" row (orderly D344 quit, then relaunch). **Owed:** click it once on Windows (no BSOD); the Linux relaunch path has never been compiled. |
|
||||||
| Front-end PC Options: Previous/Next paging for mouse users | verify | D444 | **Landed 2026-09-30 (D444):** Previous/Next page buttons; hover no longer snaps the wheel selection. **Owed:** a mouse pass through Input and Graphics. |
|
| Front-end PC Options: Previous/Next paging for mouse users | verify | D444 | **Landed 2026-09-30 (D444):** Previous/Next page buttons; hover no longer snaps the wheel selection. **Owed:** a mouse pass through Input and Graphics. |
|
||||||
| Outside PRs #107–#109, #122–#124 | decided | #122 hot-plugged pads never reopen, #123 persistent crosshair (`gunfire.c`), #124 compact health/armour bars (`bondview2.c`); #107/#108/#109 (italoarruda) | **Decided 2026-10-01 after design reviews (notes: `docs/dev/notes/PR-REVIEW-109.md`, `PR-REVIEW-123-124.md`, local):** **#120/#121/#122** merged locally (#122 = D450, with split-screen); close all three when the branch is pushed. **#123** (persistent crosshair): accept-with-changes, **deferred**; later, either amend their PR or do the work ourselves (add to the Original N64 preset, relabel D436, our Rule-2 line, 2P + sniper-zoom check). **#124** (compact health/armour bars): **deferred** (pre- or post-1.0); too many issues to merge now: ignores HudScale, overlaps the bottom-left status text, 4:3-edge anchoring on ultrawide, draws extra commands when off, mixed apparent/real values, a test knob in shipping code, not in the Original N64 preset, split-screen untested. Either we take it on or ask the author to address the list when GitHub activity resumes. **#109** (pad rebinding): **decline as-is**. It conflicts in all 4 files, would revert the D394 Jinx defaults, uses global (not per-seat) bindings, and its cancel is keyboard-only. Supersede with our own pad presets + rebinding (§5a row below), crediting the author's two-names format, menu lockout guard and release-before-capture arming (Co-authored-by); close with thanks and the change list when GitHub activity resumes. **#107**: folded into D441 on 2026-10-01 (both sites fixed with the author's `uintptr_t` arithmetic, Co-authored-by); close it on push. **#108**: belongs with `macos`; ask for a range check instead of widening the bound. Relabel any outside D-labels on merge. |
|
| Outside PRs #107–#109, #122–#124 | decided | #122 hot-plugged pads never reopen, #123 persistent crosshair (`gunfire.c`), #124 compact health/armour bars (`bondview2.c`); #107/#108/#109 (italoarruda) | **Decided 2026-10-01 after design reviews (notes: `docs/dev/notes/PR-REVIEW-109.md`, `PR-REVIEW-123-124.md`, local):** **#120/#121/#122** merged locally (#122 = D450, with split-screen); close all three when the branch is pushed. **#123** (persistent crosshair): accept-with-changes, **deferred**; later, either amend their PR or do the work ourselves (add to the Original N64 preset, relabel D436, our Rule-2 line, 2P + sniper-zoom check). **#124** (compact health/armour bars): **deferred** (pre- or post-1.0); too many issues to merge now: ignores HudScale, overlaps the bottom-left status text, 4:3-edge anchoring on ultrawide, draws extra commands when off, mixed apparent/real values, a test knob in shipping code, not in the Original N64 preset, split-screen untested. Either we take it on or ask the author to address the list when GitHub activity resumes. **#133** (F10 ultrawide aspect, JosephAHK, 2026-10-01): duplicates D472 (overlay card pillarboxed + mouse mapping, already on `release/v0.4.1`); close as superseded with thanks when the branch is pushed. **#109** (pad rebinding): **decline as-is**. It conflicts in all 4 files, would revert the D394 Jinx defaults, uses global (not per-seat) bindings, and its cancel is keyboard-only. Supersede with our own pad presets + rebinding (§5a row below), crediting the author's two-names format, menu lockout guard and release-before-capture arming (Co-authored-by); close with thanks and the change list when GitHub activity resumes. **#107**: folded into D441 on 2026-10-01 (both sites fixed with the author's `uintptr_t` arithmetic, Co-authored-by); close it on push. **#108**: belongs with `macos`; ask for a range check instead of widening the bound. Relabel any outside D-labels on merge. |
|
||||||
| Gamepad rebinding + modern controller presets | verify | D469 (supersedes PR #109), D394 | **Landed 2026-10-01 (D469):** data-driven pad action table; `Input.PadPreset` (0 = Jinx 1.1, default, proven identical to the old mapping over 20,971,520 synthetic cases; 3 = Custom); per-seat `Input.Pad[N].<Action>` bindings; F10 / front-end "Controller..." page with pad capture (B/Back cancel, hold Back / Y clear); menu lockout guard; credited to italoarruda (#109). **Maintainer live test 2026-10-01:** bind, cancel, clear, 2P per-seat tables, hot-plug and relaunch persistence pass; the Restart row (D443) works. **Still owed:** more play with the default preset ("plays as before") and longer controller-only use. **Not done:** Jinx 1.2/1.3 tables (D394 has no reference tables; presets 1/2 hidden), a per-seat preset, a warning when two actions share a button. |
|
| Gamepad rebinding + modern controller presets | verify | D469 (supersedes PR #109), D394 | **Landed 2026-10-01 (D469):** data-driven pad action table; `Input.PadPreset` (0 = Jinx 1.1, default, proven identical to the old mapping over 20,971,520 synthetic cases; 3 = Custom); per-seat `Input.Pad[N].<Action>` bindings; F10 / front-end "Controller..." page with pad capture (B/Back cancel, hold Back / Y clear); menu lockout guard; credited to italoarruda (#109). **Maintainer live test 2026-10-01:** bind, cancel, clear, 2P per-seat tables, hot-plug and relaunch persistence pass; the Restart row (D443) works. **Still owed:** more play with the default preset ("plays as before") and longer controller-only use. **Not done:** Jinx 1.2/1.3 tables (D394 has no reference tables; presets 1/2 hidden), a per-seat preset, a warning when two actions share a button. |
|
||||||
| Per-pad tuning (device index, hot-plug seats) | verify | D448, D450 (#122) | Pad seats keyed by SDL instance id, reserved in a match; hot-plug events reach the input layer. Merged with split-screen 2026-10-01. **Owed:** unplug and replug a pad mid-match. |
|
| Per-pad tuning (device index, hot-plug seats) | verify | D448, D450 (#122) | Pad seats keyed by SDL instance id, reserved in a match; hot-plug events reach the input layer. Merged with split-screen 2026-10-01. **Owed:** unplug and replug a pad mid-match. |
|
||||||
| Full-gamepad menu navigation audit, Xbox/PS button glyphs | verify | D471; local note `PAD-MENU-AUDIT.md` | **Audit done 2026-10-01:** a controller-only player is never trapped in the PC menus. **Glyphs: not wanted** (maintainer); instead D471 shows family-specific button **names** (PlayStation/Nintendo) in the help lines and binding column, Xbox text unchanged, plus player-index LEDs (PD-port parity). **Audit G1-G3 fixed by D472** (owner-pad input). **Owed:** a real PS/Nintendo pad check. |
|
| Full-gamepad menu navigation audit, Xbox/PS button glyphs | verify | D471; local note `PAD-MENU-AUDIT.md` | **Audit done 2026-10-01:** a controller-only player is never trapped in the PC menus. **Glyphs: not wanted** (maintainer); instead D471 shows family-specific button **names** (PlayStation/Nintendo) in the help lines and binding column, Xbox text unchanged, plus player-index LEDs (PD-port parity). **Audit G1-G3 fixed by D472** (owner-pad input). **Owed:** a real PS/Nintendo pad check. |
|
||||||
@@ -262,9 +258,9 @@ and a stage loader.
|
|||||||
| Security review follow-ups | open | maintainer's local security review | Three Low hardening items remain (local-input only; details are kept out of this public file). Done on `release/v0.4.1`: the Medium item (self-hosted workflow input handling), actions pinned by SHA, `pyinstaller` pinned to 6.22.3, and the ROM no longer stored in `actions/cache` by `selfhosted.yml` (fork PR workflows can restore base-branch caches; no cache entry ever existed, checked 2026-09-30; it is now staged from the runner's `_rom-seed` dir only). The Dependabot action bumps (#1/#2) would now need to land as SHA pins, not tag bumps. |
|
| Security review follow-ups | open | maintainer's local security review | Three Low hardening items remain (local-input only; details are kept out of this public file). Done on `release/v0.4.1`: the Medium item (self-hosted workflow input handling), actions pinned by SHA, `pyinstaller` pinned to 6.22.3, and the ROM no longer stored in `actions/cache` by `selfhosted.yml` (fork PR workflows can restore base-branch caches; no cache entry ever existed, checked 2026-09-30; it is now staged from the runner's `_rom-seed` dir only). The Dependabot action bumps (#1/#2) would now need to land as SHA pins, not tag bumps. |
|
||||||
| Deterministic 1964-vs-port comparison harness | open | — | Would replace by-eye/by-ear fidelity checks. Highest-leverage tooling item. |
|
| Deterministic 1964-vs-port comparison harness | open | — | Would replace by-eye/by-ear fidelity checks. Highest-leverage tooling item. |
|
||||||
| Scripted-playthrough harness | open | `GE_INPUTSCRIPT` | Input script + `GE_PCDUMP` + crash-log check, for unattended regression runs. The tool exists; the harness doesn't. |
|
| Scripted-playthrough harness | open | `GE_INPUTSCRIPT` | Input script + `GE_PCDUMP` + crash-log check, for unattended regression runs. The tool exists; the harness doesn't. |
|
||||||
| Golden baselines | open | `tools_pc/verify.sh`, D117 | bunker1 has been noisy since M-84, so the per-patch gate is unreliable. Re-base it, extend to all 21 levels × both platforms, and finish `GE_DETERM`. |
|
| Golden baselines | partial | `tools_pc/verify.sh`, `tools_pc/golden/README.md`, D117 | **Windows done 2026-10-02:** all 21 levels re-based (`tools_pc/golden/<level>/win/`) with a near-deterministic recipe (pinned seed, START skips the intro flyby, frames 900-1500 of settled gameplay; Cuba captures its ending); two-tier gate (structural + per-pixel with per-level limits) catches a global texture-filter change (22% vs <= 2.3% noise). verify.sh now quits via `GE_QUITFRAME` and never kills other game instances. **Left:** Linux baselines (needs a real-GPU Linux box: `GE_PCDUMP` reads black on llvmpipe/WSLg), then the parity gate per platform. `GE_DETERM` stays experimental (not needed for this recipe). |
|
||||||
| Self-hosted CI runner link failure | open | D244 | Needs a `pacman -Syu` on the runner (maintainer-only access). Release tag builds use GitHub runners, so it doesn't block a release. |
|
| Self-hosted CI runner link failure | open | D244 | Needs a `pacman -Syu` on the runner (maintainer-only access). Release tag builds use GitHub runners, so it doesn't block a release. |
|
||||||
| Remove temporary scaffolding before a release | partial | [`dev/GE-ENV-PROBES.md`](dev/GE-ENV-PROBES.md), D302 | The 2026-09-30 pass removed D207S, D157, D252POOL, D245V, D288, D309, the D318B suite, the D320 repro harness and `scan_op12/13.py`, demoted the D318 watchdog to detect-and-log, and cached the per-frame `getenv` sites. **Left:** dead registry rows (D116, D51, D56, D60-D63, D69*, D71LOG, D85DUMP, D86-D88, D90, D96, D104, D154, D178); closed-finding TEMPs (`GE_D235_*` once D235 closes; **removed 2026-10-01:** `GE_D306C`, `GE_D318T` (batch 2); the whole D243 diagnostic set -- `GE_D243X2`, `GE_D243X4`, `GE_D243M` (+ its `d243mProbeActive`/frame-counter plumbing and all `D243M:` log lines in `bondview2.c`/`chr.c`/`chraction.c`/`chrai.c`/`model.c`/`objecthandler.c`), `GE_D243CAM`, `GE_D243SWIRL`, `GE_D243` (front.c menu trace) (batch 3, `chore/strip-d243-probes`; the shipped fix and the M-183/M-185 clamps are kept); `GE_DYNTEXHASH_OFF`, `GE_FORCEALARM`, `GE_D204_OLD`, `GE_D252`, `GE_RSEED_LV` were already gone); the now-inert sky tile-capture/shift machinery in `sky.c` (`skyPortCaptureTile`, `skyPortPickShift`, `GE_D245_FIXEDSHIFT`); and the M-183/M-185 clamps, which need a full cutscene re-check first. |
|
| Remove temporary scaffolding before a release | partial | [`dev/GE-ENV-PROBES.md`](dev/GE-ENV-PROBES.md), D302 | The 2026-09-30 pass removed D207S, D157, D252POOL, D245V, D288, D309, the D318B suite, the D320 repro harness and `scan_op12/13.py`, demoted the D318 watchdog to detect-and-log, and cached the per-frame `getenv` sites. **Left:** dead registry rows (D116, D51, D56, D60-D63, D69*, D71LOG, D85DUMP, D86-D88, D90, D96, D104, D154, D178); closed-finding TEMPs (**removed 2026-10-02 (batch 4, `chore/strip-batch4`):** `GE_D235_OFF`/`_RELOAD`/`_NEXT` (D235 closed; the fix and the `GE_D235` log stay), `GE_D245V`, `GE_D245_FIXEDSHIFT` and the inert sky tile-capture/shift code in `sky.c`; registry drift fixed and `gen_env_probes.py --check` passing again; **removed 2026-10-01:** `GE_D306C`, `GE_D318T` (batch 2); the whole D243 diagnostic set -- `GE_D243X2`, `GE_D243X4`, `GE_D243M` (+ its `d243mProbeActive`/frame-counter plumbing and all `D243M:` log lines in `bondview2.c`/`chr.c`/`chraction.c`/`chrai.c`/`model.c`/`objecthandler.c`), `GE_D243CAM`, `GE_D243SWIRL`, `GE_D243` (front.c menu trace) (batch 3, `chore/strip-d243-probes`; the shipped fix and the M-183/M-185 clamps are kept); `GE_DYNTEXHASH_OFF`, `GE_FORCEALARM`, `GE_D204_OLD`, `GE_D252`, `GE_RSEED_LV` were already gone); and the M-183/M-185 clamps, which need a full cutscene re-check first. |
|
||||||
| Findings log and doc size | partial | `tools_pc/docs_budget.py` | The budget checker exists (per-file and tier-1 token budgets; `--toc FILE`), and everything is within budget. `findings.md` is still about 2 MB (~500k tokens), so agents must keep using the index. Remaining: nothing blocks; trim only if the budget check starts failing. |
|
| Findings log and doc size | partial | `tools_pc/docs_budget.py` | The budget checker exists (per-file and tier-1 token budgets; `--toc FILE`), and everything is within budget. `findings.md` is still about 2 MB (~500k tokens), so agents must keep using the index. Remaining: nothing blocks; trim only if the budget check starts failing. |
|
||||||
| Branch and PR housekeeping | open | — | 30 landed local branches pruned 2026-09-30 (tips kept in the notes archive, `prune-branches-2026-09-30.txt`). `docs/m138-security-disclosure` landed on `release/v0.4.1` with a stale-fact pass (SECURITY.md, the security-and-fidelity page, README roadmap bullet). `feat/unlocked-fps-phase1-audit`'s commit (Phase 0 resolved by D248, Phase 1 audit) landed as `docs/unlocked-fps-audit`. **On hold by the maintainer (2026-09-30, do not prune yet):** probe-only leftovers `d243-verify-local`, `investigate/d243-cutscene-race`, `park/d236-probes-m139`. Local `main` and `release/v0.4.1` are deliberately not pushed. **Pushed on 2026-09-30:** the `macos` integration branch; `ci/selfhosted-fork-runners` (PR #131 into `macos`, open); `fix/macos-errno-include` (PR #132, merged into `macos`). Pruned after merging, 2026-09-30: `fix/selfhosted-rom-no-cache`, `docs/macos-ci-tracking` and `fix/macos-errno-include` (local and remote), plus the `review/pr88` and `review/pr95` read-only refs. PR #62 (mouse dt-decouple) stays held until mouse feel is fundamentally better. |
|
| Branch and PR housekeeping | open | — | 30 landed local branches pruned 2026-09-30 (tips kept in the notes archive, `prune-branches-2026-09-30.txt`). `docs/m138-security-disclosure` landed on `release/v0.4.1` with a stale-fact pass (SECURITY.md, the security-and-fidelity page, README roadmap bullet). `feat/unlocked-fps-phase1-audit`'s commit (Phase 0 resolved by D248, Phase 1 audit) landed as `docs/unlocked-fps-audit`. **On hold by the maintainer (2026-09-30, do not prune yet):** probe-only leftovers `d243-verify-local`, `investigate/d243-cutscene-race`, `park/d236-probes-m139`. Local `main` and `release/v0.4.1` are deliberately not pushed. **Pushed on 2026-09-30:** the `macos` integration branch; `ci/selfhosted-fork-runners` (PR #131 into `macos`, open); `fix/macos-errno-include` (PR #132, merged into `macos`). Pruned after merging, 2026-09-30: `fix/selfhosted-rom-no-cache`, `docs/macos-ci-tracking` and `fix/macos-errno-include` (local and remote), plus the `review/pr88` and `review/pr95` read-only refs. PR #62 (mouse dt-decouple) stays held until mouse feel is fundamentally better. |
|
||||||
| Docs site: Web 1.0 phase 2 | decision | — | Phase 1 shipped. Phase 2 (centred serif page) is plan-only. |
|
| Docs site: Web 1.0 phase 2 | decision | — | Phase 1 shipped. Phase 2 (centred serif page) is plan-only. |
|
||||||
@@ -294,4 +290,4 @@ cross-play · Steamworks, achievements, cloud saves (no storefront) · HDR,
|
|||||||
frame generation, stereo 3D (ray tracing only as the opt-in post-1.0
|
frame generation, stereo 3D (ray tracing only as the opt-in post-1.0
|
||||||
experiment in §5c) · remake-scope assets (new
|
experiment in §5c) · remake-scope assets (new
|
||||||
models, music or voice) · new movement mechanics (jump, roll, melee) · the
|
models, music or voice) · new movement mechanics (jump, roll, melee) · the
|
||||||
007 Plus ROM patch as such (#110) · Windows 7 (#98, pending decision).
|
007 Plus ROM patch as such (#110).
|
||||||
@@ -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_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_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_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_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). | **inert** (D245 fixed) — since the s16 sky path was removed 2026-09-30 nothing consumes the forced shift; strip candidate |
|
| `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_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_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 |
|
| `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_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_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` | `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_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`. | **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`. | **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`). | **TEMP** |
|
| `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_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_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_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_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) |
|
| `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_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_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_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_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_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) |
|
| `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
|
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
|
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
|
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)."
|
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)
|
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)
|
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):
|
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
|
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
|
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…"
|
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…"
|
||||||
|
@@ -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 |
|
| 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 |
|
| 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 |
|
| 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. |
|
| 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`) |
|
| 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`) |
|
| 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`).… |
|
| 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. |
|
| 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 |
|
| 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). |
|
| 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 —… |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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). |
|
| 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)". |
|
| 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. |
|
| 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.
|
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).
|
## 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).
|
**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).
|
**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`…
|
## 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.
|
**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.
|
**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).
|
## 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).
|
**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.
|
**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
|
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"`).
|
**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.
|
**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.
|
- **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.
|
**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)
|
## 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?
|
**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.
|
**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)
|
## 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)
|
## 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`).
|
**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.
|
**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.
|
**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).
|
**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
|
## D469
|
||||||
|
|
||||||
**Status: DONE (port-only; live pad test owed).** Branch `feat/pad-presets-rebind` (cut from `release/v0.4.1`). No `src/game` edits.
|
**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).
|
- 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.
|
**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).
|
**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.
|
||||||
@@ -1266,6 +1266,14 @@ non-flag ones (data loads, one-time validation, subsystem init) explicitly.
|
|||||||
Grepping the skipped `init_*`/`update_*` functions for calls with no
|
Grepping the skipped `init_*`/`update_*` functions for calls with no
|
||||||
matching state flag is the concrete check.
|
matching state flag is the concrete check.
|
||||||
|
|
||||||
|
Second instance (D479): the `GE_STARTMP` split-screen harness skipped the
|
||||||
|
file-select screen, whose init sets `selected_folder_num = -1` for an MP
|
||||||
|
launch. The harness left the boot default `FOLDER1`, so every stage load ran
|
||||||
|
`fileLoadSettingsForFolder(0)` against an unvalidated (all-zero) `saves[]`
|
||||||
|
record: options 0, i.e. ammo counter, sight and auto-aim off in harness
|
||||||
|
matches only. A real menu launch never applies a Bond file in MP. Symptom
|
||||||
|
looked like a HUD bug; the tell was that it never reproduced from the menu.
|
||||||
|
|
||||||
## D11. A chr's own per-tick animation-position logic can silently overwrite an AI script's scripted position in the same frame (D243)
|
## D11. A chr's own per-tick animation-position logic can silently overwrite an AI script's scripted position in the same frame (D243)
|
||||||
|
|
||||||
`chrTick` (`src/game/chr.c:2442`) always runs one of three `chr->actiontype`-
|
`chrTick` (`src/game/chr.c:2442`) always runs one of three `chr->actiontype`-
|
||||||
@@ -1400,6 +1408,21 @@ A port stub that forces a value to `-1` (D154 `texnum`, D135) can hide latent la
|
|||||||
## D19. A "click after the shot" report needs the trigger-hold duration, not just the state machine (D433/D467)
|
## D19. A "click after the shot" report needs the trigger-hold duration, not just the state machine (D433/D467)
|
||||||
State-machine audits of a gun's fire/dry-fire path (GE: `gunTickHandState`, CLICKY weapons) are tick-driven and match the console at any tick/frame ratio; an extra click/sound that appears "with the shot" is usually the trigger still being down when the recoil state ends (about 14 ticks for the Golden Gun). Measure with `GE_STARTWEAPON` + `GE_INPUTSCRIPT` holds of 4/9/16 frames and a `state/mag/88C/890/clk` log at the sound-play sites before suspecting the audio layer; and remember `GE_QUITFRAME` counts frames from boot, the level's `g_GlobalTimer` starts ~270 later.
|
State-machine audits of a gun's fire/dry-fire path (GE: `gunTickHandState`, CLICKY weapons) are tick-driven and match the console at any tick/frame ratio; an extra click/sound that appears "with the shot" is usually the trigger still being down when the recoil state ends (about 14 ticks for the Golden Gun). Measure with `GE_STARTWEAPON` + `GE_INPUTSCRIPT` holds of 4/9/16 frames and a `state/mag/88C/890/clk` log at the sound-play sites before suspecting the audio layer; and remember `GE_QUITFRAME` counts frames from boot, the level's `g_GlobalTimer` starts ~270 later.
|
||||||
|
|
||||||
|
## D20. Running an RCP task inline on the scheduler thread delays retrace delivery (D481)
|
||||||
|
The N64 scheduler (`src/sched.c` `__scMain`) both starts RSP/RDP tasks and forwards each VI retrace to the game. On hardware the task runs on separate processors, so the scheduler is never busy. If the port executes the task synchronously inside `osSpTaskStartGo`, any retrace that arrives during it reaches the game late, and `boss.c`'s half-frame tick gate then skips the following retrace (33 ms frames). Tell: frame rate stuck below 60 at every graphics setting, moderate render and GPU times, and the lost frames failing the tick check (`skip_tick`), not the pending-gfx check. It only shows when the task takes a large fraction of a frame (slow hardware), so a fast dev box never sees it. Run hardware-side work off the scheduler thread and signal completion with the same events the hardware would.
|
||||||
|
|
||||||
|
## D21. `gDPLoadBlock` with dxt = 0 depends on the RDP's odd-row TMEM read swap; static assets need it emulated, the tex.c pipeline must not get it (D75)
|
||||||
|
|
||||||
|
The RDP swaps the two 32-bit halves of every 8-byte group on odd TMEM rows when *reading*, and compensates on *load* only when LoadBlock's dxt counter advances. A `gsDPLoadBlock(..., dxt = 0)` of a multi-row image therefore samples as "memory with odd rows pair-swapped". Rare's compiled-in assets that load this way (the Rareware logo's RAREWARE text, `DL_RAREWARETEXT`) are pre-swapped in the data and look scrambled ("interleaved/combed", identical at every texture-filter setting) if fast3d uploads them linearly. `tex.c` uses dxt = 0 for the whole runtime texture pipeline too, but there `texSwapAltRowBytes` was the matching pre-swap and D159 no-ops it, so those images are already linear. fast3d (`import_texture`) therefore swaps only dxt = 0 loads whose source is a compiled-in C array (`gfx_tex_source_is_c_array`). Generalisable tells: a texture that looks identically garbled at filter 0/1/2, interlaced along ROWS with a 2-texel horizontal shift, drawn from `assets/*.c`; decode the C array offline both ways before suspecting filtering/clamp. Counting how often a new texture-path branch fires on a level boot (a temporary env-gated log) caught a ~300-per-level over-reach before it shipped.
|
||||||
|
|
||||||
|
## D22. Per-frame counters: gate them with `portN64FrameStep()` (D486)
|
||||||
|
|
||||||
|
Decomp code that advances a counter once per *call* (per rendered frame) instead of by `g_ClockTimer` runs 2-3x faster in real time at the port's 1 tick/frame (and every frame when uncapped, `clk == 0`). D486 found three on the explosion/turret path: autogun shot counter (`unkAC++`), explosion part spawning, explosion/smoke shake decay. `port/include/porttick.h` `portN64FrameStep()` is the shared Rule-2 gate: true on every call at >= 2 ticks/frame (N64-identical), every second tick below, so the counter keeps the N64's 2-ticks-per-frame pace (the D427/D451 reference). If the gated counter also scales a per-event quantity by `g_GlobalTimerDelta` (autogun damage per shot), charge the full 2-tick frame on gated calls or the rate halves. Verify by A/B at `Video.FpsCap` 60 vs 30: the two must match. Any such gate is game logic and needs Rule-2 sign-off.
|
||||||
|
|
||||||
|
## D23. Region bring-up: tooling and port code silently assume NTSC-U (D258)
|
||||||
|
|
||||||
|
PAL/JP had never been built. What broke was all port-side: (1) asset emitters hard-coded `scripts/filelist.u.csv` (so on a PAL/JP ROM they would use US offsets with no error); (2) `romdata.c` treated "not PAL" as NTSC-U (rejecting a JP ROM's country byte); (3) port code referenced globals that exist only under a region-specific define (`LEFTOVERDEBUG` is US/JP-only), which surfaces as a PAL-only link error; (4) the decomp's PAL/JP filelists were incomplete and mislabelled. With a real ROM, prove every filelist row by bytes (same-name US file comparison, constant-shift search, header-walk for variable files) instead of trusting labels. Whenever port code touches a symbol inside a `#if` region block, check all three `REGION_DEFS` sets.
|
||||||
|
|
||||||
## E. Process / method notes
|
## E. Process / method notes
|
||||||
|
|
||||||
- **Never `exit()` while another thread may be inside the GL driver (D344).**
|
- **Never `exit()` while another thread may be inside the GL driver (D344).**
|
||||||
|
|||||||
@@ -951,10 +951,19 @@ static bool gfx_opengl_supports_shaders(void) {
|
|||||||
return GLAD_GL_EXT_gpu_shader4;
|
return GLAD_GL_EXT_gpu_shader4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* D482: GL_RENDERER as read at init, for video.c's low-end default check.
|
||||||
|
* A copy, never the driver's pointer; "" when the driver gave nothing. */
|
||||||
|
static char gl_renderer_str[256] = "";
|
||||||
|
|
||||||
|
extern "C" const char *gfx_opengl_renderer_string(void) {
|
||||||
|
return gl_renderer_str;
|
||||||
|
}
|
||||||
|
|
||||||
static void gfx_opengl_log_info(void) {
|
static void gfx_opengl_log_info(void) {
|
||||||
const char *version = (const char *)glGetString(GL_VERSION);
|
const char *version = (const char *)glGetString(GL_VERSION);
|
||||||
const char *vendor = (const char *)glGetString(GL_VENDOR);
|
const char *vendor = (const char *)glGetString(GL_VENDOR);
|
||||||
const char *renderer = (const char *)glGetString(GL_RENDERER);
|
const char *renderer = (const char *)glGetString(GL_RENDERER);
|
||||||
|
snprintf(gl_renderer_str, sizeof(gl_renderer_str), "%s", renderer ? renderer : "");
|
||||||
const char *glsl_version = (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION);
|
const char *glsl_version = (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION);
|
||||||
sysLogPrintf(LOG_NOTE, "GL: version: %s", version ? version : "unknown");
|
sysLogPrintf(LOG_NOTE, "GL: version: %s", version ? version : "unknown");
|
||||||
sysLogPrintf(LOG_NOTE, "GL: vendor: %s", vendor ? vendor : "unknown");
|
sysLogPrintf(LOG_NOTE, "GL: vendor: %s", vendor ? vendor : "unknown");
|
||||||
@@ -1033,6 +1042,11 @@ static void gfx_opengl_init(void) {
|
|||||||
|
|
||||||
gfx_opengl_init_extensions();
|
gfx_opengl_init_extensions();
|
||||||
|
|
||||||
|
{ /* D482: always capture GL_RENDERER (log_info below is --debug-gl only). */
|
||||||
|
const char *renderer = (const char *)glGetString(GL_RENDERER);
|
||||||
|
snprintf(gl_renderer_str, sizeof(gl_renderer_str), "%s", renderer ? renderer : "");
|
||||||
|
}
|
||||||
|
|
||||||
if (sysArgCheck("--debug-gl")) {
|
if (sysArgCheck("--debug-gl")) {
|
||||||
gfx_opengl_enable_debug();
|
gfx_opengl_enable_debug();
|
||||||
// dump version info as early as possible
|
// dump version info as early as possible
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ extern struct GfxRenderingAPI gfx_opengl_api;
|
|||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
/* D482: GL_RENDERER captured at init ("" if unknown). */
|
||||||
|
const char *gfx_opengl_renderer_string(void);
|
||||||
|
|
||||||
/* Frame capture (dev tool, env GE_PCDUMP=1) — see gfx_opengl.cpp. */
|
/* Frame capture (dev tool, env GE_PCDUMP=1) — see gfx_opengl.cpp. */
|
||||||
bool gfx_opengl_pcdump_enabled(void);
|
bool gfx_opengl_pcdump_enabled(void);
|
||||||
bool gfx_opengl_dump_bound_fbo(uint32_t width, uint32_t height, const char* path);
|
bool gfx_opengl_dump_bound_fbo(uint32_t width, uint32_t height, const char* path);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <set>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
|
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "portaddr.h" /* Stage B: arena windows */
|
#include "portaddr.h" /* Stage B: arena windows */
|
||||||
|
#include "envflag.h"
|
||||||
|
|
||||||
#include "gfx_pc.h"
|
#include "gfx_pc.h"
|
||||||
#include "gfx_cc.h"
|
#include "gfx_cc.h"
|
||||||
@@ -160,6 +162,13 @@ struct LoadedTexture {
|
|||||||
* a CI8 index stream apart from real RGBA16 pixels when a later tile
|
* a CI8 index stream apart from real RGBA16 pixels when a later tile
|
||||||
* re-declares the same TMEM in another format -- see import_texture. */
|
* re-declares the same TMEM in another format -- see import_texture. */
|
||||||
uint8_t src_fmt = 0xFF;
|
uint8_t src_fmt = 0xFF;
|
||||||
|
/* D75: set by gfx_dp_load_block when the load used dxt == 0. The RDP's
|
||||||
|
* LoadBlock only swaps odd rows on the way INTO TMEM when its dxt counter
|
||||||
|
* advances; TMEM reads always swap odd rows (addr ^ 4). 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 relies on this for the Rareware logo's text mip textures. */
|
||||||
|
bool dxt0 = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct RDP {
|
static struct RDP {
|
||||||
@@ -332,12 +341,95 @@ static void gfx_flush(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* D480: shader pre-warm. A combiner seen for the first time compiles its GL
|
||||||
|
* program mid-frame (2.5-11.5 ms each on the dev box; up to ~19 ms frames in
|
||||||
|
* play). The generated GLSL is a pure function of (shader_id0, shader_id1) and
|
||||||
|
* the GL version, so every pair ever created is remembered in $S/ge007.shaders
|
||||||
|
* and compiled at the top of the first gfx_run (and again after a filter change
|
||||||
|
* clears the pool). Output-identical; GE_NOSHADERWARM=1 turns it off. */
|
||||||
|
#define SHADERWARM_HEADER "ge007-shaders v1"
|
||||||
|
#define SHADERWARM_MAX 1024
|
||||||
|
static bool s_shader_warm_pending = true;
|
||||||
|
static int s_shader_list_state = 0; /* 0 = not opened, 1 = writable, -1 = disabled */
|
||||||
|
static std::string s_shader_list_path;
|
||||||
|
static std::set<std::pair<uint64_t, uint32_t>> s_shader_list_known;
|
||||||
|
|
||||||
|
static bool gfx_shader_list_open(void) {
|
||||||
|
if (s_shader_list_state == 0) {
|
||||||
|
s_shader_list_state = -1;
|
||||||
|
if (GE_ENVFLAG("GE_NOSHADERWARM")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
s_shader_list_path = sysResolvePath("$S/ge007.shaders");
|
||||||
|
bool header_ok = false;
|
||||||
|
if (FILE* f = fopen(s_shader_list_path.c_str(), "r")) {
|
||||||
|
char line[64];
|
||||||
|
if (fgets(line, sizeof line, f) && !strncmp(line, SHADERWARM_HEADER, strlen(SHADERWARM_HEADER))) {
|
||||||
|
header_ok = true;
|
||||||
|
unsigned long long id0;
|
||||||
|
unsigned int id1;
|
||||||
|
while (s_shader_list_known.size() < SHADERWARM_MAX && fgets(line, sizeof line, f)) {
|
||||||
|
if (sscanf(line, "%llx %x", &id0, &id1) == 2) {
|
||||||
|
s_shader_list_known.insert(std::make_pair((uint64_t)id0, (uint32_t)id1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
if (!header_ok) {
|
||||||
|
/* missing, foreign or corrupt: start a fresh list */
|
||||||
|
FILE* f = fopen(s_shader_list_path.c_str(), "w");
|
||||||
|
if (f == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fprintf(f, "%s\n", SHADERWARM_HEADER);
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
s_shader_list_state = 1;
|
||||||
|
}
|
||||||
|
return s_shader_list_state == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gfx_shader_list_record(uint64_t shader_id0, uint32_t shader_id1) {
|
||||||
|
if (!gfx_shader_list_open() || s_shader_list_known.size() >= SHADERWARM_MAX) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!s_shader_list_known.insert(std::make_pair(shader_id0, shader_id1)).second) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (FILE* f = fopen(s_shader_list_path.c_str(), "a")) {
|
||||||
|
fprintf(f, "%016llx %08x\n", (unsigned long long)shader_id0, (unsigned int)shader_id1);
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gfx_shader_prewarm(void) {
|
||||||
|
s_shader_warm_pending = false;
|
||||||
|
if (!gfx_shader_list_open() || s_shader_list_known.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto t0 = std::chrono::steady_clock::now();
|
||||||
|
int compiled = 0;
|
||||||
|
gfx_rapi->unload_shader(rendering_state.shader_program);
|
||||||
|
rendering_state.shader_program = nullptr;
|
||||||
|
for (const auto& id : s_shader_list_known) {
|
||||||
|
if (gfx_rapi->lookup_shader(id.first, id.second) == NULL) {
|
||||||
|
struct ShaderProgram* prg = gfx_rapi->create_and_load_new_shader(id.first, id.second);
|
||||||
|
gfx_rapi->unload_shader(prg);
|
||||||
|
compiled++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const double ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - t0).count();
|
||||||
|
sysLogPrintf(LOG_NOTE, "SHADERWARM: compiled %d of %d in %.1f ms", compiled, (int)s_shader_list_known.size(), ms);
|
||||||
|
}
|
||||||
|
|
||||||
static struct ShaderProgram* gfx_lookup_or_create_shader_program(uint64_t shader_id0, uint32_t shader_id1) {
|
static struct ShaderProgram* gfx_lookup_or_create_shader_program(uint64_t shader_id0, uint32_t shader_id1) {
|
||||||
struct ShaderProgram* prg = gfx_rapi->lookup_shader(shader_id0, shader_id1);
|
struct ShaderProgram* prg = gfx_rapi->lookup_shader(shader_id0, shader_id1);
|
||||||
if (prg == NULL) {
|
if (prg == NULL) {
|
||||||
gfx_rapi->unload_shader(rendering_state.shader_program);
|
gfx_rapi->unload_shader(rendering_state.shader_program);
|
||||||
prg = gfx_rapi->create_and_load_new_shader(shader_id0, shader_id1);
|
prg = gfx_rapi->create_and_load_new_shader(shader_id0, shader_id1);
|
||||||
rendering_state.shader_program = prg;
|
rendering_state.shader_program = prg;
|
||||||
|
gfx_shader_list_record(shader_id0, shader_id1);
|
||||||
}
|
}
|
||||||
return prg;
|
return prg;
|
||||||
}
|
}
|
||||||
@@ -1143,6 +1235,16 @@ static void import_texture(int i, int tile, bool importReplacement) {
|
|||||||
} else {
|
} else {
|
||||||
key = { orig_addr, {}, fmt, siz, palette_index, loaded_texture.size_bytes, 0u };
|
key = { orig_addr, {}, fmt, siz, palette_index, loaded_texture.size_bytes, 0u };
|
||||||
}
|
}
|
||||||
|
/* D75: a dxt==0 load samples with swapped odd rows -> distinct image.
|
||||||
|
* Only compiled-in (exe-image) assets: the runtime texture pipeline
|
||||||
|
* (tex.c/image.c, 0x70xxxxxx) loads dxt==0 too, but D159 no-ops its
|
||||||
|
* compensating pre-swap, so those are already linear. */
|
||||||
|
const bool d75_swap = loaded_texture.dxt0 && siz != G_IM_SIZ_32b &&
|
||||||
|
gfx_tex_source_is_c_array(orig_addr) &&
|
||||||
|
rdp.texture_tile[tile].line_size_bytes != 0 &&
|
||||||
|
(rdp.texture_tile[tile].line_size_bytes & 7) == 0 &&
|
||||||
|
loaded_texture.size_bytes > rdp.texture_tile[tile].line_size_bytes;
|
||||||
|
if (d75_swap) key.palette_hash ^= 0xD75D75D7u;
|
||||||
|
|
||||||
/* Intro blood (M-201): textures the game regenerates IN PLACE in the
|
/* Intro blood (M-201): textures the game regenerates IN PLACE in the
|
||||||
* per-frame dynamic pool (dynAllocate, [g_VtxBuffers[0], g_VtxBuffers[2]))
|
* per-frame dynamic pool (dynAllocate, [g_VtxBuffers[0], g_VtxBuffers[2]))
|
||||||
@@ -1212,6 +1314,26 @@ static void import_texture(int i, int tile, bool importReplacement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* D75: emulate the RDP's odd-row TMEM read swap for dxt==0 loads (see
|
||||||
|
* LoadedTexture::dxt0). Done after destride so rows are contiguous. */
|
||||||
|
std::vector<uint8_t> d75_buf;
|
||||||
|
if (d75_swap) {
|
||||||
|
const uint32_t ln = rdp.texture_tile[tile].line_size_bytes;
|
||||||
|
const uint32_t rows = loaded_texture.size_bytes / ln;
|
||||||
|
d75_buf.assign(loaded_texture.addr, loaded_texture.addr + (size_t)rows * ln +
|
||||||
|
(loaded_texture.size_bytes - rows * ln));
|
||||||
|
for (uint32_t r = 1; r < rows; r += 2) {
|
||||||
|
uint8_t* row = &d75_buf[(size_t)r * ln];
|
||||||
|
for (uint32_t b = 0; b + 8 <= ln; b += 8) {
|
||||||
|
uint8_t t4[4];
|
||||||
|
memcpy(t4, row + b, 4);
|
||||||
|
memcpy(row + b, row + b + 4, 4);
|
||||||
|
memcpy(row + b + 4, t4, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loaded_texture.addr = d75_buf.data();
|
||||||
|
}
|
||||||
|
|
||||||
/* GE_DTEX: dump the load parameters for the first N textures of a frame so
|
/* GE_DTEX: dump the load parameters for the first N textures of a frame so
|
||||||
* RC2 (mip-chain contamination -> over-tall upload) can be told apart from a
|
* RC2 (mip-chain contamination -> over-tall upload) can be told apart from a
|
||||||
* decode/row-swap bug. tile_h = base-tile height from SETTILESIZE; if the
|
* decode/row-swap bug. tile_h = base-tile height from SETTILESIZE; if the
|
||||||
@@ -3195,6 +3317,7 @@ static void gfx_dp_load_block(uint8_t tile, uint32_t uls, uint32_t ult, uint32_t
|
|||||||
loaded_texture.raw_tex_metadata = rdp.texture_to_load.raw_tex_metadata;
|
loaded_texture.raw_tex_metadata = rdp.texture_to_load.raw_tex_metadata;
|
||||||
loaded_texture.addr = rdp.texture_to_load.addr;
|
loaded_texture.addr = rdp.texture_to_load.addr;
|
||||||
loaded_texture.src_fmt = rdp.texture_to_load.fmt; /* D229 */
|
loaded_texture.src_fmt = rdp.texture_to_load.fmt; /* D229 */
|
||||||
|
loaded_texture.dxt0 = (dxt == 0); /* D75 */
|
||||||
|
|
||||||
rdp.textures_changed[0] = rdp.textures_changed[1] = true;
|
rdp.textures_changed[0] = rdp.textures_changed[1] = true;
|
||||||
}
|
}
|
||||||
@@ -3236,6 +3359,7 @@ static void gfx_dp_load_tile(uint8_t tile, uint32_t uls, uint32_t ult, uint32_t
|
|||||||
loaded_texture.raw_tex_metadata = rdp.texture_to_load.raw_tex_metadata;
|
loaded_texture.raw_tex_metadata = rdp.texture_to_load.raw_tex_metadata;
|
||||||
loaded_texture.addr = rdp.texture_to_load.addr + start_offset_bytes;
|
loaded_texture.addr = rdp.texture_to_load.addr + start_offset_bytes;
|
||||||
loaded_texture.src_fmt = rdp.texture_to_load.fmt; /* D229 */
|
loaded_texture.src_fmt = rdp.texture_to_load.fmt; /* D229 */
|
||||||
|
loaded_texture.dxt0 = false; /* D75 */
|
||||||
|
|
||||||
rdp.texture_tile[tile].uls = uls;
|
rdp.texture_tile[tile].uls = uls;
|
||||||
rdp.texture_tile[tile].ult = ult;
|
rdp.texture_tile[tile].ult = ult;
|
||||||
@@ -4235,6 +4359,10 @@ extern "C" void gfx_run(Gfx* commands) {
|
|||||||
}
|
}
|
||||||
dropped_frame = false;
|
dropped_frame = false;
|
||||||
|
|
||||||
|
if (s_shader_warm_pending) {
|
||||||
|
gfx_shader_prewarm(); /* D480 */
|
||||||
|
}
|
||||||
|
|
||||||
gfx_rapi->update_framebuffer_parameters(0, gfx_current_window_dimensions.width,
|
gfx_rapi->update_framebuffer_parameters(0, gfx_current_window_dimensions.width,
|
||||||
gfx_current_window_dimensions.height, 1, false, true, true,
|
gfx_current_window_dimensions.height, 1, false, true, true,
|
||||||
!game_renders_to_framebuffer);
|
!game_renders_to_framebuffer);
|
||||||
@@ -4344,6 +4472,7 @@ extern "C" void reset_texture_state() {
|
|||||||
rendering_state.shader_program = nullptr;
|
rendering_state.shader_program = nullptr;
|
||||||
}
|
}
|
||||||
gfx_rapi->clear_shaders();
|
gfx_rapi->clear_shaders();
|
||||||
|
s_shader_warm_pending = true; /* D480: re-warm on the next frame */
|
||||||
color_combiner_pool.clear();
|
color_combiner_pool.clear();
|
||||||
prev_combiner = color_combiner_pool.end();
|
prev_combiner = color_combiner_pool.end();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef PORT_PORTTICK_H
|
||||||
|
#define PORT_PORTTICK_H
|
||||||
|
|
||||||
|
#include <ultra64.h>
|
||||||
|
|
||||||
|
/* D486 (RULE-2, D13 class): some decomp code advances a counter once per
|
||||||
|
* rendered frame. The N64 rendered at 2 ticks per frame (more under load);
|
||||||
|
* the port renders 1 tick per frame at 60 fps (0 when uncapped), so those
|
||||||
|
* counters ran 2-3x faster in real time. portN64FrameStep() is true on every
|
||||||
|
* call at >= 2 ticks per frame (identical to the N64 path) and on every
|
||||||
|
* second tick below that, so a per-frame counter gated by it keeps the N64's
|
||||||
|
* 2-ticks-per-frame pace -- the same reference D427/D451 use for auto-fire.
|
||||||
|
* Stateless: it only looks at the tick counter crossing an even boundary. */
|
||||||
|
extern s32 g_ClockTimer;
|
||||||
|
extern s32 g_GlobalTimer;
|
||||||
|
|
||||||
|
static inline int portN64FrameStep(void)
|
||||||
|
{
|
||||||
|
return g_ClockTimer >= 2 || ((g_GlobalTimer >> 1) != ((g_GlobalTimer - g_ClockTimer) >> 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -2785,11 +2785,26 @@ static unsigned inputComputePadSlot(int idx, signed char *stick_x, signed char *
|
|||||||
!p->pause_state && !p->mpmenuon && !g_PlayerIsInTank &&
|
!p->pause_state && !p->mpmenuon && !g_PlayerIsInTank &&
|
||||||
!lvlGetControlsLockedFlag() &&
|
!lvlGetControlsLockedFlag() &&
|
||||||
!gameScriptedCameraActive();
|
!gameScriptedCameraActive();
|
||||||
|
int weaponBlocks = 0;
|
||||||
if (canCrouch && (crouchNow || s_crouchApplied)) { /* weapon lookup only when it can matter */
|
if (canCrouch && (crouchNow || s_crouchApplied)) { /* weapon lookup only when it can matter */
|
||||||
canCrouch = !bondwalkItemCheckBitflags(p->hands[GUNRIGHT].weaponnum,
|
weaponBlocks = bondwalkItemCheckBitflags(p->hands[GUNRIGHT].weaponnum,
|
||||||
WEAPONSTATBITFLAG_DISABLE_CROUCH);
|
WEAPONSTATBITFLAG_DISABLE_CROUCH);
|
||||||
|
canCrouch = !weaponBlocks;
|
||||||
}
|
}
|
||||||
if (canCrouch && crouchNow) {
|
static int frozenLogged = 0;
|
||||||
|
if (!weaponBlocks) frozenLogged = 0;
|
||||||
|
if (weaponBlocks) {
|
||||||
|
/* D483 (#136): a weapon with DISABLE_CROUCH (the sniper rifle)
|
||||||
|
* blocks the native crouch-down AND crouch-up (bondview2.c), so
|
||||||
|
* on the N64 Bond keeps whatever stance he had when he switched
|
||||||
|
* to it. Freeze the port stance the same way: don't apply a new
|
||||||
|
* crouch, don't stand up, and never feed C-down (it zooms out
|
||||||
|
* with this weapon). Switching back resumes the normal rules. */
|
||||||
|
if (!frozenLogged && configGetInputLog())
|
||||||
|
sysLogPrintf(LOG_NOTE, "GE_INPUTLOG free crouch frozen (weapon %d disables crouch, crouchpos=%d, key=%d)",
|
||||||
|
(int)p->hands[GUNRIGHT].weaponnum, (int)p->crouchpos, crouchNow);
|
||||||
|
frozenLogged = 1;
|
||||||
|
} else if (canCrouch && crouchNow) {
|
||||||
/* In aim mode the native crouchUp branch runs every tick without
|
/* In aim mode the native crouchUp branch runs every tick without
|
||||||
* C-down, undoing the port stance by one step. Feed the native
|
* C-down, undoing the port stance by one step. Feed the native
|
||||||
* axis ONLY while aiming; hipfire still crouches by stance alone. */
|
* axis ONLY while aiming; hipfire still crouches by stance alone. */
|
||||||
|
|||||||
@@ -68,11 +68,15 @@ extern void videoSyncSplitScreen(void); /* D416 */
|
|||||||
u64 osClockRate = 6250000; /* RSP counter rate (Hz) — see PD port */
|
u64 osClockRate = 6250000; /* RSP counter rate (Hz) — see PD port */
|
||||||
u32 osMemSize = 16 * 1024 * 1024; /* pretend 16MB RDRAM (+expansion) */
|
u32 osMemSize = 16 * 1024 * 1024; /* pretend 16MB RDRAM (+expansion) */
|
||||||
/* TV type (normally set from console hardware in os/initialize.c, excluded).
|
/* TV type (normally set from console hardware in os/initialize.c, excluded).
|
||||||
* The game reads it to pick PAL/NTSC behaviour (e.g. schedulerInitThread
|
* Set to what the matching console reports: a European (PAL) N64 reports
|
||||||
* checks osTvType == OS_TV_MPAL). Set to match the emulated region: PAL
|
* OS_TV_PAL (0); OS_TV_MPAL (2) is Brazil's 60 Hz PAL-M console. GE's shared
|
||||||
* consoles report MPAL, NTSC consoles report NTSC. */
|
* code only tests for MPAL (sched.c / init.c / fr.c: MPAL LAN1 mode, else
|
||||||
|
* NTSC LAN1), so on a real PAL console those take the NTSC branch and the
|
||||||
|
* EU build's own video_related_8 (fr.c, #ifdef VERSION_EU) then installs
|
||||||
|
* the PAL modes. D488: this used to be MPAL (a scaffolding-era inference
|
||||||
|
* from those checks), which sent the port down the Brazil-only branches. */
|
||||||
#ifdef REFRESH_PAL
|
#ifdef REFRESH_PAL
|
||||||
u32 osTvType = OS_TV_MPAL; /* EU: PAL (0=PAL 1=NTSC 2=MPAL) */
|
u32 osTvType = OS_TV_PAL; /* EU: PAL (0=PAL 1=NTSC 2=MPAL) */
|
||||||
#else
|
#else
|
||||||
u32 osTvType = OS_TV_NTSC; /* US/JP: NTSC */
|
u32 osTvType = OS_TV_NTSC; /* US/JP: NTSC */
|
||||||
#endif
|
#endif
|
||||||
@@ -329,7 +333,7 @@ void portKernelInit(void)
|
|||||||
g_lastFrameUs = sysGetMicroseconds();
|
g_lastFrameUs = sysGetMicroseconds();
|
||||||
g_lastHeartbeatUs = g_lastFrameUs;
|
g_lastHeartbeatUs = g_lastFrameUs;
|
||||||
|
|
||||||
if (osTvType == OS_TV_MPAL || osTvType == OS_TV_PAL) {
|
if (osTvType == OS_TV_PAL) { /* D488: MPAL (PAL-M) is a 60 Hz system */
|
||||||
g_tickIntervalUs = 1000000 / 50; /* PAL: 50 frames/s -> 20ms */
|
g_tickIntervalUs = 1000000 / 50; /* PAL: 50 frames/s -> 20ms */
|
||||||
g_determQuantum = 931050;
|
g_determQuantum = 931050;
|
||||||
} else {
|
} else {
|
||||||
@@ -820,7 +824,29 @@ void osViSetMode(OSViMode *vm)
|
|||||||
* every frame into a half-height band. */
|
* every frame into a half-height band. */
|
||||||
u32 w = vm->comRegs.width;
|
u32 w = vm->comRegs.width;
|
||||||
s32 h = (s32)(((vm->fldRegs[0].yScale & 0xFFF) * 480u) / 0x800u);
|
s32 h = (s32)(((vm->fldRegs[0].yScale & 0xFFF) * 480u) / 0x800u);
|
||||||
|
#ifdef VERSION_EU
|
||||||
|
/* D487: the EU build's video_related_8 (src/fr.c, #ifdef VERSION_EU)
|
||||||
|
* encodes yScale = bufy * 0x800 / (0x220 + (bufy == 330 ? 28 : 0)),
|
||||||
|
* i.e. against 544 (572) lines, not 480. Inverting with 480 gave a CFB
|
||||||
|
* ~12% too short (269 -> 237), so fast3d cropped the bottom of every
|
||||||
|
* PAL frame (HUD ammo counter reduced to a dot). Recover bufy exactly:
|
||||||
|
* the smallest height whose EU encoding reproduces this yScale. Keyed on
|
||||||
|
* the build, not the OS_VI_BIT_PAL ctrl bit: the modes reaching here
|
||||||
|
* (the scheduler's LAN1 start mode and the EU video_related_8 modes) do
|
||||||
|
* not reliably carry that bit (observed: yScale 0x400 = 272 without it). */
|
||||||
|
{
|
||||||
|
u32 ys = vm->fldRegs[0].yScale & 0xFFF;
|
||||||
|
s32 cand;
|
||||||
|
for (cand = 1; cand <= 576; cand++) {
|
||||||
|
u32 den = 0x220u + (cand == 330 ? 28u : 0u);
|
||||||
|
if (((u32)cand * 0x800u) / den == ys) { h = cand; break; }
|
||||||
|
}
|
||||||
|
if (cand > 576) h = 0;
|
||||||
|
}
|
||||||
|
if (h <= 0 || h > 576) h = 272; /* fallback: the EU CFB height (fr.h SCREEN_HEIGHT_272) */
|
||||||
|
#else
|
||||||
if (h <= 0 || h > 480) h = pal ? 272 : 240; /* fallback: LAN1 CFB height */
|
if (h <= 0 || h > 480) h = pal ? 272 : 240; /* fallback: LAN1 CFB height */
|
||||||
|
#endif
|
||||||
if (w > 640) w = 640;
|
if (w > 640) w = 640;
|
||||||
videoUpdateNativeResolution((s32)w, h);
|
videoUpdateNativeResolution((s32)w, h);
|
||||||
}
|
}
|
||||||
@@ -982,7 +1008,8 @@ static void piServiceDma(s32 direction, u32 srcPA, void *dstVA, u32 size)
|
|||||||
char win[1200] = "";
|
char win[1200] = "";
|
||||||
char *wp = win;
|
char *wp = win;
|
||||||
for (int i = 0; i < 32; i++) {
|
for (int i = 0; i < 32; i++) {
|
||||||
wp += snprintf(wp, win + sizeof(win) - (wp - win),
|
/* D478: the bound is the bytes left, not a pointer. */
|
||||||
|
wp += snprintf(wp, sizeof(win) - (size_t)(wp - win),
|
||||||
" %p", (void *)sp[i]);
|
" %p", (void *)sp[i]);
|
||||||
}
|
}
|
||||||
#if defined(PLATFORM_WINDOWS)
|
#if defined(PLATFORM_WINDOWS)
|
||||||
@@ -1312,6 +1339,109 @@ s32 osMotorStop(OSPfs *pfs)
|
|||||||
|
|
||||||
void osSpTaskLoad(OSTask *t) { (void)t; /* nothing to load on the host */ }
|
void osSpTaskLoad(OSTask *t) { (void)t; /* nothing to load on the host */ }
|
||||||
|
|
||||||
|
static void portRenderGfxTask(Gfx *dl)
|
||||||
|
{
|
||||||
|
uint64_t t0 = sysGetMicroseconds();
|
||||||
|
videoStartFrame();
|
||||||
|
videoSyncSplitScreen();
|
||||||
|
gfx_run(dl);
|
||||||
|
videoEndFrame();
|
||||||
|
g_lastFrameUs = sysGetMicroseconds();
|
||||||
|
if (++g_framesRendered <= 5 || (g_framesRendered % 300) == 0)
|
||||||
|
sysLogPrintf(LOG_NOTE, "frame %d rendered in %llu us",
|
||||||
|
g_framesRendered,
|
||||||
|
(unsigned long long)(sysGetMicroseconds() - t0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* D481: render worker. The N64 RSP/RDP run beside the CPU; the scheduler
|
||||||
|
* starts a task and learns it finished from the SP/DP interrupts. The port
|
||||||
|
* used to run fast3d inline here, on the scheduler thread, so a VI retrace
|
||||||
|
* arriving mid-render was forwarded to the game only after the render: the
|
||||||
|
* game frame started late, the next retrace came < half a frame after it, and
|
||||||
|
* boss.c's tick gate skipped it (a 33 ms frame). Invisible with 1-3 ms
|
||||||
|
* renders, ~1 frame in 4 on an Intel HD 3000 (8-12 ms renders). Gfx tasks now
|
||||||
|
* go to this worker; it posts SP/DP done exactly as the inline path did.
|
||||||
|
* One-slot mailbox: the game submits frame N+1 only after frame N's done event
|
||||||
|
* has round-tripped (worker -> interruptQ -> __scTaskComplete -> client DONE
|
||||||
|
* -> boss.c), so the slot is always empty at hand-off. GE_DETERM keeps the
|
||||||
|
* inline path (its retrace synthesis is call-sequenced, see osRecvMesg), and
|
||||||
|
* GE_RENDERINLINE=1 restores it as a falsifier. */
|
||||||
|
static pthread_mutex_t s_rwLock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static pthread_cond_t s_rwCond = PTHREAD_COND_INITIALIZER;
|
||||||
|
static Gfx *s_rwTask = NULL; /* submitted display list; NULL = worker idle */
|
||||||
|
static int s_rwMode = 0; /* 0 = undecided, 1 = worker, -1 = inline */
|
||||||
|
|
||||||
|
static void *portRenderWorker(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
for (;;) {
|
||||||
|
pthread_mutex_lock(&s_rwLock);
|
||||||
|
while (s_rwTask == NULL) {
|
||||||
|
/* Quit while idle: stop taking frames and never touch GL again.
|
||||||
|
* The host only waits for the renderer while it is inside a frame
|
||||||
|
* (video.c videoHostExitIfRequested), so an idle worker is already
|
||||||
|
* a safe exit point; a frame that is running parks itself at its
|
||||||
|
* boundary as before (D344). */
|
||||||
|
if (videoQuitRequested()) {
|
||||||
|
pthread_mutex_unlock(&s_rwLock);
|
||||||
|
for (;;) sysSleep(100000);
|
||||||
|
}
|
||||||
|
struct timespec ts;
|
||||||
|
clock_gettime(CLOCK_REALTIME, &ts);
|
||||||
|
ts.tv_nsec += 100 * 1000 * 1000;
|
||||||
|
if (ts.tv_nsec >= 1000000000L) { ts.tv_sec++; ts.tv_nsec -= 1000000000L; }
|
||||||
|
pthread_cond_timedwait(&s_rwCond, &s_rwLock, &ts);
|
||||||
|
}
|
||||||
|
Gfx *dl = s_rwTask;
|
||||||
|
pthread_mutex_unlock(&s_rwLock);
|
||||||
|
|
||||||
|
portRenderGfxTask(dl);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&s_rwLock);
|
||||||
|
s_rwTask = NULL;
|
||||||
|
pthread_cond_broadcast(&s_rwCond);
|
||||||
|
pthread_mutex_unlock(&s_rwLock);
|
||||||
|
portPostEventForce(OS_EVENT_SP); /* D134: must not be dropped */
|
||||||
|
portPostEventForce(OS_EVENT_DP); /* gfx task is its own DP task */
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int portRenderWorkerOn(void)
|
||||||
|
{
|
||||||
|
if (s_rwMode == 0) {
|
||||||
|
s_rwMode = -1;
|
||||||
|
if (g_determEnabled) {
|
||||||
|
sysLogPrintf(LOG_NOTE, "D481: GE_DETERM -> gfx tasks run inline");
|
||||||
|
} else if (getenv("GE_RENDERINLINE") != NULL) {
|
||||||
|
sysLogPrintf(LOG_NOTE, "D481: GE_RENDERINLINE -> gfx tasks run inline");
|
||||||
|
} else {
|
||||||
|
pthread_attr_t attr;
|
||||||
|
pthread_t th;
|
||||||
|
pthread_attr_init(&attr);
|
||||||
|
#if !defined(_WIN32)
|
||||||
|
/* Same low stack as the game threads (see portAllocLowStack). */
|
||||||
|
void *lowStack = portAllocLowStack(PORT_THREAD_STACK);
|
||||||
|
if (lowStack)
|
||||||
|
pthread_attr_setstack(&attr, lowStack, PORT_THREAD_STACK);
|
||||||
|
else
|
||||||
|
pthread_attr_setstacksize(&attr, PORT_THREAD_STACK);
|
||||||
|
#else
|
||||||
|
pthread_attr_setstacksize(&attr, PORT_THREAD_STACK);
|
||||||
|
#endif
|
||||||
|
if (pthread_create(&th, &attr, portRenderWorker, NULL) == 0) {
|
||||||
|
pthread_detach(th);
|
||||||
|
s_rwMode = 1;
|
||||||
|
sysLogPrintf(LOG_NOTE, "D481: render worker started");
|
||||||
|
} else {
|
||||||
|
sysLogPrintf(LOG_ERROR, "D481: render worker failed to start; gfx tasks run inline");
|
||||||
|
}
|
||||||
|
pthread_attr_destroy(&attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s_rwMode == 1;
|
||||||
|
}
|
||||||
|
|
||||||
void osSpTaskStartGo(OSTask *t)
|
void osSpTaskStartGo(OSTask *t)
|
||||||
{
|
{
|
||||||
if (g_determTraceEnabled && !g_determTaskEverRun) {
|
if (g_determTraceEnabled && !g_determTaskEverRun) {
|
||||||
@@ -1324,18 +1454,23 @@ void osSpTaskStartGo(OSTask *t)
|
|||||||
/* Phase 3: execute the audio ucode against t->t.data_ptr (an Acmd
|
/* Phase 3: execute the audio ucode against t->t.data_ptr (an Acmd
|
||||||
* list). For now the task simply completes; libaudio's amMain still
|
* list). For now the task simply completes; libaudio's amMain still
|
||||||
* runs its per-frame bookkeeping. */
|
* runs its per-frame bookkeeping. */
|
||||||
|
} else if (portRenderWorkerOn()) {
|
||||||
|
/* D481: hand the display list to the render worker; it posts the
|
||||||
|
* SP/DP done events when the frame is presented. */
|
||||||
|
pthread_mutex_lock(&s_rwLock);
|
||||||
|
if (s_rwTask != NULL) {
|
||||||
|
sysLogPrintf(LOG_ERROR, "D481: gfx task submitted while the previous one is still rendering (invariant broken); waiting");
|
||||||
|
while (s_rwTask != NULL) {
|
||||||
|
pthread_cond_wait(&s_rwCond, &s_rwLock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s_rwTask = (Gfx *)t->t.data_ptr;
|
||||||
|
pthread_cond_broadcast(&s_rwCond);
|
||||||
|
pthread_mutex_unlock(&s_rwLock);
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
/* Graphics task: run the software RSP on the display list. */
|
/* Graphics task: run the software RSP on the display list inline. */
|
||||||
uint64_t t0 = sysGetMicroseconds();
|
portRenderGfxTask((Gfx *)t->t.data_ptr);
|
||||||
videoStartFrame();
|
|
||||||
videoSyncSplitScreen();
|
|
||||||
gfx_run((Gfx *)t->t.data_ptr);
|
|
||||||
videoEndFrame();
|
|
||||||
g_lastFrameUs = sysGetMicroseconds();
|
|
||||||
if (++g_framesRendered <= 5 || (g_framesRendered % 300) == 0)
|
|
||||||
sysLogPrintf(LOG_NOTE, "frame %d rendered in %llu us",
|
|
||||||
g_framesRendered,
|
|
||||||
(unsigned long long)(sysGetMicroseconds() - t0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
portPostEventForce(OS_EVENT_SP); /* D134: must not be dropped */
|
portPostEventForce(OS_EVENT_SP); /* D134: must not be dropped */
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ int main(int argc, char **argv)
|
|||||||
* cheat-unlock bit in the save block at read time (per-slot CRC
|
* cheat-unlock bit in the save block at read time (per-slot CRC
|
||||||
* recomputed via the game's own fileGenerateCRC), so the cheat
|
* recomputed via the game's own fileGenerateCRC), so the cheat
|
||||||
* menu is fully populated without completed levels. */
|
* menu is fully populated without completed levels. */
|
||||||
|
#if defined(LEFTOVERDEBUG) /* the debug unlock flags only exist with LEFTOVERDEBUG (not PAL); D442's query-time hooks cover All unlocked everywhere */
|
||||||
{
|
{
|
||||||
extern s32 portAllUnlocked; /* port/src/video.c */
|
extern s32 portAllUnlocked; /* port/src/video.c */
|
||||||
extern s32 debug_enable_all_levels_flag; /* src/game/debugmenu_handler.c */
|
extern s32 debug_enable_all_levels_flag; /* src/game/debugmenu_handler.c */
|
||||||
@@ -162,6 +163,7 @@ int main(int argc, char **argv)
|
|||||||
"(Game.AllUnlocked=1)");
|
"(Game.AllUnlocked=1)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* 2. Load the ROM and map segments. */
|
/* 2. Load the ROM and map segments. */
|
||||||
if (romdataInit() != 0) {
|
if (romdataInit() != 0) {
|
||||||
|
|||||||
@@ -1809,7 +1809,20 @@ static int s_d314Enabled = -1; /* -1 = not yet resolved */
|
|||||||
* changes it; its name only selects it. Reset actions use the whole row. */
|
* changes it; its name only selects it. Reset actions use the whole row. */
|
||||||
static int overlayControlSpan(int i, s32 *x0, s32 *x1);
|
static int overlayControlSpan(int i, s32 *x0, s32 *x1);
|
||||||
|
|
||||||
|
/* D481: HandleInput runs on the scheduler thread (controller poll) and Emit on
|
||||||
|
* the render worker; both rebuild s_visIdx/s_visN/s_scroll/s_sel. They used to
|
||||||
|
* share one thread, so serialise them now that rendering has its own. */
|
||||||
|
static SDL_SpinLock s_ovLock;
|
||||||
|
|
||||||
|
static void overlayHandleInputLocked(void);
|
||||||
void optionsOverlayHandleInput(void)
|
void optionsOverlayHandleInput(void)
|
||||||
|
{
|
||||||
|
SDL_AtomicLock(&s_ovLock);
|
||||||
|
overlayHandleInputLocked();
|
||||||
|
SDL_AtomicUnlock(&s_ovLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void overlayHandleInputLocked(void)
|
||||||
{
|
{
|
||||||
static int prevUp, prevDn, prevLf, prevRt, prevLmb, prevRmb, prevPadBack;
|
static int prevUp, prevDn, prevLf, prevRt, prevLmb, prevRmb, prevPadBack;
|
||||||
/* D347: hold-to-repeat state (18/4-frame cadence, same as the options
|
/* D347: hold-to-repeat state (18/4-frame cadence, same as the options
|
||||||
@@ -2416,7 +2429,16 @@ static Gfx *drawBodyFit(Gfx *gdl, s32 x, s32 y, s32 maxW, const char *str, u32 c
|
|||||||
return drawBody(gdl, x, y, out, colour);
|
return drawBody(gdl, x, y, out, colour);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Gfx *overlayEmitLocked(void);
|
||||||
Gfx *optionsOverlayEmit(void)
|
Gfx *optionsOverlayEmit(void)
|
||||||
|
{
|
||||||
|
SDL_AtomicLock(&s_ovLock); /* D481: see optionsOverlayHandleInput */
|
||||||
|
Gfx *dl = overlayEmitLocked();
|
||||||
|
SDL_AtomicUnlock(&s_ovLock);
|
||||||
|
return dl;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Gfx *overlayEmitLocked(void)
|
||||||
{
|
{
|
||||||
if (!s_inited) {
|
if (!s_inited) {
|
||||||
overlayInit();
|
overlayInit();
|
||||||
|
|||||||
@@ -75,8 +75,10 @@ unsigned char *g_pc_animdata_base = NULL;
|
|||||||
* data/ takes precedence. */
|
* data/ takes precedence. */
|
||||||
static const char *romFileCandidates(void)
|
static const char *romFileCandidates(void)
|
||||||
{
|
{
|
||||||
#if GE007_IS_PAL
|
#if defined(VERSION_EU)
|
||||||
return "ge007.pal-final.z64|baserom.e.z64";
|
return "ge007.pal-final.z64|baserom.e.z64";
|
||||||
|
#elif defined(VERSION_JP)
|
||||||
|
return "ge007.jpn-final.z64|baserom.j.z64";
|
||||||
#else
|
#else
|
||||||
return "ge007.ntsc-final.z64|baserom.u.z64";
|
return "ge007.ntsc-final.z64|baserom.u.z64";
|
||||||
#endif
|
#endif
|
||||||
@@ -85,8 +87,10 @@ static const char *romFileCandidates(void)
|
|||||||
static int romHeaderValid(const u8 *h, char *err, size_t errsz)
|
static int romHeaderValid(const u8 *h, char *err, size_t errsz)
|
||||||
{
|
{
|
||||||
static const u8 magic[4] = { 0x80, 0x37, 0x12, 0x40 };
|
static const u8 magic[4] = { 0x80, 0x37, 0x12, 0x40 };
|
||||||
#if GE007_IS_PAL
|
#if defined(VERSION_EU)
|
||||||
const char country = 'P';
|
const char country = 'P';
|
||||||
|
#elif defined(VERSION_JP)
|
||||||
|
const char country = 'J';
|
||||||
#else
|
#else
|
||||||
const char country = 'E';
|
const char country = 'E';
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include <PR/ultratypes.h>
|
#include <PR/ultratypes.h>
|
||||||
#include <PR/gbi.h>
|
#include <PR/gbi.h>
|
||||||
|
#include <bondconstants.h> /* LEVELID_TITLE (D416); MinGW's SDL.h pulled it in by accident, glibc's doesn't */
|
||||||
|
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
@@ -37,11 +38,12 @@
|
|||||||
#include "../fast3d/gfx_sdl.h"
|
#include "../fast3d/gfx_sdl.h"
|
||||||
#include "../fast3d/gfx_opengl.h"
|
#include "../fast3d/gfx_opengl.h"
|
||||||
|
|
||||||
/* GE's internal resolution: NTSC LAN1 is 640x480; PAL LAN1 shows a
|
/* GE's internal resolution before the game's first osViSetMode (which then
|
||||||
* 640x400 area. The window opens at the native size (1:1) by default. */
|
* sets the real CFB size): NTSC 640x480; PAL is taller, not shorter -- the EU
|
||||||
|
* game renders 320x269 at 50 Hz with no borders (TCRF), so 2x = 640x538 (D487). */
|
||||||
#ifdef REFRESH_PAL
|
#ifdef REFRESH_PAL
|
||||||
#define GE_NATIVE_W 640
|
#define GE_NATIVE_W 640
|
||||||
#define GE_NATIVE_H 400
|
#define GE_NATIVE_H 538
|
||||||
#else
|
#else
|
||||||
#define GE_NATIVE_W 640
|
#define GE_NATIVE_W 640
|
||||||
#define GE_NATIVE_H 480
|
#define GE_NATIVE_H 480
|
||||||
@@ -75,6 +77,7 @@ static int cfgSafeAreaCrop = 1; /* crop the N64 TV-overscan safe-area margin
|
|||||||
static int cfgAspectMode = 0; /* D447: 0 = Window (fill the window), 1 = Original (pillar/letterbox to the console aspect: 4:3, or 16:9 while the game's Ratio option is 16:9) */
|
static int cfgAspectMode = 0; /* D447: 0 = Window (fill the window), 1 = Original (pillar/letterbox to the console aspect: 4:3, or 16:9 while the game's Ratio option is 16:9) */
|
||||||
static int cfgFullscreen = 0; /* 0 = windowed, 1 = borderless fullscreen */
|
static int cfgFullscreen = 0; /* 0 = windowed, 1 = borderless fullscreen */
|
||||||
static int cfgDeckPresetApplied = 0; /* D283: 1 once the Steam Deck preset has been considered */
|
static int cfgDeckPresetApplied = 0; /* D283: 1 once the Steam Deck preset has been considered */
|
||||||
|
static int cfgLowEndConsidered = 0; /* D482: 1 once the low-end GPU defaults have been considered */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* [Window] persistence. W/H = 0 -> auto (gfx_sdl2 fits a 4:3 window into ~85%
|
* [Window] persistence. W/H = 0 -> auto (gfx_sdl2 fits a 4:3 window into ~85%
|
||||||
@@ -246,6 +249,11 @@ f32 portNativeAspect(void)
|
|||||||
|
|
||||||
f32 portScaleFovY(f32 fovy, s32 isTitleScreen)
|
f32 portScaleFovY(f32 fovy, s32 isTitleScreen)
|
||||||
{
|
{
|
||||||
|
/* D484 (#136): the 20-degree floor below guards the port's own widening,
|
||||||
|
* but it also raised the game's narrow zoom FOVs (sniper/camera down to
|
||||||
|
* 7 degrees, the watch zoom) to 20, capping scope magnification at about
|
||||||
|
* a third of the N64's. Never floor above the game's own value. */
|
||||||
|
const f32 floorFovY = fovy < 20.0f ? fovy : 20.0f;
|
||||||
if (!isTitleScreen) {
|
if (!isTitleScreen) {
|
||||||
/* D334: native widescreen already widens the horizontal FOV through
|
/* D334: native widescreen already widens the horizontal FOV through
|
||||||
* the projection aspect; the Phase-4 vertical boost below was the
|
* the projection aspect; the Phase-4 vertical boost below was the
|
||||||
@@ -259,8 +267,8 @@ f32 portScaleFovY(f32 fovy, s32 isTitleScreen)
|
|||||||
fovy *= portFovScale;
|
fovy *= portFovScale;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fovy > 160.0f) { fovy = 160.0f; }
|
if (fovy > 160.0f) { fovy = 160.0f; }
|
||||||
if (fovy < 20.0f) { fovy = 20.0f; }
|
if (fovy < floorFovY) { fovy = floorFovY; }
|
||||||
return fovy;
|
return fovy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,6 +511,7 @@ PD_CONSTRUCTOR static void videoConfigInit(void)
|
|||||||
configRegisterInt("Window.Y", &cfgWinY, -1, 16384);
|
configRegisterInt("Window.Y", &cfgWinY, -1, 16384);
|
||||||
configRegisterInt("Window.Maximized", &cfgWinMax, 0, 1);
|
configRegisterInt("Window.Maximized", &cfgWinMax, 0, 1);
|
||||||
configRegisterInt("Video.DeckPresetApplied", &cfgDeckPresetApplied, 0, 1); /* D283 */
|
configRegisterInt("Video.DeckPresetApplied", &cfgDeckPresetApplied, 0, 1); /* D283 */
|
||||||
|
configRegisterInt("Video.LowEndConsidered", &cfgLowEndConsidered, 0, 1); /* D482 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* D283: Steam Deck preset. Called from main() right AFTER configLoad(), so
|
/* D283: Steam Deck preset. Called from main() right AFTER configLoad(), so
|
||||||
@@ -592,6 +601,81 @@ void videoApplySteamDeckPreset(void)
|
|||||||
configSave();
|
configSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* D482: lighter defaults on Atom/Celeron-class GPUs (#92).
|
||||||
|
* Measured (Lenovo X220 + the PD port on the same box): fast3d costs no more
|
||||||
|
* per batch/triangle than PD's; the gap on very weak hardware is workload.
|
||||||
|
* DrawDistance/LodDistance 250 send 2.5-3x the draw batches of the authored
|
||||||
|
* distance and MSAA 2x adds ~25-30% GPU time -- PD ships authored distance and
|
||||||
|
* no MSAA. On Intel HD 400-class GPUs (Bay Trail/Braswell/Apollo Lake/Gemini
|
||||||
|
* Lake) and software renderers, lower those three to 100/100/1, but only where
|
||||||
|
* the value is still the port default, and only once per ini
|
||||||
|
* (Video.LowEndConsidered). Core-series iGPUs (HD 2000-6000, HD 5xx, UHD 6xx
|
||||||
|
* except 600/605) keep the full defaults; an HD 3000 holds 60 with them.
|
||||||
|
* The bare "Intel(R) HD Graphics" (no number) counts as low-end on purpose: it
|
||||||
|
* is what Bay Trail and Celeron/Pentium parts report; no Core-series part
|
||||||
|
* reports it, and a miss costs one reversible lightening. Don't tighten this
|
||||||
|
* without measurements.
|
||||||
|
* Must run after gfx_init (needs GL_RENDERER), so it cannot sit next to
|
||||||
|
* videoApplySteamDeckPreset in main.c, which runs before any GL context. The
|
||||||
|
* two touch disjoint keys (Deck: window/fullscreen; this: DD/LOD/MSAA).
|
||||||
|
* GE_FAKE_LOWEND=1/0 forces the classification for testing. */
|
||||||
|
static int videoIsLowEndRenderer(const char *r, const char **why)
|
||||||
|
{
|
||||||
|
static const char *const kTags[] = {
|
||||||
|
"(BYT)", "(BSW)", "(CHV)", "(APL)", "(GLK)",
|
||||||
|
"Bay Trail", "Braswell", "Cherryview", "Apollo Lake", "Gemini Lake",
|
||||||
|
"llvmpipe", "softpipe", "SVGA3D", "Microsoft Basic Render", "GDI Generic",
|
||||||
|
};
|
||||||
|
static const int kModels[] = { 400, 405, 500, 505, 600, 605 };
|
||||||
|
const char *p;
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)(sizeof(kTags) / sizeof(kTags[0])); i++) {
|
||||||
|
if (strstr(r, kTags[i])) { *why = kTags[i]; return 1; }
|
||||||
|
}
|
||||||
|
p = strstr(r, "HD Graphics");
|
||||||
|
if (p) {
|
||||||
|
int n = 0, digits = 0;
|
||||||
|
p += strlen("HD Graphics");
|
||||||
|
while (*p == ' ') p++;
|
||||||
|
while (*p >= '0' && *p <= '9') { n = n * 10 + (*p - '0'); p++; digits++; }
|
||||||
|
if (!digits) { *why = "numberless HD Graphics"; return 1; }
|
||||||
|
for (int i = 0; i < (int)(sizeof(kModels) / sizeof(kModels[0])); i++) {
|
||||||
|
if (n == kModels[i]) { *why = "HD Graphics 400-605 class"; return 1; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*why = "";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void videoApplyLowEndDefaults(void)
|
||||||
|
{
|
||||||
|
const char *r = gfx_opengl_renderer_string();
|
||||||
|
const char *why = "";
|
||||||
|
const char *fake = getenv("GE_FAKE_LOWEND");
|
||||||
|
int low;
|
||||||
|
|
||||||
|
if (cfgLowEndConsidered) return;
|
||||||
|
if (fake && *fake) {
|
||||||
|
low = atoi(fake) != 0;
|
||||||
|
why = "GE_FAKE_LOWEND";
|
||||||
|
} else {
|
||||||
|
low = videoIsLowEndRenderer(r, &why);
|
||||||
|
}
|
||||||
|
if (low) {
|
||||||
|
int changed = 0;
|
||||||
|
if (cfgDrawDistance == 250) { cfgDrawDistance = 100; changed++; }
|
||||||
|
if (cfgLodDistance == 250) { cfgLodDistance = 100; changed++; }
|
||||||
|
if (cfgMSAA == 2) { cfgMSAA = 1; gfx_msaa_level = 1; changed++; }
|
||||||
|
sysLogPrintf(LOG_INFO, "video: low-end renderer \"%s\" (%s); lowered %d default(s): DrawDistance=%d LodDistance=%d MSAA=%d",
|
||||||
|
r, why, changed, cfgDrawDistance, cfgLodDistance, cfgMSAA);
|
||||||
|
} else {
|
||||||
|
sysLogPrintf(LOG_INFO, "video: renderer \"%s\" (%s); not low-end, defaults kept",
|
||||||
|
r, why[0] ? why : "no match");
|
||||||
|
}
|
||||||
|
cfgLowEndConsidered = 1;
|
||||||
|
configSave();
|
||||||
|
}
|
||||||
|
|
||||||
/* D440: "Original N64" preset + its inverse ("Port defaults").
|
/* D440: "Original N64" preset + its inverse ("Port defaults").
|
||||||
* One row per config key whose port default differs from what the N64
|
* One row per config key whose port default differs from what the N64
|
||||||
* showed, plus the fidelity-identity keys a user may have moved away from
|
* showed, plus the fidelity-identity keys a user may have moved away from
|
||||||
@@ -860,6 +944,7 @@ int videoInit(void)
|
|||||||
};
|
};
|
||||||
|
|
||||||
gfx_init(&set);
|
gfx_init(&set);
|
||||||
|
videoApplyLowEndDefaults(); /* D482: needs GL_RENDERER; before the first frame */
|
||||||
|
|
||||||
/* VSync + optional fps cap; fast3d paces the window itself. */
|
/* VSync + optional fps cap; fast3d paces the window itself. */
|
||||||
wmAPI->set_swap_interval(cfgVSync ? 1 : 0);
|
wmAPI->set_swap_interval(cfgVSync ? 1 : 0);
|
||||||
@@ -1016,8 +1101,9 @@ void videoStartFrame(void)
|
|||||||
videoRenderPark();
|
videoRenderPark();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Rendering runs on the game's scheduler thread; the GL context was
|
/* Rendering runs on the D481 render worker (or the scheduler thread with
|
||||||
* created on the host main thread. */
|
* GE_RENDERINLINE/GE_DETERM); the GL context was created on the host
|
||||||
|
* main thread. */
|
||||||
gfx_sdl_make_context_current();
|
gfx_sdl_make_context_current();
|
||||||
|
|
||||||
int dirty = SDL_AtomicSet(&liveCfgDirty, 0);
|
int dirty = SDL_AtomicSet(&liveCfgDirty, 0);
|
||||||
|
|||||||
@@ -1,3 +1,99 @@
|
|||||||
|
1066992,192,assets/ge007.u.117880.jfont_dl.bin,0,0
|
||||||
|
1067184,46848,assets/ge007.e.1048B0.jfont_chardata.bin,0,0
|
||||||
|
1114032,6784,assets/ge007.u.123040.efont_chardata.bin,0,0
|
||||||
|
1120816,1482432,assets/animationtable_entries.bin,0,0
|
||||||
|
2603248,59360,assets/animationtable_data.bin,0,0
|
||||||
|
2662608,2760,assets/ge007.u.29D160.Globalimagetable.bin,0,0
|
||||||
|
2667728,26608,assets/rarewarelogo.bin,0,0
|
||||||
|
2694336,107904,assets/ge007.u.2A4D50.usedby7F008DE4.bin,0,1
|
||||||
|
2802240,8976,assets/ramrom/ramrom_Dam_1.bin,0,1
|
||||||
|
2811216,19888,assets/ramrom/ramrom_Dam_2.bin,0,1
|
||||||
|
2831104,12944,assets/ramrom/ramrom_Facility_1.bin,0,1
|
||||||
|
2844048,13696,assets/ramrom/ramrom_Facility_2.bin,0,1
|
||||||
|
2857744,13632,assets/ramrom/ramrom_Facility_3.bin,0,1
|
||||||
|
2871376,9968,assets/ramrom/ramrom_Runway_1.bin,0,1
|
||||||
|
2881344,27600,assets/ramrom/ramrom_Runway_2.bin,0,1
|
||||||
|
2908944,20688,assets/ramrom/ramrom_BunkerI_1.bin,0,1
|
||||||
|
2929632,17440,assets/ramrom/ramrom_BunkerI_2.bin,0,1
|
||||||
|
2947072,9472,assets/ramrom/ramrom_Silo_1.bin,0,1
|
||||||
|
2956544,13392,assets/ramrom/ramrom_Silo_2.bin,0,1
|
||||||
|
2969936,10816,assets/ramrom/ramrom_Frigate_1.bin,0,1
|
||||||
|
2980752,8224,assets/ramrom/ramrom_Frigate_2.bin,0,1
|
||||||
|
2988976,11392,assets/ramrom/ramrom_Train.bin,0,1
|
||||||
|
3000368,676,assets/font/fontBankGothic_kerning.bin,0,0
|
||||||
|
3001044,8716,assets/font/fontBankGothic_fontchartable.bin,0,0
|
||||||
|
3009760,676,assets/font/fontZurichBold_kerning.bin,0,0
|
||||||
|
3010436,12956,assets/font/fontZurichBold_fontchartable.bin,0,0
|
||||||
|
3023392,23488,assets/music/sfx.ctl,0,1
|
||||||
|
3046880,797360,assets/music/sfx.tbl,0,1
|
||||||
|
3844240,17312,assets/music/instruments.ctl,0,1
|
||||||
|
3861552,397216,assets/music/instruments.tbl,0,1
|
||||||
|
4258768,126667,assets/music/music.sbk,0,1
|
||||||
|
4258768,4,assets/music/number_music_samples,0,0
|
||||||
|
4258772,504,assets/music/table_music_data.bin,0,0
|
||||||
|
4259276,42,assets/music/Mno_music.bin,1,1
|
||||||
|
4259318,470,assets/music/Msolo_death_abrev.bin,1,1
|
||||||
|
4259788,2222,assets/music/Mintro_eye.bin,1,1
|
||||||
|
4262010,3050,assets/music/Mtrain.bin,1,1
|
||||||
|
4265060,3488,assets/music/Mdepot.bin,1,1
|
||||||
|
4268548,3480,assets/music/Mjungle_unused.bin,1,1
|
||||||
|
4272028,3520,assets/music/Mcitadel.bin,1,1
|
||||||
|
4275548,2766,assets/music/Mfacility.bin,1,1
|
||||||
|
4278314,2910,assets/music/Mcontrol.bin,1,1
|
||||||
|
4281224,3588,assets/music/Mdam.bin,1,1
|
||||||
|
4284812,3552,assets/music/Mfrigate.bin,1,1
|
||||||
|
4288364,2388,assets/music/Marchives.bin,1,1
|
||||||
|
4290752,3696,assets/music/Msilo.bin,1,1
|
||||||
|
4294448,3948,assets/music/Mjungle_perimeter_unused.bin,1,1
|
||||||
|
4298396,3330,assets/music/Mstreets.bin,1,1
|
||||||
|
4301726,1650,assets/music/Mbunker1.bin,1,1
|
||||||
|
4303376,1664,assets/music/Mbunker2.bin,1,1
|
||||||
|
4305040,2456,assets/music/Mstatue.bin,1,1
|
||||||
|
4307496,2522,assets/music/Melevator_control.bin,1,1
|
||||||
|
4310018,3424,assets/music/Mcradle.bin,1,1
|
||||||
|
4313442,42,assets/music/Mnull1.bin,1,1
|
||||||
|
4313484,1606,assets/music/Melevator_wc.bin,1,1
|
||||||
|
4315090,3482,assets/music/Megyptian.bin,1,1
|
||||||
|
4318572,994,assets/music/Mfolders.bin,1,1
|
||||||
|
4319566,498,assets/music/Mwatchmusic.bin,1,1
|
||||||
|
4320064,3186,assets/music/Maztec.bin,1,1
|
||||||
|
4323250,3628,assets/music/Mwatercaverns.bin,1,1
|
||||||
|
4326878,870,assets/music/Mdeathsolo.bin,1,1
|
||||||
|
4327748,3510,assets/music/Msurface2.bin,1,1
|
||||||
|
4331258,2208,assets/music/Mtrainx.bin,1,1
|
||||||
|
4333466,42,assets/music/Mnull2.bin,1,1
|
||||||
|
4333508,2392,assets/music/Mfacilityx.bin,1,1
|
||||||
|
4335900,1980,assets/music/Mdepotx.bin,1,1
|
||||||
|
4337880,1352,assets/music/Mcontrolx.bin,1,1
|
||||||
|
4339232,1876,assets/music/Mwatercavernsx.bin,1,1
|
||||||
|
4341108,1312,assets/music/Mdamx.bin,1,1
|
||||||
|
4342420,1474,assets/music/Mfrigatex.bin,1,1
|
||||||
|
4343894,1828,assets/music/Marchivesx.bin,1,1
|
||||||
|
4345722,2292,assets/music/Msilox.bin,1,1
|
||||||
|
4348014,42,assets/music/Mnull3.bin,1,1
|
||||||
|
4348056,1644,assets/music/Mstreetsx.bin,1,1
|
||||||
|
4349700,1958,assets/music/Mbunker1x.bin,1,1
|
||||||
|
4351658,1614,assets/music/Mbunker2x.bin,1,1
|
||||||
|
4353272,2070,assets/music/Mjunglex.bin,1,1
|
||||||
|
4355342,1074,assets/music/Mnint_rare_logo.bin,1,1
|
||||||
|
4356416,1720,assets/music/Mstatuex.bin,1,1
|
||||||
|
4358136,2262,assets/music/Maztecx.bin,1,1
|
||||||
|
4360398,2224,assets/music/Megyptianx.bin,1,1
|
||||||
|
4362622,1738,assets/music/Mcradlex.bin,1,1
|
||||||
|
4364360,2122,assets/music/Mcuba.bin,1,1
|
||||||
|
4366482,3358,assets/music/Mrunway.bin,1,1
|
||||||
|
4369840,730,assets/music/Mrunway_plane.bin,1,1
|
||||||
|
4370570,1832,assets/music/Msurface2x.bin,1,1
|
||||||
|
4372402,1314,assets/music/Mwindblowing.bin,1,1
|
||||||
|
4373716,524,assets/music/Mmultideath_alt.bin,1,1
|
||||||
|
4374240,1928,assets/music/Mjungle.bin,1,1
|
||||||
|
4376168,1570,assets/music/Mrunwayx.bin,1,1
|
||||||
|
4377738,3432,assets/music/Msurface1.bin,1,1
|
||||||
|
4381170,712,assets/music/Mmultiplayerdeath.bin,1,1
|
||||||
|
4381882,1832,assets/music/Msurface1x.bin,1,1
|
||||||
|
4383714,668,assets/music/Msurface2_ending.bin,1,1
|
||||||
|
4384382,358,assets/music/Mstatue_ending.bin,1,1
|
||||||
|
4384740,700,assets/music/Mfrigate_outro.bin,1,1
|
||||||
4385440,69104,assets/obseg/bg/bg_sev_all_p.bin,0,0
|
4385440,69104,assets/obseg/bg/bg_sev_all_p.bin,0,0
|
||||||
4454544,331584,assets/obseg/bg/bg_silo_all_p.bin,0,0
|
4454544,331584,assets/obseg/bg/bg_silo_all_p.bin,0,0
|
||||||
4786128,139472,assets/obseg/bg/bg_stat_all_p.bin,0,1
|
4786128,139472,assets/obseg/bg/bg_stat_all_p.bin,0,1
|
||||||
@@ -26,12 +122,12 @@
|
|||||||
7148064,0,assets/obseg/bg/bg_ash_all_p.bin,0,1
|
7148064,0,assets/obseg/bg/bg_ash_all_p.bin,0,1
|
||||||
7148064,0,assets/obseg/bg/bg_lue_all_p.bin,0,1
|
7148064,0,assets/obseg/bg/bg_lue_all_p.bin,0,1
|
||||||
7148064,40800,assets/obseg/bg/bg_ame_all_p.bin,0,1
|
7148064,40800,assets/obseg/bg/bg_ame_all_p.bin,0,1
|
||||||
7188864,0,assets/obseg/bg/bg_oat_all_p.bin,0,0
|
7188864,0,assets/obseg/bg/bg_rit_all_p.bin,0,1
|
||||||
7188864,28240,assets/obseg/bg/bg_rit_all_p.bin,0,1
|
7188864,28240,assets/obseg/bg/bg_oat_all_p.bin,0,0
|
||||||
7217104,0,assets/obseg/bg/bg_ear_all_p.bin,0,0
|
7217104,0,assets/obseg/bg/bg_ear_all_p.bin,0,0
|
||||||
7217104,0,assets/obseg/bg/bg_lee_all_p.bin,0,0
|
7217104,0,assets/obseg/bg/bg_lee_all_p.bin,0,0
|
||||||
7217104,0,assets/obseg/bg/bg_len_all_p.bin,0,0
|
7217104,0,assets/obseg/bg/bg_lip_all_p.bin,0,0
|
||||||
7217104,4000,assets/obseg/bg/bg_lip_all_p.bin,0,1
|
7217104,4000,assets/obseg/bg/bg_len_all_p.bin,0,1
|
||||||
7221104,0,assets/obseg/bg/bg_wax_all_p.bin,0,0
|
7221104,0,assets/obseg/bg/bg_wax_all_p.bin,0,0
|
||||||
7221104,0,assets/obseg/bg/bg_pam_all_p.bin,0,0
|
7221104,0,assets/obseg/bg/bg_pam_all_p.bin,0,0
|
||||||
7221104,9344,assets/obseg/chr/CarmourguardZ,1,1
|
7221104,9344,assets/obseg/chr/CarmourguardZ,1,1
|
||||||
@@ -57,10 +153,10 @@
|
|||||||
7403216,976,assets/obseg/chr/CheadbalaclavaZ,1,1
|
7403216,976,assets/obseg/chr/CheadbalaclavaZ,1,1
|
||||||
7404192,1696,assets/obseg/chr/CheadbikeZ,1,1
|
7404192,1696,assets/obseg/chr/CheadbikeZ,1,1
|
||||||
7405888,3408,assets/obseg/chr/CheadbrosnanZ,1,1
|
7405888,3408,assets/obseg/chr/CheadbrosnanZ,1,1
|
||||||
7409296,2976,assets/obseg/chr/Cheadbrosnanboiler,1,1
|
7409296,2976,assets/obseg/chr/CheadbrosnanboilerZ.bin,1,1
|
||||||
7412272,3712,assets/obseg/chr/Cheadbrosnansnow,1,1
|
7412272,3712,assets/obseg/chr/CheadbrosnansnowZ.bin,1,1
|
||||||
7415984,3456,assets/obseg/chr/Cheadbrosnansuit,1,1
|
7415984,3456,assets/obseg/chr/CheadbrosnansuitZ.bin,1,1
|
||||||
7419440,3008,assets/obseg/chr/Cheadbrosnantimber,1,1
|
7419440,3008,assets/obseg/chr/CheadbrosnantimberZ.bin,1,1
|
||||||
7422448,1344,assets/obseg/chr/CheadchrisZ,1,1
|
7422448,1344,assets/obseg/chr/CheadchrisZ,1,1
|
||||||
7423792,1408,assets/obseg/chr/CheaddaveZ,1,1
|
7423792,1408,assets/obseg/chr/CheaddaveZ,1,1
|
||||||
7425200,1328,assets/obseg/chr/CheaddesZ,1,1
|
7425200,1328,assets/obseg/chr/CheaddesZ,1,1
|
||||||
@@ -140,7 +236,7 @@
|
|||||||
7770944,848,assets/obseg/gun/GdoorexploderZ,1,1
|
7770944,848,assets/obseg/gun/GdoorexploderZ,1,1
|
||||||
7771792,864,assets/obseg/gun/GdossierredZ,1,1
|
7771792,864,assets/obseg/gun/GdossierredZ,1,1
|
||||||
7772656,848,assets/obseg/gun/GdynamiteZ,1,1
|
7772656,848,assets/obseg/gun/GdynamiteZ,1,1
|
||||||
7773504,592,assets/obseg/gun/Gexplosivefloppy,1,1
|
7773504,592,assets/obseg/gun/GexplosivefloppyZ.bin,1,1
|
||||||
7774096,848,assets/obseg/gun/GexplosivepenZ,1,1
|
7774096,848,assets/obseg/gun/GexplosivepenZ,1,1
|
||||||
7774944,2032,assets/obseg/gun/GextinguisherZ,1,1
|
7774944,2032,assets/obseg/gun/GextinguisherZ,1,1
|
||||||
7776976,848,assets/obseg/gun/GfingergunZ,1,1
|
7776976,848,assets/obseg/gun/GfingergunZ,1,1
|
||||||
@@ -156,7 +252,7 @@
|
|||||||
7808944,4224,assets/obseg/gun/GgrenadelaunchZ,1,1
|
7808944,4224,assets/obseg/gun/GgrenadelaunchZ,1,1
|
||||||
7813168,848,assets/obseg/gun/GheroinZ,1,1
|
7813168,848,assets/obseg/gun/GheroinZ,1,1
|
||||||
7814016,7856,assets/obseg/gun/GjoypadZ,1,1
|
7814016,7856,assets/obseg/gun/GjoypadZ,1,1
|
||||||
7821872,1936,assets/obseg/gun/Gkeyanalysercase,1,1
|
7821872,1936,assets/obseg/gun/GkeyanalysercaseZ.bin,1,1
|
||||||
7823808,2544,assets/obseg/gun/GkeyboltZ,1,1
|
7823808,2544,assets/obseg/gun/GkeyboltZ,1,1
|
||||||
7826352,304,assets/obseg/gun/GkeycardZ,1,1
|
7826352,304,assets/obseg/gun/GkeycardZ,1,1
|
||||||
7826656,3408,assets/obseg/gun/GkeyyaleZ,1,1
|
7826656,3408,assets/obseg/gun/GkeyyaleZ,1,1
|
||||||
@@ -180,7 +276,7 @@
|
|||||||
7861104,2496,assets/obseg/gun/GremotemineZ,1,1
|
7861104,2496,assets/obseg/gun/GremotemineZ,1,1
|
||||||
7863600,4640,assets/obseg/gun/GrocketlaunchZ,1,1
|
7863600,4640,assets/obseg/gun/GrocketlaunchZ,1,1
|
||||||
7868240,7568,assets/obseg/gun/GrugerZ,1,1
|
7868240,7568,assets/obseg/gun/GrugerZ,1,1
|
||||||
7875808,1936,assets/obseg/gun/Gsafecrackercase,1,1
|
7875808,1936,assets/obseg/gun/GsafecrackercaseZ.bin,1,1
|
||||||
7877744,3808,assets/obseg/gun/GshotgunZ,1,1
|
7877744,3808,assets/obseg/gun/GshotgunZ,1,1
|
||||||
7881552,6496,assets/obseg/gun/GsilverwppkZ,1,1
|
7881552,6496,assets/obseg/gun/GsilverwppkZ,1,1
|
||||||
7888048,4608,assets/obseg/gun/GskorpionZ,1,1
|
7888048,4608,assets/obseg/gun/GskorpionZ,1,1
|
||||||
@@ -236,12 +332,12 @@
|
|||||||
8038560,1088,assets/obseg/prop/Pboxes3x4Z,1,1
|
8038560,1088,assets/obseg/prop/Pboxes3x4Z,1,1
|
||||||
8039648,1632,assets/obseg/prop/Pboxes4x4Z,1,1
|
8039648,1632,assets/obseg/prop/Pboxes4x4Z,1,1
|
||||||
8041280,880,assets/obseg/prop/PbrakeunitZ,1,1
|
8041280,880,assets/obseg/prop/PbrakeunitZ,1,1
|
||||||
8042160,1408,assets/obseg/prop/Pbridge_console,1,1
|
8042160,1408,assets/obseg/prop/Pbridge_console1aZ.bin,1,1
|
||||||
8043568,1376,assets/obseg/prop/Pbridge_console,1,1
|
8043568,1376,assets/obseg/prop/Pbridge_console1bZ.bin,1,1
|
||||||
8044944,1408,assets/obseg/prop/Pbridge_console,1,1
|
8044944,1408,assets/obseg/prop/Pbridge_console2aZ.bin,1,1
|
||||||
8046352,1264,assets/obseg/prop/Pbridge_console,1,1
|
8046352,1264,assets/obseg/prop/Pbridge_console2bZ.bin,1,1
|
||||||
8047616,1360,assets/obseg/prop/Pbridge_console,1,1
|
8047616,1360,assets/obseg/prop/Pbridge_console3aZ.bin,1,1
|
||||||
8048976,1424,assets/obseg/prop/Pbridge_console,1,1
|
8048976,1424,assets/obseg/prop/Pbridge_console3bZ.bin,1,1
|
||||||
8050400,3296,assets/obseg/prop/PcarbmwZ,1,1
|
8050400,3296,assets/obseg/prop/PcarbmwZ,1,1
|
||||||
8053696,512,assets/obseg/prop/Pcard_box1Z,1,1
|
8053696,512,assets/obseg/prop/Pcard_box1Z,1,1
|
||||||
8054208,576,assets/obseg/prop/Pcard_box2Z,1,1
|
8054208,576,assets/obseg/prop/Pcard_box2Z,1,1
|
||||||
@@ -265,38 +361,38 @@
|
|||||||
8085280,368,assets/obseg/prop/PchrbugdetectorZ,1,1
|
8085280,368,assets/obseg/prop/PchrbugdetectorZ,1,1
|
||||||
8085648,368,assets/obseg/prop/PchrbungeeZ,1,1
|
8085648,368,assets/obseg/prop/PchrbungeeZ,1,1
|
||||||
8086016,1680,assets/obseg/prop/PchrcameraZ,1,1
|
8086016,1680,assets/obseg/prop/PchrcameraZ,1,1
|
||||||
8087696,416,assets/obseg/prop/Pchrcircuitboard,1,1
|
8087696,416,assets/obseg/prop/PchrcircuitboardZ.bin,1,1
|
||||||
8088112,640,assets/obseg/prop/PchrclipboardZ,1,1
|
8088112,640,assets/obseg/prop/PchrclipboardZ,1,1
|
||||||
8088752,368,assets/obseg/prop/PchrcreditcardZ,1,1
|
8088752,368,assets/obseg/prop/PchrcreditcardZ,1,1
|
||||||
8089120,368,assets/obseg/prop/PchrdarkglassesZ,1,1
|
8089120,368,assets/obseg/prop/PchrdarkglassesZ,1,1
|
||||||
8089488,416,assets/obseg/prop/PchrdatathiefZ,1,1
|
8089488,416,assets/obseg/prop/PchrdatathiefZ,1,1
|
||||||
8089904,496,assets/obseg/prop/PchrdattapeZ,1,1
|
8089904,496,assets/obseg/prop/PchrdattapeZ,1,1
|
||||||
8090400,2144,assets/obseg/prop/PchrdoordecoderZ,1,1
|
8090400,2144,assets/obseg/prop/PchrdoordecoderZ,1,1
|
||||||
8092544,368,assets/obseg/prop/Pchrdoorexploder,1,1
|
8092544,368,assets/obseg/prop/PchrdoorexploderZ.bin,1,1
|
||||||
8092912,1232,assets/obseg/prop/PchrdossierredZ,1,1
|
8092912,1232,assets/obseg/prop/PchrdossierredZ,1,1
|
||||||
8094144,368,assets/obseg/prop/PchrdynamiteZ,1,1
|
8094144,368,assets/obseg/prop/PchrdynamiteZ,1,1
|
||||||
8094512,368,assets/obseg/prop/Pchrexplosivepen,1,1
|
8094512,368,assets/obseg/prop/PchrexplosivepenZ.bin,1,1
|
||||||
8094880,1280,assets/obseg/prop/Pchrextinguisher,1,1
|
8094880,1280,assets/obseg/prop/PchrextinguisherZ.bin,1,1
|
||||||
8096160,368,assets/obseg/prop/PchrfingergunZ,1,1
|
8096160,368,assets/obseg/prop/PchrfingergunZ,1,1
|
||||||
8096528,368,assets/obseg/prop/PchrflarepistolZ,1,1
|
8096528,368,assets/obseg/prop/PchrflarepistolZ,1,1
|
||||||
8096896,1120,assets/obseg/prop/Pchrfnp90Z,1,1
|
8096896,1120,assets/obseg/prop/Pchrfnp90Z,1,1
|
||||||
8098016,3856,assets/obseg/prop/PchrgaskeyringZ,1,1
|
8098016,3856,assets/obseg/prop/PchrgaskeyringZ,1,1
|
||||||
8101872,368,assets/obseg/prop/PchrgoldbarZ,1,1
|
8101872,368,assets/obseg/prop/PchrgoldbarZ,1,1
|
||||||
8102240,624,assets/obseg/prop/PchrgoldenZ,1,1
|
8102240,624,assets/obseg/prop/PchrgoldenZ,1,1
|
||||||
8102864,3744,assets/obseg/prop/Pchrgoldeneyekey,1,1
|
8102864,3744,assets/obseg/prop/PchrgoldeneyekeyZ.bin,1,1
|
||||||
8106608,368,assets/obseg/prop/PchrgoldwppkZ,1,1
|
8106608,368,assets/obseg/prop/PchrgoldwppkZ,1,1
|
||||||
8106976,880,assets/obseg/prop/PchrgrenadeZ,1,1
|
8106976,880,assets/obseg/prop/PchrgrenadeZ,1,1
|
||||||
8107856,912,assets/obseg/prop/Pchrgrenadelauncher,1,1
|
8107856,912,assets/obseg/prop/PchrgrenadelaunchZ.bin,1,1
|
||||||
8108768,624,assets/obseg/prop/Pchrgrenaderound,1,1
|
8108768,624,assets/obseg/prop/PchrgrenaderoundZ.bin,1,1
|
||||||
8109392,368,assets/obseg/prop/PchrheroinZ,1,1
|
8109392,368,assets/obseg/prop/PchrheroinZ,1,1
|
||||||
8109760,1008,assets/obseg/prop/PchrkalashZ,1,1
|
8109760,1008,assets/obseg/prop/PchrkalashZ,1,1
|
||||||
8110768,496,assets/obseg/prop/Pchrkeyanalyser,1,1
|
8110768,496,assets/obseg/prop/PchrkeyanalysercaseZ.bin,1,1
|
||||||
8111264,3744,assets/obseg/prop/PchrkeyboltZ,1,1
|
8111264,3744,assets/obseg/prop/PchrkeyboltZ,1,1
|
||||||
8115008,5216,assets/obseg/prop/PchrkeyyaleZ,1,1
|
8115008,5216,assets/obseg/prop/PchrkeyyaleZ,1,1
|
||||||
8120224,512,assets/obseg/prop/PchrknifeZ,1,1
|
8120224,512,assets/obseg/prop/PchrknifeZ,1,1
|
||||||
8120736,960,assets/obseg/prop/PchrlaserZ,1,1
|
8120736,960,assets/obseg/prop/PchrlaserZ,1,1
|
||||||
8121696,368,assets/obseg/prop/PchrlectreZ,1,1
|
8121696,368,assets/obseg/prop/PchrlectreZ,1,1
|
||||||
8122064,368,assets/obseg/prop/Pchrlockexploder,1,1
|
8122064,368,assets/obseg/prop/PchrlockexploderZ.bin,1,1
|
||||||
8122432,976,assets/obseg/prop/Pchrm16Z,1,1
|
8122432,976,assets/obseg/prop/Pchrm16Z,1,1
|
||||||
8123408,336,assets/obseg/prop/PchrmapZ,1,1
|
8123408,336,assets/obseg/prop/PchrmapZ,1,1
|
||||||
8123744,2288,assets/obseg/prop/PchrmicrocameraZ,1,1
|
8123744,2288,assets/obseg/prop/PchrmicrocameraZ,1,1
|
||||||
@@ -308,13 +404,13 @@
|
|||||||
8129072,368,assets/obseg/prop/PchrpitongunZ,1,1
|
8129072,368,assets/obseg/prop/PchrpitongunZ,1,1
|
||||||
8129440,656,assets/obseg/prop/PchrplansZ,1,1
|
8129440,656,assets/obseg/prop/PchrplansZ,1,1
|
||||||
8130096,1120,assets/obseg/prop/PchrplastiqueZ,1,1
|
8130096,1120,assets/obseg/prop/PchrplastiqueZ,1,1
|
||||||
8131216,2240,assets/obseg/prop/Pchrpolarizedglasses,1,1
|
8131216,2240,assets/obseg/prop/PchrpolarizedglassesZ.bin,1,1
|
||||||
8133456,1120,assets/obseg/prop/Pchrproximitymine,1,1
|
8133456,1120,assets/obseg/prop/PchrproximitymineZ.bin,1,1
|
||||||
8134576,1120,assets/obseg/prop/PchrremotemineZ,1,1
|
8134576,1120,assets/obseg/prop/PchrremotemineZ,1,1
|
||||||
8135696,1456,assets/obseg/prop/PchrrocketZ,1,1
|
8135696,1456,assets/obseg/prop/PchrrocketZ,1,1
|
||||||
8137152,992,assets/obseg/prop/Pchrrocketlauncher,1,1
|
8137152,992,assets/obseg/prop/PchrrocketlaunchZ.bin,1,1
|
||||||
8138144,992,assets/obseg/prop/PchrrugerZ,1,1
|
8138144,992,assets/obseg/prop/PchrrugerZ,1,1
|
||||||
8139136,496,assets/obseg/prop/Pchrsafecracker,1,1
|
8139136,496,assets/obseg/prop/PchrsafecrackercaseZ.bin,1,1
|
||||||
8139632,848,assets/obseg/prop/PchrshotgunZ,1,1
|
8139632,848,assets/obseg/prop/PchrshotgunZ,1,1
|
||||||
8140480,368,assets/obseg/prop/PchrsilverwppkZ,1,1
|
8140480,368,assets/obseg/prop/PchrsilverwppkZ,1,1
|
||||||
8140848,896,assets/obseg/prop/PchrskorpionZ,1,1
|
8140848,896,assets/obseg/prop/PchrskorpionZ,1,1
|
||||||
@@ -340,8 +436,8 @@
|
|||||||
8157488,1216,assets/obseg/prop/Pconsole_sev2bZ,1,1
|
8157488,1216,assets/obseg/prop/Pconsole_sev2bZ,1,1
|
||||||
8158704,1088,assets/obseg/prop/Pconsole_sev2cZ,1,1
|
8158704,1088,assets/obseg/prop/Pconsole_sev2cZ,1,1
|
||||||
8159792,1072,assets/obseg/prop/Pconsole_sev2dZ,1,1
|
8159792,1072,assets/obseg/prop/Pconsole_sev2dZ,1,1
|
||||||
8160864,1072,assets/obseg/prop/Pconsole_sev_GE,1,1
|
8160864,1072,assets/obseg/prop/Pconsole_sev_GEaZ.bin,1,1
|
||||||
8161936,1072,assets/obseg/prop/Pconsole_sev_GE,1,1
|
8161936,1072,assets/obseg/prop/Pconsole_sev_GEbZ.bin,1,1
|
||||||
8163008,1152,assets/obseg/prop/Pconsole_sevaZ,1,1
|
8163008,1152,assets/obseg/prop/Pconsole_sevaZ,1,1
|
||||||
8164160,1136,assets/obseg/prop/Pconsole_sevbZ,1,1
|
8164160,1136,assets/obseg/prop/Pconsole_sevbZ,1,1
|
||||||
8165296,1072,assets/obseg/prop/Pconsole_sevcZ,1,1
|
8165296,1072,assets/obseg/prop/Pconsole_sevcZ,1,1
|
||||||
@@ -355,8 +451,8 @@
|
|||||||
8170048,640,assets/obseg/prop/PdamchaindoorZ,1,1
|
8170048,640,assets/obseg/prop/PdamchaindoorZ,1,1
|
||||||
8170688,544,assets/obseg/prop/PdamgatedoorZ,1,1
|
8170688,544,assets/obseg/prop/PdamgatedoorZ,1,1
|
||||||
8171232,880,assets/obseg/prop/PdamtundoorZ,1,1
|
8171232,880,assets/obseg/prop/PdamtundoorZ,1,1
|
||||||
8172112,416,assets/obseg/prop/Pdepot_door_ste,1,1
|
8172112,416,assets/obseg/prop/Pdepot_door_steelZ.bin,1,1
|
||||||
8172528,576,assets/obseg/prop/Pdepot_gate_ent,1,1
|
8172528,576,assets/obseg/prop/Pdepot_gate_entryZ.bin,1,1
|
||||||
8173104,384,assets/obseg/prop/Pdesk1Z,1,1
|
8173104,384,assets/obseg/prop/Pdesk1Z,1,1
|
||||||
8173488,384,assets/obseg/prop/Pdesk2Z,1,1
|
8173488,384,assets/obseg/prop/Pdesk2Z,1,1
|
||||||
8173872,576,assets/obseg/prop/Pdesk_arecibo1Z,1,1
|
8173872,576,assets/obseg/prop/Pdesk_arecibo1Z,1,1
|
||||||
@@ -370,7 +466,7 @@
|
|||||||
8191552,400,assets/obseg/prop/Pdisk_drive1Z,1,1
|
8191552,400,assets/obseg/prop/Pdisk_drive1Z,1,1
|
||||||
8191952,384,assets/obseg/prop/Pdoor_azt_chairZ,1,1
|
8191952,384,assets/obseg/prop/Pdoor_azt_chairZ,1,1
|
||||||
8192336,1088,assets/obseg/prop/Pdoor_azt_deskZ,1,1
|
8192336,1088,assets/obseg/prop/Pdoor_azt_deskZ,1,1
|
||||||
8193424,912,assets/obseg/prop/Pdoor_azt_desk_,1,1
|
8193424,912,assets/obseg/prop/Pdoor_azt_desk_topZ.bin,1,1
|
||||||
8194336,560,assets/obseg/prop/Pdoor_aztecZ,1,1
|
8194336,560,assets/obseg/prop/Pdoor_aztecZ,1,1
|
||||||
8194896,768,assets/obseg/prop/Pdoor_dest1Z,1,1
|
8194896,768,assets/obseg/prop/Pdoor_dest1Z,1,1
|
||||||
8195664,960,assets/obseg/prop/Pdoor_dest2Z,1,1
|
8195664,960,assets/obseg/prop/Pdoor_dest2Z,1,1
|
||||||
@@ -381,7 +477,7 @@
|
|||||||
8202272,576,assets/obseg/prop/Pdoor_roller2Z,1,1
|
8202272,576,assets/obseg/prop/Pdoor_roller2Z,1,1
|
||||||
8202848,576,assets/obseg/prop/Pdoor_roller3Z,1,1
|
8202848,576,assets/obseg/prop/Pdoor_roller3Z,1,1
|
||||||
8203424,608,assets/obseg/prop/Pdoor_roller4Z,1,1
|
8203424,608,assets/obseg/prop/Pdoor_roller4Z,1,1
|
||||||
8204032,304,assets/obseg/prop/Pdoor_rollertra,1,1
|
8204032,304,assets/obseg/prop/Pdoor_rollertrainZ.bin,1,1
|
||||||
8204336,608,assets/obseg/prop/Pdoor_st_arec1Z,1,1
|
8204336,608,assets/obseg/prop/Pdoor_st_arec1Z,1,1
|
||||||
8204944,736,assets/obseg/prop/Pdoor_st_arec2Z,1,1
|
8204944,736,assets/obseg/prop/Pdoor_st_arec2Z,1,1
|
||||||
8205680,416,assets/obseg/prop/Pdoor_winZ,1,1
|
8205680,416,assets/obseg/prop/Pdoor_winZ,1,1
|
||||||
@@ -390,19 +486,19 @@
|
|||||||
8208112,336,assets/obseg/prop/Pdoorprison1Z,1,1
|
8208112,336,assets/obseg/prop/Pdoorprison1Z,1,1
|
||||||
8208448,512,assets/obseg/prop/PdoorstatgateZ,1,1
|
8208448,512,assets/obseg/prop/PdoorstatgateZ,1,1
|
||||||
8208960,288,assets/obseg/prop/PexplosionbitZ,1,1
|
8208960,288,assets/obseg/prop/PexplosionbitZ,1,1
|
||||||
8209248,384,assets/obseg/prop/Pfiling_cabinet,1,1
|
8209248,384,assets/obseg/prop/Pfiling_cabinet1Z.bin,1,1
|
||||||
8209632,304,assets/obseg/prop/PflagZ,1,1
|
8209632,304,assets/obseg/prop/PflagZ,1,1
|
||||||
8209936,800,assets/obseg/prop/PfloppyZ,1,1
|
8209936,800,assets/obseg/prop/PfloppyZ,1,1
|
||||||
8210736,416,assets/obseg/prop/Pfnp90magZ,1,1
|
8210736,416,assets/obseg/prop/Pfnp90magZ,1,1
|
||||||
8211152,896,assets/obseg/prop/Pgas_plant_met1,1,1
|
8211152,896,assets/obseg/prop/Pgas_plant_met1_do1Z.bin,1,1
|
||||||
8212048,480,assets/obseg/prop/Pgas_plant_sw2_,1,1
|
8212048,480,assets/obseg/prop/Pgas_plant_sw2_do1Z.bin,1,1
|
||||||
8212528,512,assets/obseg/prop/Pgas_plant_sw3_,1,1
|
8212528,512,assets/obseg/prop/Pgas_plant_sw3_do1Z.bin,1,1
|
||||||
8213040,352,assets/obseg/prop/Pgas_plant_sw4_,1,1
|
8213040,352,assets/obseg/prop/Pgas_plant_sw4_do1Z.bin,1,1
|
||||||
8213392,656,assets/obseg/prop/Pgas_plant_sw_d,1,1
|
8213392,656,assets/obseg/prop/Pgas_plant_sw_do1Z.bin,1,1
|
||||||
8214048,528,assets/obseg/prop/Pgas_plant_wc_c,1,1
|
8214048,528,assets/obseg/prop/Pgas_plant_wc_cub1Z.bin,1,1
|
||||||
8214576,528,assets/obseg/prop/PgasbarrelZ,1,1
|
8214576,528,assets/obseg/prop/PgasbarrelZ,1,1
|
||||||
8215104,1344,assets/obseg/prop/PgasbarrelsZ,1,1
|
8215104,1344,assets/obseg/prop/PgasbarrelsZ,1,1
|
||||||
8216448,1376,assets/obseg/prop/Pgasplant_clear,1,1
|
8216448,1376,assets/obseg/prop/Pgasplant_clear_doorZ.bin,1,1
|
||||||
8217824,1456,assets/obseg/prop/PgastankZ,1,1
|
8217824,1456,assets/obseg/prop/PgastankZ,1,1
|
||||||
8219280,352,assets/obseg/prop/Pglassware1Z,1,1
|
8219280,352,assets/obseg/prop/Pglassware1Z,1,1
|
||||||
8219632,656,assets/obseg/prop/Pglassware2Z,1,1
|
8219632,656,assets/obseg/prop/Pglassware2Z,1,1
|
||||||
@@ -493,7 +589,7 @@
|
|||||||
8359552,816,assets/obseg/prop/Psev_door_v1Z,1,1
|
8359552,816,assets/obseg/prop/Psev_door_v1Z,1,1
|
||||||
8360368,944,assets/obseg/prop/Psev_trislideZ,1,1
|
8360368,944,assets/obseg/prop/Psev_trislideZ,1,1
|
||||||
8361312,3872,assets/obseg/prop/PsevdishZ,1,1
|
8361312,3872,assets/obseg/prop/PsevdishZ,1,1
|
||||||
8365184,736,assets/obseg/prop/Psevdoormetslid,1,1
|
8365184,736,assets/obseg/prop/PsevdoormetslideZ.bin,1,1
|
||||||
8365920,368,assets/obseg/prop/PsevdoornowindZ,1,1
|
8365920,368,assets/obseg/prop/PsevdoornowindZ,1,1
|
||||||
8366288,1072,assets/obseg/prop/PsevdoorwindZ,1,1
|
8366288,1072,assets/obseg/prop/PsevdoorwindZ,1,1
|
||||||
8367360,944,assets/obseg/prop/PsevdoorwoodZ,1,1
|
8367360,944,assets/obseg/prop/PsevdoorwoodZ,1,1
|
||||||
@@ -506,11 +602,11 @@
|
|||||||
8387248,352,assets/obseg/prop/PskorpionmagZ,1,1
|
8387248,352,assets/obseg/prop/PskorpionmagZ,1,1
|
||||||
8387600,368,assets/obseg/prop/PspectremagZ,1,1
|
8387600,368,assets/obseg/prop/PspectremagZ,1,1
|
||||||
8387968,3392,assets/obseg/prop/PspeedboatZ,1,1
|
8387968,3392,assets/obseg/prop/PspeedboatZ,1,1
|
||||||
8391360,12608,assets/obseg/prop/Pst_pete_room_1,1,1
|
8391360,12608,assets/obseg/prop/Pst_pete_room_1iZ.bin,1,1
|
||||||
8403968,12768,assets/obseg/prop/Pst_pete_room_2,1,1
|
8403968,12768,assets/obseg/prop/Pst_pete_room_2iZ.bin,1,1
|
||||||
8416736,12096,assets/obseg/prop/Pst_pete_room_3,1,1
|
8416736,12096,assets/obseg/prop/Pst_pete_room_3tZ.bin,1,1
|
||||||
8428832,13712,assets/obseg/prop/Pst_pete_room_5,1,1
|
8428832,13712,assets/obseg/prop/Pst_pete_room_5cZ.bin,1,1
|
||||||
8442544,13328,assets/obseg/prop/Pst_pete_room_6,1,1
|
8442544,13328,assets/obseg/prop/Pst_pete_room_6cZ.bin,1,1
|
||||||
8455872,624,assets/obseg/prop/Psteel_door1Z,1,1
|
8455872,624,assets/obseg/prop/Psteel_door1Z,1,1
|
||||||
8456496,688,assets/obseg/prop/Psteel_door2Z,1,1
|
8456496,688,assets/obseg/prop/Psteel_door2Z,1,1
|
||||||
8457184,720,assets/obseg/prop/Psteel_door2bZ,1,1
|
8457184,720,assets/obseg/prop/Psteel_door2bZ,1,1
|
||||||
@@ -526,7 +622,7 @@
|
|||||||
8479232,624,assets/obseg/prop/Ptrain_doorZ,1,1
|
8479232,624,assets/obseg/prop/Ptrain_doorZ,1,1
|
||||||
8479856,832,assets/obseg/prop/PtrainextdoorZ,1,1
|
8479856,832,assets/obseg/prop/PtrainextdoorZ,1,1
|
||||||
8480688,320,assets/obseg/prop/Ptt33magZ,1,1
|
8480688,320,assets/obseg/prop/Ptt33magZ,1,1
|
||||||
8481008,1312,assets/obseg/prop/Ptuning_console,1,1
|
8481008,1312,assets/obseg/prop/Ptuning_console1Z.bin,1,1
|
||||||
8482320,464,assets/obseg/prop/Ptv1Z,1,1
|
8482320,464,assets/obseg/prop/Ptv1Z,1,1
|
||||||
8482784,416,assets/obseg/prop/Ptv4screenZ,1,1
|
8482784,416,assets/obseg/prop/Ptv4screenZ,1,1
|
||||||
8483200,1744,assets/obseg/prop/Ptv_holderZ,1,1
|
8483200,1744,assets/obseg/prop/Ptv_holderZ,1,1
|
||||||
@@ -779,4 +875,5 @@
|
|||||||
9399728,1376,LwaxE,1,1
|
9399728,1376,LwaxE,1,1
|
||||||
9401104,1296,LwaxJ,1,1
|
9401104,1296,LwaxJ,1,1
|
||||||
9402400,16,LwaxP,1,1
|
9402400,16,LwaxP,1,1
|
||||||
|
9402416,16,assets/obseg/ob__ob_end.seg,0,1
|
||||||
|
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
3014208,13536,assets/ramrom/ramrom_Frigate_2.bin,0,1
|
3014208,13536,assets/ramrom/ramrom_Frigate_2.bin,0,1
|
||||||
3027744,15856,assets/ramrom/ramrom_Train.bin,0,1
|
3027744,15856,assets/ramrom/ramrom_Train.bin,0,1
|
||||||
3043600,676,assets/font/fontBankGothic_kerning.bin,0,0
|
3043600,676,assets/font/fontBankGothic_kerning.bin,0,0
|
||||||
3044276,8716,assets/font/fontZurichBold_fontchartable.bin,0,0
|
3044276,8716,assets/font/fontBankGothic_fontchartable.bin,0,0
|
||||||
3052992,676,assets/font/fontZurichBold_kerning.bin,0,0
|
3052992,676,assets/font/fontZurichBold_kerning.bin,0,0
|
||||||
3053668,12956,assets/font/fontZurichBold_fontchartable.bin,0,0
|
3053668,12956,assets/font/fontZurichBold_fontchartable.bin,0,0
|
||||||
3066624,23488,assets/music/sfx.ctl,0,1
|
3066624,23488,assets/music/sfx.ctl,0,1
|
||||||
|
|||||||
|
@@ -146,9 +146,6 @@ struct memallocstring memallocstringtable[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
s32 g_MainStageNum = LEVELID_NONE;
|
s32 g_MainStageNum = LEVELID_NONE;
|
||||||
#ifdef PORT
|
|
||||||
static s32 g_d235ReloadStage = -1, g_d235ReloadWait = 0; /* TEMP D235 verification */
|
|
||||||
#endif
|
|
||||||
s32 g_BossIsDebugMenuOpen = FALSE;
|
s32 g_BossIsDebugMenuOpen = FALSE;
|
||||||
|
|
||||||
OSScMsg g_bossGfxDoneMsg = { OS_SC_DONE_MSG };
|
OSScMsg g_bossGfxDoneMsg = { OS_SC_DONE_MSG };
|
||||||
@@ -524,35 +521,7 @@ void bossMainloop(void)
|
|||||||
if (ge_d235)
|
if (ge_d235)
|
||||||
osSyncPrintf("D235: stage=%d texcache entries purged at load=%d\n",
|
osSyncPrintf("D235: stage=%d texcache entries purged at load=%d\n",
|
||||||
(s32)g_StageNum, gfx_texture_cache_count());
|
(s32)g_StageNum, gfx_texture_cache_count());
|
||||||
if (!getenv("GE_D235_OFF")) /* TEMP: A/B switch for verification */
|
videoResetTextureCache();
|
||||||
videoResetTextureCache();
|
|
||||||
|
|
||||||
/* TEMP (D235 verification, remove with the probe):
|
|
||||||
* GE_D235_RELOAD=<frames> re-enters the first level once, <frames>
|
|
||||||
* retraces after the title stage loads following it (e.g. after a
|
|
||||||
* watch Abort) -- so the front end has rendered its own textures
|
|
||||||
* into the reset stage arena first, like a real death/restart --
|
|
||||||
* via the same bossSetLoadedStage() call the front end's mission
|
|
||||||
* start makes (front.c). Inert when unset. */
|
|
||||||
{
|
|
||||||
static int s_reload = -2, s_firstLevel = -1;
|
|
||||||
if (s_reload == -2) {
|
|
||||||
const char *e = getenv("GE_D235_RELOAD");
|
|
||||||
s_reload = e ? atoi(e) : -1;
|
|
||||||
}
|
|
||||||
if (s_reload >= 0) {
|
|
||||||
if (g_StageNum != LEVELID_TITLE && s_firstLevel < 0)
|
|
||||||
s_firstLevel = g_StageNum;
|
|
||||||
else if (g_StageNum == LEVELID_TITLE && s_firstLevel >= 0 &&
|
|
||||||
g_d235ReloadStage < 0 && g_d235ReloadWait == 0) {
|
|
||||||
/* GE_D235_NEXT=<stage id>: load a DIFFERENT stage
|
|
||||||
* instead (level A -> title -> level B leaks). */
|
|
||||||
const char *nx = getenv("GE_D235_NEXT");
|
|
||||||
g_d235ReloadStage = (nx && *nx) ? (s32)strtol(nx, NULL, 0) : s_firstLevel;
|
|
||||||
g_d235ReloadWait = s_reload + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -630,13 +599,6 @@ void bossMainloop(void)
|
|||||||
{
|
{
|
||||||
if (g_MainStageNum < 0 && pendingGfx < 2U)
|
if (g_MainStageNum < 0 && pendingGfx < 2U)
|
||||||
{
|
{
|
||||||
#ifdef PORT
|
|
||||||
/* TEMP D235 verification (see GE_D235_RELOAD above) */
|
|
||||||
if (g_d235ReloadWait > 0 && --g_d235ReloadWait == 0) {
|
|
||||||
osSyncPrintf("D235: RELOAD re-entering stage %d\n", g_d235ReloadStage);
|
|
||||||
bossSetLoadedStage(g_d235ReloadStage);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (get_is_ramrom_flag())
|
if (get_is_ramrom_flag())
|
||||||
{
|
{
|
||||||
iterate_ramrom_entries_handle_camera_out();
|
iterate_ramrom_entries_handle_camera_out();
|
||||||
@@ -671,7 +633,7 @@ void bossMainloop(void)
|
|||||||
joyButtons = joyGetButtons(0, ANY_BUTTON);
|
joyButtons = joyGetButtons(0, ANY_BUTTON);
|
||||||
g_BossIsDebugMenuOpen = debug_menu_processor(joyStickXPos, joyStickYPos, joyButtons, joyGetButtonsPressedThisFrame(0, ANY_BUTTON));
|
g_BossIsDebugMenuOpen = debug_menu_processor(joyStickXPos, joyStickYPos, joyButtons, joyGetButtonsPressedThisFrame(0, ANY_BUTTON));
|
||||||
}
|
}
|
||||||
#elif defined(PORT)
|
#elif defined(PORT) && defined(LEFTOVERDEBUG) /* the debug-menu globals only exist with LEFTOVERDEBUG (not PAL) */
|
||||||
// GE_DEBUGMENU=1 -- diagnostic-only: enable leftover debug-menu open
|
// GE_DEBUGMENU=1 -- diagnostic-only: enable leftover debug-menu open
|
||||||
// path (D243). Unset (default) reproduces the #ifndef DEBUGMENU
|
// path (D243). Unset (default) reproduces the #ifndef DEBUGMENU
|
||||||
// behavior below exactly -- no behavior change.
|
// behavior below exactly -- no behavior change.
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
#ifdef PORT
|
||||||
|
#include "porttick.h" /* D486 */
|
||||||
|
#endif
|
||||||
#include <ultra64.h>
|
#include <ultra64.h>
|
||||||
#include <PR/os.h>
|
#include <PR/os.h>
|
||||||
#include <PR/gbi.h>
|
#include <PR/gbi.h>
|
||||||
@@ -434,6 +437,22 @@ void explosionScreenShake(coord3d* source_pos, coord3d* source_mag, coord3d* res
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef PORT
|
||||||
|
/* D486 (RULE-2): the shake counters decayed once per frame; decay at the N64's 2-ticks-per-frame pace */
|
||||||
|
if (g_NumSmokeEntries > 0)
|
||||||
|
{
|
||||||
|
if (portN64FrameStep())
|
||||||
|
{
|
||||||
|
g_NumSmokeEntries--;
|
||||||
|
}
|
||||||
|
explosion_mag++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (portN64FrameStep())
|
||||||
|
{
|
||||||
|
g_NumExplosionEntries--;
|
||||||
|
}
|
||||||
|
#else
|
||||||
if (g_NumSmokeEntries > 0)
|
if (g_NumSmokeEntries > 0)
|
||||||
{
|
{
|
||||||
g_NumSmokeEntries--;
|
g_NumSmokeEntries--;
|
||||||
@@ -441,6 +460,7 @@ void explosionScreenShake(coord3d* source_pos, coord3d* source_mag, coord3d* res
|
|||||||
}
|
}
|
||||||
|
|
||||||
g_NumExplosionEntries--;
|
g_NumExplosionEntries--;
|
||||||
|
#endif
|
||||||
if (g_NumExplosionEntries & 2)
|
if (g_NumExplosionEntries & 2)
|
||||||
{
|
{
|
||||||
result->y = explosion_mag;
|
result->y = explosion_mag;
|
||||||
@@ -721,6 +741,13 @@ s32 explosionTick(PropRecord* arg0)
|
|||||||
}
|
}
|
||||||
|
|
||||||
sp9C = (s32) (((f32)explosiontype->propagationrate * (f32)exp->age) / (f32)explosiontype->duration) + 1;
|
sp9C = (s32) (((f32)explosiontype->propagationrate * (f32)exp->age) / (f32)explosiontype->duration) + 1;
|
||||||
|
#ifdef PORT
|
||||||
|
/* D486 (RULE-2): parts spawned once per frame; spawn at the N64's 2-ticks-per-frame pace */
|
||||||
|
if (!portN64FrameStep())
|
||||||
|
{
|
||||||
|
sp9C = 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
for (var_s4 = 0; var_s4 < sp9C; var_s4++)
|
for (var_s4 = 0; var_s4 < sp9C; var_s4++)
|
||||||
{
|
{
|
||||||
for (j=0; j<EXPLOSION_PARTS_LEN; j++)
|
for (j=0; j<EXPLOSION_PARTS_LEN; j++)
|
||||||
|
|||||||
@@ -1278,10 +1278,37 @@ f32 get_item_in_hand_zoom(void) {
|
|||||||
return get_ptr_item_statistics(get_item_in_hand_or_watch_menu(GUNRIGHT))->Zoom;
|
return get_ptr_item_statistics(get_item_in_hand_or_watch_menu(GUNRIGHT))->Zoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef PORT
|
||||||
|
/* D485 RULE-2-SIGNOFF (maintainer, 2026-10-02: "approve the rule 2 change to
|
||||||
|
* match the N64 real time speed"). The sniper/camera zoom steps once per game
|
||||||
|
* FRAME with no tick scaling. The N64 ran these frames at ~2 ticks each
|
||||||
|
* (30 fps; 20 fps = 3), the port at 1 tick per frame, so the port zoomed 2x
|
||||||
|
* faster in real time. Scale the per-frame factor to base^(ticks/2): one
|
||||||
|
* N64 frame at 2 ticks gets exactly the original factor, and the port's
|
||||||
|
* 1-tick frame gets its square root. Same "N64 frame = 2 ticks" convention as
|
||||||
|
* the D427 fire gate. g_ClockTimer == 0 (paused/locked) means no step. */
|
||||||
|
float sqrtf(float);
|
||||||
|
static f32 gunZoomStepFactor(f32 zoom)
|
||||||
|
{
|
||||||
|
f32 half = sqrtf(1.0f + (zoom * 0.1f));
|
||||||
|
f32 factor = 1.0f;
|
||||||
|
s32 i;
|
||||||
|
|
||||||
|
for (i = 0; i < g_ClockTimer; i++) {
|
||||||
|
factor *= half;
|
||||||
|
}
|
||||||
|
|
||||||
|
return factor;
|
||||||
|
}
|
||||||
|
#define GUN_ZOOM_STEP(zoom) gunZoomStepFactor(zoom)
|
||||||
|
#else
|
||||||
|
#define GUN_ZOOM_STEP(zoom) (1.0f + ((zoom) * 0.1f))
|
||||||
|
#endif
|
||||||
|
|
||||||
void camera_sniper_zoom_out(f32 zoom)
|
void camera_sniper_zoom_out(f32 zoom)
|
||||||
{
|
{
|
||||||
if (get_item_in_hand_or_watch_menu(GUNRIGHT) == ITEM_SNIPERRIFLE) {
|
if (get_item_in_hand_or_watch_menu(GUNRIGHT) == ITEM_SNIPERRIFLE) {
|
||||||
g_CurrentPlayer->sniper_zoom *= (1.0f + (zoom * 0.1f));
|
g_CurrentPlayer->sniper_zoom *= GUN_ZOOM_STEP(zoom);
|
||||||
if (g_CurrentPlayer->sniper_zoom > 60.0f) {
|
if (g_CurrentPlayer->sniper_zoom > 60.0f) {
|
||||||
g_CurrentPlayer->sniper_zoom = 60.0f;
|
g_CurrentPlayer->sniper_zoom = 60.0f;
|
||||||
}
|
}
|
||||||
@@ -1289,7 +1316,7 @@ void camera_sniper_zoom_out(f32 zoom)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (get_item_in_hand_or_watch_menu(GUNRIGHT) == ITEM_CAMERA) {
|
if (get_item_in_hand_or_watch_menu(GUNRIGHT) == ITEM_CAMERA) {
|
||||||
g_CurrentPlayer->camera_zoom *= (1.0f + (zoom * 0.1f));
|
g_CurrentPlayer->camera_zoom *= GUN_ZOOM_STEP(zoom);
|
||||||
if (g_CurrentPlayer->camera_zoom > 60.0f) {
|
if (g_CurrentPlayer->camera_zoom > 60.0f) {
|
||||||
g_CurrentPlayer->camera_zoom = 60.0f;
|
g_CurrentPlayer->camera_zoom = 60.0f;
|
||||||
}
|
}
|
||||||
@@ -1300,7 +1327,7 @@ void camera_sniper_zoom_out(f32 zoom)
|
|||||||
void camera_sniper_zoom_in(f32 zoom)
|
void camera_sniper_zoom_in(f32 zoom)
|
||||||
{
|
{
|
||||||
if (get_item_in_hand_or_watch_menu(GUNRIGHT) == ITEM_SNIPERRIFLE) {
|
if (get_item_in_hand_or_watch_menu(GUNRIGHT) == ITEM_SNIPERRIFLE) {
|
||||||
g_CurrentPlayer->sniper_zoom /= (1.0f + (zoom * 0.1f));
|
g_CurrentPlayer->sniper_zoom /= GUN_ZOOM_STEP(zoom);
|
||||||
if (g_CurrentPlayer->sniper_zoom < 7.0f) {
|
if (g_CurrentPlayer->sniper_zoom < 7.0f) {
|
||||||
g_CurrentPlayer->sniper_zoom = 7.0f;
|
g_CurrentPlayer->sniper_zoom = 7.0f;
|
||||||
}
|
}
|
||||||
@@ -1308,7 +1335,7 @@ void camera_sniper_zoom_in(f32 zoom)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (get_item_in_hand_or_watch_menu(GUNRIGHT) == ITEM_CAMERA) {
|
if (get_item_in_hand_or_watch_menu(GUNRIGHT) == ITEM_CAMERA) {
|
||||||
g_CurrentPlayer->camera_zoom /= (1.0f + (zoom * 0.1f));
|
g_CurrentPlayer->camera_zoom /= GUN_ZOOM_STEP(zoom);
|
||||||
if (g_CurrentPlayer->camera_zoom < 7.0f) {
|
if (g_CurrentPlayer->camera_zoom < 7.0f) {
|
||||||
g_CurrentPlayer->camera_zoom = 7.0f;
|
g_CurrentPlayer->camera_zoom = 7.0f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -407,11 +407,18 @@ void lvlStageLoad(s32 stage)
|
|||||||
extern struct mp_stage_setup multi_stage_setups[];
|
extern struct mp_stage_setup multi_stage_setups[];
|
||||||
extern void init_mp_options_for_scenario(s32 numplayers);
|
extern void init_mp_options_for_scenario(s32 numplayers);
|
||||||
extern s32 selected_num_players;
|
extern s32 selected_num_players;
|
||||||
|
extern s32 selected_folder_num;
|
||||||
s32 np = (s32)strtol(smp, NULL, 0);
|
s32 np = (s32)strtol(smp, NULL, 0);
|
||||||
const char *comma = strchr(smp, ',');
|
const char *comma = strchr(smp, ',');
|
||||||
if (np < 2) np = 2;
|
if (np < 2) np = 2;
|
||||||
if (np > 4) np = 4;
|
if (np > 4) np = 4;
|
||||||
gamemode = GAMEMODE_MULTI;
|
gamemode = GAMEMODE_MULTI;
|
||||||
|
/* D479: the file-select screen a real MP launch passes through
|
||||||
|
* sets selected_folder_num = -1 (front.c), so the stage load's
|
||||||
|
* fileLoadSettingsForFolder applies no Bond file's options.
|
||||||
|
* Without this the harness applied a blank folder-1 record
|
||||||
|
* (options 0: ammo counter, sight and auto-aim off). */
|
||||||
|
selected_folder_num = -1;
|
||||||
init_mp_options_for_scenario(np);
|
init_mp_options_for_scenario(np);
|
||||||
if (comma) MP_stage_selected = (s32)strtol(comma + 1, NULL, 0);
|
if (comma) MP_stage_selected = (s32)strtol(comma + 1, NULL, 0);
|
||||||
selected_stage = multi_stage_setups[MP_stage_selected].stage_id;
|
selected_stage = multi_stage_setups[MP_stage_selected].stage_id;
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
|
|
||||||
---------------------------------------------------------------------*/
|
---------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#ifdef PORT
|
||||||
|
#include "porttick.h" /* D486 */
|
||||||
|
#endif
|
||||||
#include <ultra64.h>
|
#include <ultra64.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#ifdef PORT
|
#ifdef PORT
|
||||||
@@ -6338,6 +6341,11 @@ s32 objTick(struct PropRecord *prop)
|
|||||||
|
|
||||||
if ((autogun->is_active != 0) && (!(obj->flags & PROPFLAG_IS_DRONE_GUN)))
|
if ((autogun->is_active != 0) && (!(obj->flags & PROPFLAG_IS_DRONE_GUN)))
|
||||||
{
|
{
|
||||||
|
#ifdef PORT
|
||||||
|
/* D486 (RULE-2): the shot counter advanced per frame; step it at the N64's 2-ticks-per-frame pace */
|
||||||
|
if (portN64FrameStep())
|
||||||
|
{
|
||||||
|
#endif
|
||||||
autogun->unkAC = autogun->unkAC + 1;
|
autogun->unkAC = autogun->unkAC + 1;
|
||||||
sp13C = (autogun->unkAC & 1) == 0;
|
sp13C = (autogun->unkAC & 1) == 0;
|
||||||
|
|
||||||
@@ -6345,6 +6353,9 @@ s32 objTick(struct PropRecord *prop)
|
|||||||
{
|
{
|
||||||
sp138 = (autogun->unkAC & 1) == 1;
|
sp138 = (autogun->unkAC & 1) == 1;
|
||||||
}
|
}
|
||||||
|
#ifdef PORT
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (autogun->unkC0 < g_GlobalTimer)
|
if (autogun->unkC0 < g_GlobalTimer)
|
||||||
{
|
{
|
||||||
@@ -6439,7 +6450,12 @@ s32 objTick(struct PropRecord *prop)
|
|||||||
if ((temp_f20_4 <= (((beam_xdiff * beam_xdiff) + (beam_ydiff * beam_ydiff)) + (beam_collisionTile * beam_collisionTile))) && (bondviewGetIfCurrentPlayerDamageShowTime() == 0))
|
if ((temp_f20_4 <= (((beam_xdiff * beam_xdiff) + (beam_ydiff * beam_ydiff)) + (beam_collisionTile * beam_collisionTile))) && (bondviewGetIfCurrentPlayerDamageShowTime() == 0))
|
||||||
{
|
{
|
||||||
temp_f0_35 = sqrtf(temp_f20_4);
|
temp_f0_35 = sqrtf(temp_f20_4);
|
||||||
|
#ifdef PORT
|
||||||
|
/* D486: one shot now spans a 2-tick N64 frame below 2 ticks/frame, so charge that frame's delta (damage per second unchanged) */
|
||||||
|
var_f2_7 = (0.16f * (g_ClockTimer >= 2 ? OBJECT_INTERACTION_TIMER_DELTA : 2.0f)) * g_AutogunPendingDamageTick;
|
||||||
|
#else
|
||||||
var_f2_7 = (0.16f * OBJECT_INTERACTION_TIMER_DELTA) * g_AutogunPendingDamageTick;
|
var_f2_7 = (0.16f * OBJECT_INTERACTION_TIMER_DELTA) * g_AutogunPendingDamageTick;
|
||||||
|
#endif
|
||||||
if (temp_f0_35 > 200.0f)
|
if (temp_f0_35 > 200.0f)
|
||||||
{
|
{
|
||||||
var_f2_7 *= 200.0f / temp_f0_35;
|
var_f2_7 *= 200.0f / temp_f0_35;
|
||||||
|
|||||||
@@ -31,18 +31,8 @@
|
|||||||
/* D227 (M-95): forward decls -- the two fan-drawing blocks that call these
|
/* D227 (M-95): forward decls -- the two fan-drawing blocks that call these
|
||||||
* (further down this file) come before the static definitions themselves.
|
* (further down this file) come before the static definitions themselves.
|
||||||
* See the comment above skyPortBeginFan()'s definition. */
|
* See the comment above skyPortBeginFan()'s definition. */
|
||||||
static void skyPortBeginFan(SkyRelated38 *v, s32 n, bool allowShift);
|
static void skyPortBeginFan(SkyRelated38 *v, s32 n);
|
||||||
static void skyPortEndFan(void);
|
static void skyPortEndFan(void);
|
||||||
/* D245 (M-142): same reason -- the water call site (further down) needs to
|
|
||||||
* override the adaptive shift picked by skyPortBeginFan() before the
|
|
||||||
* definition itself. */
|
|
||||||
static void skyPortForceFanShift(s32 kS, s32 kT);
|
|
||||||
/* D227 (M-100): same reason -- the texSelect call sites precede the definition. */
|
|
||||||
static void skyPortCaptureTile(Gfx *start, Gfx *end);
|
|
||||||
/* D227 (M-100): relocated up here (was just above skyPortPickShift, further
|
|
||||||
* down) so the D245 water call site's GE_D245_FIXEDSHIFT clamp can use it --
|
|
||||||
* same forward-declare reason as the functions above. */
|
|
||||||
#define SKY_TC_MAX_SHIFT 5
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// bss
|
// bss
|
||||||
@@ -921,67 +911,14 @@ Gfx *skyRender(Gfx *gdl)
|
|||||||
{
|
{
|
||||||
gDPPipeSync(gdl++);
|
gDPPipeSync(gdl++);
|
||||||
|
|
||||||
#ifdef PORT
|
|
||||||
/* D227 (M-100): remember the tiles this draw sets up so
|
|
||||||
* skyPortEmitTileShift() can replay them with a shift. Captured
|
|
||||||
* AFTER sub_GAME_7F09343C, which re-declares both tiles -- see the
|
|
||||||
* comment above skyPortCaptureTile(). */
|
|
||||||
{
|
|
||||||
Gfx *skyTileFrom = gdl;
|
|
||||||
texSelect(&gdl, &skywaterimages[fogGetCurrentEnvironmentp()->WaterImageId], 1, 0, 2);
|
|
||||||
gdl = sub_GAME_7F09343C(gdl, 0); // ???
|
|
||||||
skyPortCaptureTile(skyTileFrom, gdl);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
texSelect(&gdl, &skywaterimages[fogGetCurrentEnvironmentp()->WaterImageId], 1, 0, 2);
|
texSelect(&gdl, &skywaterimages[fogGetCurrentEnvironmentp()->WaterImageId], 1, 0, 2);
|
||||||
gdl = sub_GAME_7F09343C(gdl, 0); // ???
|
gdl = sub_GAME_7F09343C(gdl, 0); // ???
|
||||||
#endif
|
|
||||||
gDPSetRenderMode(gdl++, G_RM_OPA_SURF, G_RM_OPA_SURF2);
|
gDPSetRenderMode(gdl++, G_RM_OPA_SURF, G_RM_OPA_SURF2);
|
||||||
|
|
||||||
#ifdef PORT
|
#ifdef PORT
|
||||||
/* D227 (M-95): one shared wScale for every triangle drawn from
|
/* D227 (M-95): one shared wScale for every triangle drawn from
|
||||||
* this sp274[] fan -- see skyPortBeginFan() above. D245: allow
|
* this sp274[] fan -- see skyPortBeginFan() above. */
|
||||||
* the tc-shift safety valve on the water quad too -- see the
|
skyPortBeginFan(sp274, s1);
|
||||||
* comment above skyPortBeginFan() for why this doesn't flatten
|
|
||||||
* the cross-fade. */
|
|
||||||
skyPortBeginFan(sp274, s1, TRUE); /* water: see skyPortBeginFan */
|
|
||||||
|
|
||||||
/* D245 (M-142): opt-in A/B candidate -- see skyPortForceFanShift's
|
|
||||||
* comment. Off by default (unset env => identical to the current
|
|
||||||
* allowShift=TRUE behaviour this row is still reopened against). */
|
|
||||||
{
|
|
||||||
const char *fs = GE_ENVSTR("GE_D245_FIXEDSHIFT");
|
|
||||||
if (fs) {
|
|
||||||
s32 kFixed = atoi(fs);
|
|
||||||
if (kFixed < 0) kFixed = 0;
|
|
||||||
if (kFixed > SKY_TC_MAX_SHIFT) kFixed = SKY_TC_MAX_SHIFT;
|
|
||||||
skyPortForceFanShift(kFixed, kFixed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* D245 (M-142, reopened): dumps the water quad's raw S/T span
|
|
||||||
* every call, so a scripted multi-frame capture can show whether
|
|
||||||
* the span crosses skyPortPickShift's ~30719-unit threshold (and
|
|
||||||
* therefore the tc-shift k) as the camera rotates -- the
|
|
||||||
* still-open question after allowShift=TRUE didn't fix the live
|
|
||||||
* symptom. Recomputed locally rather than reading skyPortBeginFan's
|
|
||||||
* statics (defined later in this TU, not forward-declared). */
|
|
||||||
if (GE_ENVFLAG("GE_D245V")) {
|
|
||||||
static int callN = 0;
|
|
||||||
f32 minS = sp274[0].unk20, maxS = sp274[0].unk20;
|
|
||||||
f32 minT = sp274[0].unk24, maxT = sp274[0].unk24;
|
|
||||||
s32 vi;
|
|
||||||
for (vi = 1; vi < s1; vi++) {
|
|
||||||
if (sp274[vi].unk20 < minS) minS = sp274[vi].unk20;
|
|
||||||
if (sp274[vi].unk20 > maxS) maxS = sp274[vi].unk20;
|
|
||||||
if (sp274[vi].unk24 < minT) minT = sp274[vi].unk24;
|
|
||||||
if (sp274[vi].unk24 > maxT) maxT = sp274[vi].unk24;
|
|
||||||
}
|
|
||||||
fprintf(stderr, "D245V call=%d s1=%d minS=%.1f maxS=%.1f minT=%.1f maxT=%.1f "
|
|
||||||
"spanS=%.1f spanT=%.1f\n", callN++, s1,
|
|
||||||
(double) minS, (double) maxS, (double) minT, (double) maxT,
|
|
||||||
(double) (maxS - minS), (double) (maxT - minT));
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
if (s1 == 4)
|
if (s1 == 4)
|
||||||
{
|
{
|
||||||
@@ -1401,16 +1338,7 @@ Gfx *skyRender(Gfx *gdl)
|
|||||||
|
|
||||||
gDPPipeSync(gdl++);
|
gDPPipeSync(gdl++);
|
||||||
|
|
||||||
#ifdef PORT
|
|
||||||
/* D227 (M-100): see the matching capture in the IsWater block above. */
|
|
||||||
{
|
|
||||||
Gfx *skyTileFrom = gdl;
|
|
||||||
texSelect(&gdl, &skywaterimages[fogGetCurrentEnvironmentp()->SkyImageId], 1, 0, 2);
|
|
||||||
skyPortCaptureTile(skyTileFrom, gdl);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
texSelect(&gdl, &skywaterimages[fogGetCurrentEnvironmentp()->SkyImageId], 1, 0, 2);
|
texSelect(&gdl, &skywaterimages[fogGetCurrentEnvironmentp()->SkyImageId], 1, 0, 2);
|
||||||
#endif
|
|
||||||
|
|
||||||
if (1);
|
if (1);
|
||||||
|
|
||||||
@@ -1442,7 +1370,7 @@ Gfx *skyRender(Gfx *gdl)
|
|||||||
/* D227 (M-95): one shared wScale for every triangle drawn from this
|
/* D227 (M-95): one shared wScale for every triangle drawn from this
|
||||||
* sp94[] fan -- see skyPortBeginFan() above. Computed from unk0c,
|
* sp94[] fan -- see skyPortBeginFan() above. Computed from unk0c,
|
||||||
* which none of the position overrides below touch. */
|
* which none of the position overrides below touch. */
|
||||||
skyPortBeginFan(sp94, s1, TRUE);
|
skyPortBeginFan(sp94, s1);
|
||||||
#endif
|
#endif
|
||||||
if (s1 == 4)
|
if (s1 == 4)
|
||||||
{
|
{
|
||||||
@@ -1659,129 +1587,18 @@ static bool s_skyFanFoldSet = FALSE;
|
|||||||
* per-fan consistency is still what keeps shared vertices bit-identical
|
* per-fan consistency is still what keeps shared vertices bit-identical
|
||||||
* between the fan's primitives.
|
* between the fan's primitives.
|
||||||
*
|
*
|
||||||
* THE SAFETY VALVE (normally inactive, k == 0). A span can in principle still
|
* (The tile-shift "safety valve" described here before went with the s16 sky
|
||||||
* exceed s16. The RDP's own answer to that is the tile descriptor's shift
|
* vertex path it served: that path was removed 2026-09-30 and its leftover
|
||||||
* field: coordinates shift right by `shift` for shift <= 10 and LEFT by
|
* capture/shift code on 2026-10-02. The float path has no s16 range limit.)
|
||||||
* (16 - shift) for shift >= 11. So divide tc by 2^k and set the tile shift to
|
|
||||||
* (16 - k) -- decoded coordinate unchanged, s16 reaches 2^k further.
|
|
||||||
* port/fast3d implements this exactly (gfx_pc.cpp, gfx_dp_set_tile plus the
|
|
||||||
* u/v shift block), so it is a native GBI mechanism, not a port-side hack: no
|
|
||||||
* fast3d change, no tessellation, no extra vertices.
|
|
||||||
*
|
|
||||||
* k is chosen per FAN, not per primitive, for the same reason wScale and the
|
|
||||||
* fold are: the decoded coordinate is identical for any k, but the granularity
|
|
||||||
* is 2^k units, so two primitives sharing an edge under different k could round
|
|
||||||
* a shared vertex apart.
|
|
||||||
*
|
|
||||||
* k = 5 is the cap, because shift (16 - 5) == 11 is the smallest value fast3d
|
|
||||||
* still treats as a left shift. Beyond that a span would genuinely need
|
|
||||||
* Defect-2 tessellation; skyPortPickShift clamps rather than overflowing, so
|
|
||||||
* the failure mode stays graceful.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* One tile period expressed in unk20/unk24's units: 64 texels * 32 = 2048.
|
/* One tile period expressed in unk20/unk24's units: 64 texels * 32 = 2048.
|
||||||
* The fold must be a whole number of these or the rebase shifts the texture. */
|
* The fold must be a whole number of these or the rebase shifts the texture. */
|
||||||
#define SKY_TC_WRAP 2048
|
#define SKY_TC_WRAP 2048
|
||||||
|
|
||||||
static s32 s_skyFanShiftS = 0;
|
/* D227 (M-95/M-98): compute the per-fan wScale and S/T fold shared by every
|
||||||
static s32 s_skyFanShiftT = 0;
|
* primitive of one fan (see above). */
|
||||||
|
static void skyPortBeginFan(SkyRelated38 *v, s32 n)
|
||||||
/*
|
|
||||||
* Applying the shift means re-emitting the render tile's G_SETTILE with only
|
|
||||||
* its two shift fields changed. Rather than rebuild that command -- its format,
|
|
||||||
* depth, line and mask fields are derived inside texSelect() from the texture
|
|
||||||
* pool, and duplicating that derivation here would rot the moment a sky image
|
|
||||||
* had a different format -- capture the exact command texSelect() just emitted
|
|
||||||
* and replay a patched copy. Exact by construction, and it stays correct if
|
|
||||||
* texSelect's derivation ever changes.
|
|
||||||
*
|
|
||||||
* G_SETTILE word 1 layout (see port/fast3d/gfx_pc.cpp's G_SETTILE dispatch):
|
|
||||||
* tile = bits 24..26, shiftt = bits 10..13, shifts = bits 0..3.
|
|
||||||
*/
|
|
||||||
#define SKY_SETTILE_SHIFTT_SHIFT 10
|
|
||||||
#define SKY_SETTILE_FIELD_MASK 0xF
|
|
||||||
|
|
||||||
/* Tiles 0 and 1 are both captured. The IsWater path is a two-texunit draw
|
|
||||||
* (sub_GAME_7F09343C binds tile 0 and tile 1 to the same TMEM and cross-fades
|
|
||||||
* TEXEL0/TEXEL1 by an animated PRIM_LOD_FRAC), and both texunits read the same
|
|
||||||
* vertex tc, so a shift applied to only one of them would desynchronise the
|
|
||||||
* two layers. Capture must also run AFTER every tile-setup call for the draw,
|
|
||||||
* not just after texSelect -- sub_GAME_7F09343C re-declares both tiles, so
|
|
||||||
* capturing earlier would replay a stale declaration and clobber it. */
|
|
||||||
#define SKY_NUM_CAPTURED_TILES 2
|
|
||||||
|
|
||||||
static Gfx s_skyTileCmd[SKY_NUM_CAPTURED_TILES];
|
|
||||||
static bool s_skyTileCmdOk[SKY_NUM_CAPTURED_TILES];
|
|
||||||
|
|
||||||
/* Scan the display list just written for each tile's G_SETTILE. Takes the last
|
|
||||||
* match per tile: the mipmapped path emits one per level, and a later setup
|
|
||||||
* call may re-declare a tile the earlier one already set. */
|
|
||||||
static void skyPortCaptureTile(Gfx *start, Gfx *end)
|
|
||||||
{
|
|
||||||
Gfx *p;
|
|
||||||
s32 i;
|
|
||||||
|
|
||||||
for (i = 0; i < SKY_NUM_CAPTURED_TILES; i++)
|
|
||||||
s_skyTileCmdOk[i] = FALSE;
|
|
||||||
|
|
||||||
for (p = start; p < end; p++)
|
|
||||||
{
|
|
||||||
u32 tile;
|
|
||||||
|
|
||||||
if ((u32) (p->words.w0 >> 24) != (u32) (u8) G_SETTILE)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
tile = (p->words.w1 >> 24) & 7;
|
|
||||||
if (tile >= (u32) SKY_NUM_CAPTURED_TILES)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
s_skyTileCmd[tile] = *p;
|
|
||||||
s_skyTileCmdOk[tile] = TRUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Smallest k in [0, SKY_TC_MAX_SHIFT] for which the whole span still fits an
|
|
||||||
* s16 once divided by 2^k. `span` is the fan's S or T extent in S10.5 units;
|
|
||||||
* the +SKY_TC_WRAP covers the fold's rebase to a tile boundary. Normally
|
|
||||||
* returns 0 -- see the unit note above, the coordinates fit unaided. */
|
|
||||||
static s32 skyPortPickShift(f32 span)
|
|
||||||
{
|
|
||||||
s32 k;
|
|
||||||
for (k = 0; k < SKY_TC_MAX_SHIFT; k++)
|
|
||||||
{
|
|
||||||
if ((span + (f32) SKY_TC_WRAP) / (f32) (1 << k) <= 32767.0f)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return k;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* `allowShift` gates the safety valve. The IsWater quad used to pass FALSE
|
|
||||||
* (see D245) on the theory that a matched shift on both texunits would
|
|
||||||
* flatten the cross-fade; that theory was wrong and D245 corrects it here.
|
|
||||||
*
|
|
||||||
* That quad is a two-texunit draw: sub_GAME_7F09343C binds tile 0 and tile 1
|
|
||||||
* to the same TMEM and cross-fades TEXEL0/TEXEL1 with an animated
|
|
||||||
* PRIM_LOD_FRAC (the water shimmer). Both texunits read the SAME vertex tc;
|
|
||||||
* what makes tile 1 sample a different part of the texture is its own
|
|
||||||
* SetTileSize uls/ult offset (90,150 in 1/4-texel units -- see
|
|
||||||
* sub_GAME_7F09343C), applied by the RDP AFTER the tile's shift field
|
|
||||||
* multiplies the (possibly pre-divided) coordinate back up. skyPortEmitTileShift
|
|
||||||
* replays BOTH tiles' captured SETTILE commands with the identical k, so both
|
|
||||||
* texunits' decoded S/T are rescaled by the same 2^k before their own
|
|
||||||
* (unshifted, tile-native) uls/ult offset is applied -- the offset between
|
|
||||||
* tile 0 and tile 1's sample points is preserved, not flattened. Confirmed by
|
|
||||||
* a diagnostic build with allowShift forced TRUE on the water path: the
|
|
||||||
* cross-fade shimmer still animates, no flattening. D245 (view-tracking
|
|
||||||
* seam) was this quad's horizon vertex genuinely overflowing an s16 tc with
|
|
||||||
* no shift applied -- same class of bug as D227 before M-100b's fix, just
|
|
||||||
* never given the safety valve.
|
|
||||||
*
|
|
||||||
* The water on IsWater levels was separately and visibly broken (D229, green
|
|
||||||
* / pulsating, now FIXED) and that was NOT caused by any of this -- it
|
|
||||||
* reproduced identically on main with none of the D227 work present.
|
|
||||||
*/
|
|
||||||
static void skyPortBeginFan(SkyRelated38 *v, s32 n, bool allowShift)
|
|
||||||
{
|
{
|
||||||
f32 maxAbsW = 0.0f;
|
f32 maxAbsW = 0.0f;
|
||||||
f32 minS, minT, maxS, maxT;
|
f32 minS, minT, maxS, maxT;
|
||||||
@@ -1805,40 +1622,12 @@ static void skyPortBeginFan(SkyRelated38 *v, s32 n, bool allowShift)
|
|||||||
s_skyFanFoldS = (f32) ((s32) floorf(minS / (f32) SKY_TC_WRAP) * SKY_TC_WRAP);
|
s_skyFanFoldS = (f32) ((s32) floorf(minS / (f32) SKY_TC_WRAP) * SKY_TC_WRAP);
|
||||||
s_skyFanFoldT = (f32) ((s32) floorf(minT / (f32) SKY_TC_WRAP) * SKY_TC_WRAP);
|
s_skyFanFoldT = (f32) ((s32) floorf(minT / (f32) SKY_TC_WRAP) * SKY_TC_WRAP);
|
||||||
s_skyFanFoldSet = TRUE;
|
s_skyFanFoldSet = TRUE;
|
||||||
|
|
||||||
s_skyFanShiftS = allowShift ? skyPortPickShift(maxS - minS) : 0;
|
|
||||||
s_skyFanShiftT = allowShift ? skyPortPickShift(maxT - minT) : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void skyPortEndFan(void)
|
static void skyPortEndFan(void)
|
||||||
{
|
{
|
||||||
s_skyFanWScale = 0.0f;
|
s_skyFanWScale = 0.0f;
|
||||||
s_skyFanFoldSet = FALSE;
|
s_skyFanFoldSet = FALSE;
|
||||||
s_skyFanShiftS = 0;
|
|
||||||
s_skyFanShiftT = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* D245 (M-142): overrides the shift skyPortBeginFan() just picked. A GE_D245V
|
|
||||||
* capture (scripted camera turn, Frigate, headless) proved the water quad's
|
|
||||||
* raw S/T span sits in the ~290k-390k unit range -- roughly 10x sky's typical
|
|
||||||
* span -- and, critically, that skyPortPickShift's adaptive k genuinely
|
|
||||||
* CHANGES during ordinary view rotation (observed kT flipping 4->3 as spanT
|
|
||||||
* crossed skyPortPickShift's per-k breakpoint mid-turn). That contradicts the
|
|
||||||
* "normally k==0, this is a rare safety valve" assumption the mechanism was
|
|
||||||
* verified against (a single static frame, one camera angle) -- every k
|
|
||||||
* transition re-quantizes the shared vertex tc at a different granularity,
|
|
||||||
* which is a plausible mechanism for "glitches, goes back and forth... changes
|
|
||||||
* depending on your view" even with both texunits' relative offset preserved
|
|
||||||
* (D245's already-verified allowShift=TRUE reasoning). Untested candidate,
|
|
||||||
* NOT the default: forcing a single fixed k for the whole water draw removes
|
|
||||||
* the transition, at the cost of coarser (whole-k5-step) tc quantization
|
|
||||||
* always, which could itself look worse -- needs a live A/B, not another
|
|
||||||
* blind ship (see the finding's own "don't repeat the same mistake" note).
|
|
||||||
* GE_D245_FIXEDSHIFT=<k> (0..SKY_TC_MAX_SHIFT) opts in; unset changes nothing. */
|
|
||||||
static void skyPortForceFanShift(s32 kS, s32 kT)
|
|
||||||
{
|
|
||||||
s_skyFanShiftS = kS;
|
|
||||||
s_skyFanShiftT = kT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1882,7 +1671,7 @@ static Gfx *skyPortRenderPoly(Gfx *gdl, SkyRelated38 **v, s32 nverts)
|
|||||||
* multiple of that drops magnitudes into s16 range while preserving
|
* multiple of that drops magnitudes into s16 range while preserving
|
||||||
* inter-vertex deltas (hence the interpolated texture) exactly. With the
|
* inter-vertex deltas (hence the interpolated texture) exactly. With the
|
||||||
* units read correctly the spans fit unaided and no Defect-2 tessellation
|
* units read correctly the spans fit unaided and no Defect-2 tessellation
|
||||||
* is required; skyPortPickShift is the safety valve if one ever doesn't. */
|
* is required. */
|
||||||
f32 foldS, foldT;
|
f32 foldS, foldT;
|
||||||
/* D227 (M-98): prefer the whole-fan fold set by skyPortBeginFan() so
|
/* D227 (M-98): prefer the whole-fan fold set by skyPortBeginFan() so
|
||||||
* every triangle in a multi-call fan bakes its shared vertices against
|
* every triangle in a multi-call fan bakes its shared vertices against
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ if len(sys.argv) > 1:
|
|||||||
ROM_PATH = f"data/ge007.{REGION}.z64"
|
ROM_PATH = f"data/ge007.{REGION}.z64"
|
||||||
OUT_DIR = f"data/pcmodels-{REGION}"
|
OUT_DIR = f"data/pcmodels-{REGION}"
|
||||||
TABLE = "assets/obseg/file_resource_table.inc.c"
|
TABLE = "assets/obseg/file_resource_table.inc.c"
|
||||||
FILELIST = "scripts/filelist.u.csv"
|
# D258: each region has its own ROM layout; read its own filelist
|
||||||
|
FILELIST = "scripts/filelist.%s.csv" % {"ntsc-final": "u", "pal-final": "e", "jpn-final": "j"}[REGION]
|
||||||
|
|
||||||
rom = open(ROM_PATH, "rb").read()
|
rom = open(ROM_PATH, "rb").read()
|
||||||
rows = list(csv.reader(open(FILELIST)))
|
rows = list(csv.reader(open(FILELIST)))
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ if len(sys.argv) > 1 and sys.argv[1] in ("ntsc-final", "pal-final", "jpn-final")
|
|||||||
ROM_PATH = f"data/ge007.{REGION}.z64"
|
ROM_PATH = f"data/ge007.{REGION}.z64"
|
||||||
OUT_DIR = f"data/pccg-{REGION}"
|
OUT_DIR = f"data/pccg-{REGION}"
|
||||||
TABLE = "assets/obseg/file_resource_table.inc.c"
|
TABLE = "assets/obseg/file_resource_table.inc.c"
|
||||||
FILELIST = "scripts/filelist.u.csv"
|
# D258: each region has its own ROM layout; read its own filelist
|
||||||
|
FILELIST = "scripts/filelist.%s.csv" % {"ntsc-final": "u", "pal-final": "e", "jpn-final": "j"}[REGION]
|
||||||
|
|
||||||
if not os.path.exists(ROM_PATH):
|
if not os.path.exists(ROM_PATH):
|
||||||
print(f"SKIP: {ROM_PATH} not present in this environment", file=sys.stderr)
|
print(f"SKIP: {ROM_PATH} not present in this environment", file=sys.stderr)
|
||||||
|
|||||||
@@ -94,7 +94,8 @@ for _a in sys.argv[1:]:
|
|||||||
ROM_PATH = f"data/ge007.{REGION}.z64"
|
ROM_PATH = f"data/ge007.{REGION}.z64"
|
||||||
OUT_DIR = f"data/pccg-{REGION}"
|
OUT_DIR = f"data/pccg-{REGION}"
|
||||||
TABLE = "assets/obseg/file_resource_table.inc.c"
|
TABLE = "assets/obseg/file_resource_table.inc.c"
|
||||||
FILELIST = "scripts/filelist.u.csv"
|
# D258: each region has its own ROM layout; read its own filelist
|
||||||
|
FILELIST = "scripts/filelist.%s.csv" % {"ntsc-final": "u", "pal-final": "e", "jpn-final": "j"}[REGION]
|
||||||
|
|
||||||
if not os.path.exists(ROM_PATH):
|
if not os.path.exists(ROM_PATH):
|
||||||
print(f"SKIP: {ROM_PATH} not present in this environment", file=sys.stderr)
|
print(f"SKIP: {ROM_PATH} not present in this environment", file=sys.stderr)
|
||||||
|
|||||||
@@ -26,8 +26,13 @@ DOC = ROOT / "docs" / "dev" / "GE-ENV-PROBES.md"
|
|||||||
SCAN_DIRS = ["src", "port"]
|
SCAN_DIRS = ["src", "port"]
|
||||||
# envflag.h's GE_ENVFLAG/GE_ENVSTR are the cached-env wrappers most port code
|
# envflag.h's GE_ENVFLAG/GE_ENVSTR are the cached-env wrappers most port code
|
||||||
# uses; matching only bare getenv() reported every wrapper-read var as GONE.
|
# uses; matching only bare getenv() reported every wrapper-read var as GONE.
|
||||||
|
# d466Env (port/src/drawdistgameplay.c) is a local cached wrapper of the same
|
||||||
|
# shape: d466Env("GE_...", &cache).
|
||||||
GETENV_RE = re.compile(
|
GETENV_RE = re.compile(
|
||||||
r'(?:getenv|GE_ENVFLAG|GE_ENVSTR)\(\s*"(GE_[A-Z0-9_]+)"\s*\)')
|
r'(?:getenv|GE_ENVFLAG|GE_ENVSTR)\(\s*"(GE_[A-Z0-9_]+)"\s*\)'
|
||||||
|
r'|d466Env\(\s*"(GE_[A-Z0-9_]+)"\s*,')
|
||||||
|
# The wrapper macros' own names appear in the doc's prose; they are not vars.
|
||||||
|
NOT_VARS = {"GE_ENVFLAG", "GE_ENVSTR"}
|
||||||
|
|
||||||
|
|
||||||
def live_sites() -> dict[str, list[str]]:
|
def live_sites() -> dict[str, list[str]]:
|
||||||
@@ -43,7 +48,7 @@ def live_sites() -> dict[str, list[str]]:
|
|||||||
for n, line in enumerate(lines, 1):
|
for n, line in enumerate(lines, 1):
|
||||||
for m in GETENV_RE.finditer(line):
|
for m in GETENV_RE.finditer(line):
|
||||||
rel = path.relative_to(ROOT).as_posix()
|
rel = path.relative_to(ROOT).as_posix()
|
||||||
sites.setdefault(m.group(1), []).append(f"{rel}:{n}")
|
sites.setdefault(m.group(1) or m.group(2), []).append(f"{rel}:{n}")
|
||||||
return sites
|
return sites
|
||||||
|
|
||||||
|
|
||||||
@@ -60,6 +65,7 @@ def doc_vars() -> tuple[set[str], set[str]]:
|
|||||||
# names it plainly in the table's first cell, so it is still picked up.
|
# names it plainly in the table's first cell, so it is still picked up.
|
||||||
vs = [m.group(1) for m in re.finditer(r"(GE_[A-Z0-9_]+)(\[?)", line)
|
vs = [m.group(1) for m in re.finditer(r"(GE_[A-Z0-9_]+)(\[?)", line)
|
||||||
if not m.group(2)]
|
if not m.group(2)]
|
||||||
|
vs = [v for v in vs if v not in NOT_VARS]
|
||||||
named.update(vs)
|
named.update(vs)
|
||||||
if "tombstone" in line.lower():
|
if "tombstone" in line.lower():
|
||||||
tombstone.update(vs)
|
tombstone.update(vs)
|
||||||
|
|||||||
@@ -1,23 +1,29 @@
|
|||||||
# Golden baseline frames
|
# Golden baseline frames
|
||||||
|
|
||||||
`frame_NNNNNN.png` — reference `GE_PCDUMP` captures for `framediff.py`
|
`<level>/<platform>/frame_NNNNNN.png`: reference `GE_PCDUMP` captures for
|
||||||
(`-level_09` BUNKER1: settled gameplay after the intro). Compared structurally, not exactly — the port
|
`tools_pc/verify.sh`, one folder per solo level (all 21, `cuba` = the ending)
|
||||||
is not frame-deterministic (see `docs/internals.md` "D117").
|
and per platform (`win` today; `linux` to come).
|
||||||
|
|
||||||
Regenerate after a deliberate visual change:
|
**Recipe** (2026-10-02 re-base; `verify.sh` applies all of it):
|
||||||
|
- `data/ge007.ini` pinned to ONLY `[Window] Width = 640, Height = 480` (every
|
||||||
|
other option at its compiled-in default; verify.sh backs yours up).
|
||||||
|
- `GE_RSEED=0x0123456789abcdef` (pinned PRNG).
|
||||||
|
- `GE_INPUTSCRIPT="20:START"` (skips the intro flyby; the scripted pad is
|
||||||
|
the only input) and `GE_PCDUMP="900-1500:300"`: frames 900/1200/1500 are
|
||||||
|
settled gameplay on every level. **Cuba** instead: no input
|
||||||
|
(`1:SNONE`) and `300-900:300` (the ending cutscene; it returns to the boot
|
||||||
|
screens before frame 1200).
|
||||||
|
- Every run ends via `GE_QUITFRAME` (orderly quit; never a hard kill, D344).
|
||||||
|
|
||||||
```sh
|
**Gate** (two tiers): the structural framediff (cell means, coverage,
|
||||||
# data/ge007.ini must hold ONLY this (back yours up) — every other option at
|
phash) catches gross breakage; then a per-pixel `--exact --tol 2` check
|
||||||
# its compiled-in default; a personal FovScale/DrawDistance/MSAA fails the gate
|
with a per-level limit: 1% of pixels, 3% for Jungle and Surface 2, Cuba
|
||||||
printf '[Window]
|
structural only. Measured run to run: <= 0.57% on 18 levels, Jungle 1.75%,
|
||||||
Width = 640
|
Surface 2 2.33%, Cuba 36%; a global texture-filter switch measured 22%.
|
||||||
Height = 480
|
|
||||||
' > data/ge007.ini
|
|
||||||
GE_PCDUMP="640-1120:240" ./build-pc/ge007.x86_64.exe -level_09
|
|
||||||
python tools_pc/framediff.py ppm --update # then git rm any stale stems
|
|
||||||
```
|
|
||||||
|
|
||||||
`tools_pc/verify.sh bunker1` does the ini pin + capture + diff for you.
|
Regenerate one level after a deliberate visual change: run
|
||||||
|
`tools_pc/verify.sh <level>` twice to confirm the new look is stable, then
|
||||||
|
capture with the recipe above and replace `<level>/<platform>/*.png`.
|
||||||
|
|
||||||
## History
|
## History
|
||||||
|
|
||||||
@@ -39,3 +45,9 @@ python tools_pc/framediff.py ppm --update # then git rm any stale stems
|
|||||||
its camera is nondeterministic (D117): 1 in ~5 gate runs failed it (dmean
|
its camera is nondeterministic (D117): 1 in ~5 gate runs failed it (dmean
|
||||||
0.7 vs 20.7 run to run). All three frames are now settled gameplay; three
|
0.7 vs 20.7 run to run). All three frames are now settled gameplay; three
|
||||||
independent captures agree (worst dmean ≤ 6.8).
|
independent captures agree (worst dmean ≤ 6.8).
|
||||||
|
- **2026-10-02:** re-based and extended to all 21 levels (`<level>/win/`),
|
||||||
|
recipe above. The flat bunker1 set at the folder root was retired. Two
|
||||||
|
independent 21-level passes agreed; noise per level measured as listed.
|
||||||
|
`verify.sh` no longer hard-kills (taskkill of every `ge007.x86_64.exe`,
|
||||||
|
including a maintainer's game); it quits via `GE_QUITFRAME` and only stops
|
||||||
|
its own PID as a watchdog fallback.
|
||||||
|
After Width: | Height: | Size: 334 KiB |
|
After Width: | Height: | Size: 332 KiB |
|
After Width: | Height: | Size: 333 KiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 273 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 212 KiB |
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 218 KiB |
|
After Width: | Height: | Size: 218 KiB |
|
After Width: | Height: | Size: 218 KiB |
|
After Width: | Height: | Size: 237 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 267 KiB |
|
After Width: | Height: | Size: 271 KiB |
|
After Width: | Height: | Size: 267 KiB |
|
After Width: | Height: | Size: 253 KiB |
|
After Width: | Height: | Size: 252 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 201 KiB |
|
After Width: | Height: | Size: 328 KiB |
|
After Width: | Height: | Size: 328 KiB |
|
After Width: | Height: | Size: 329 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 244 KiB |
|
Before Width: | Height: | Size: 244 KiB |
|
Before Width: | Height: | Size: 244 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 262 KiB |
|
After Width: | Height: | Size: 261 KiB |
|
After Width: | Height: | Size: 260 KiB |
|
After Width: | Height: | Size: 331 KiB |
|
After Width: | Height: | Size: 331 KiB |
|
After Width: | Height: | Size: 331 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 296 KiB |
|
After Width: | Height: | Size: 297 KiB |
|
After Width: | Height: | Size: 296 KiB |
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 237 KiB |
|
After Width: | Height: | Size: 237 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 130 KiB |
@@ -18,10 +18,14 @@
|
|||||||
#
|
#
|
||||||
# Options:
|
# Options:
|
||||||
# --platform win|linux default: autodetect via `uname -s`
|
# --platform win|linux default: autodetect via `uname -s`
|
||||||
# --dump lo-hi:step GE_PCDUMP window (default: 640-1120:240 single level,
|
# --dump lo-hi:step GE_PCDUMP window (default: 900-1500:300, the golden
|
||||||
# 80-400:40 sweep — matches the existing golden set
|
# capture window for every level and both modes)
|
||||||
# and level_sweep.sh respectively)
|
# --script "..." GE_INPUTSCRIPT (default "20:START": skip the intro
|
||||||
# --script "..." GE_INPUTSCRIPT passthrough (scripted input)
|
# flyby so frame 900+ is settled gameplay)
|
||||||
|
#
|
||||||
|
# Golden recipe (2026-10-02 re-base): 640x480 defaults-only ini, GE_RSEED pinned
|
||||||
|
# (GOLDEN_SEED below), GE_INPUTSCRIPT "20:START", GE_PCDUMP 900-1500:300,
|
||||||
|
# goldens in tools_pc/golden/<level>/<platform>/. See tools_pc/golden/README.md.
|
||||||
# --json also emit one JSON object (or array, for sweep) to
|
# --json also emit one JSON object (or array, for sweep) to
|
||||||
# stdout: {level, platform, status, frames,
|
# stdout: {level, platform, status, frames,
|
||||||
# worst_cell, crash_sym}
|
# worst_cell, crash_sym}
|
||||||
@@ -41,13 +45,28 @@ cd "$(dirname "$0")/.."
|
|||||||
ROOT="$(pwd)"
|
ROOT="$(pwd)"
|
||||||
|
|
||||||
# --- arg parsing -------------------------------------------------------------
|
# --- arg parsing -------------------------------------------------------------
|
||||||
MODE=""; LEVEL_ARG=""; PLATFORM=""; DUMP=""; SCRIPT=""; JSON=0; AGAINST=""
|
MODE=""; LEVEL_ARG=""; PLATFORM=""; DUMP=""; SCRIPT="20:START"; JSON=0; AGAINST=""
|
||||||
|
GOLDEN_DUMP="900-1500:300"
|
||||||
|
USER_DUMP=0; USER_SCRIPT=0
|
||||||
|
# Per-level golden recipe overrides: Cuba is the ending cutscene + credits (it
|
||||||
|
# drops back to the boot screens before frame 1200, and START would skip it),
|
||||||
|
# so it captures the cutscene itself with no input.
|
||||||
|
golden_dump_for() { case "$1" in cuba) echo "300-900:300" ;; *) echo "$GOLDEN_DUMP" ;; esac; }
|
||||||
|
# Second tier: per-pixel (framediff --exact) limit, % of pixels over a
|
||||||
|
# per-channel tolerance of 2. Measured 2026-10-02 run to run (21 levels x 2
|
||||||
|
# captures): <= 0.57% everywhere except Jungle 1.75% / Surface 2 2.33% (moving
|
||||||
|
# foliage/fog) and Cuba 36% (cutscene animation timing; structural tier only).
|
||||||
|
# A global texture-filter change measured 22% on Dam, so 1% leaves ~2x margin
|
||||||
|
# over the noise and still catches whole-frame render changes.
|
||||||
|
golden_tolpct_for() { case "$1" in cuba) echo "" ;; jungle|surface2) echo "3.0" ;; *) echo "1.0" ;; esac; }
|
||||||
|
golden_script_for() { case "$1" in cuba) echo "1:SNONE" ;; *) echo "20:START" ;; esac; }
|
||||||
|
GOLDEN_SEED="${GE_RSEED:-0x0123456789abcdef}"
|
||||||
POSARGS=()
|
POSARGS=()
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--platform) PLATFORM="$2"; shift 2 ;;
|
--platform) PLATFORM="$2"; shift 2 ;;
|
||||||
--dump) DUMP="$2"; shift 2 ;;
|
--dump) DUMP="$2"; USER_DUMP=1; shift 2 ;;
|
||||||
--script) SCRIPT="$2"; shift 2 ;;
|
--script) SCRIPT="$2"; USER_SCRIPT=1; shift 2 ;;
|
||||||
--against) AGAINST="$2"; shift 2 ;;
|
--against) AGAINST="$2"; shift 2 ;;
|
||||||
--json) JSON=1; shift ;;
|
--json) JSON=1; shift ;;
|
||||||
-h|--help) sed -n '2,32p' "$0"; exit 0 ;;
|
-h|--help) sed -n '2,32p' "$0"; exit 0 ;;
|
||||||
@@ -74,13 +93,18 @@ if [ "$PLATFORM" = win ]; then
|
|||||||
BUILD_DIR=build-pc
|
BUILD_DIR=build-pc
|
||||||
EXE="$ROOT/build-pc/ge007.x86_64.exe"
|
EXE="$ROOT/build-pc/ge007.x86_64.exe"
|
||||||
export PATH="/c/msys64/mingw64/bin:$PATH" # addr2line + runtime DLLs
|
export PATH="/c/msys64/mingw64/bin:$PATH" # addr2line + runtime DLLs
|
||||||
KILL() { taskkill //F //IM ge007.x86_64.exe >/dev/null 2>&1; }
|
|
||||||
else
|
else
|
||||||
BUILD_DIR=build-linux
|
BUILD_DIR=build-linux
|
||||||
EXE="$ROOT/build-linux/ge007.x86_64"
|
EXE="$ROOT/build-linux/ge007.x86_64"
|
||||||
KILL() { pkill -f ge007.x86_64 >/dev/null 2>&1; }
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Only ever stop the game process THIS script started (never other instances:
|
||||||
|
# the maintainer may be playing), and only as a watchdog fallback -- every run
|
||||||
|
# normally ends through GE_QUITFRAME's orderly quit (D344: a hard kill of a
|
||||||
|
# live GL/audio process has caused a host BSOD).
|
||||||
|
GAMEPID=""
|
||||||
|
kill_ours() { [ -n "$GAMEPID" ] && kill "$GAMEPID" >/dev/null 2>&1; GAMEPID=""; }
|
||||||
|
|
||||||
# name -> -level_XX (mission order; matches playtest.sh / level_sweep.sh)
|
# name -> -level_XX (mission order; matches playtest.sh / level_sweep.sh)
|
||||||
LEVELS=(
|
LEVELS=(
|
||||||
"dam:33" "facility:34" "runway:35" "surface1:36" "bunker1:09" "silo:20"
|
"dam:33" "facility:34" "runway:35" "surface1:36" "bunker1:09" "silo:20"
|
||||||
@@ -129,9 +153,6 @@ golden_dir_for() {
|
|||||||
local name="$1"
|
local name="$1"
|
||||||
local per="$ROOT/tools_pc/golden/$name/$PLATFORM"
|
local per="$ROOT/tools_pc/golden/$name/$PLATFORM"
|
||||||
if [ -d "$per" ] && ls "$per"/*.png >/dev/null 2>&1; then echo "$per"; return; fi
|
if [ -d "$per" ] && ls "$per"/*.png >/dev/null 2>&1; then echo "$per"; return; fi
|
||||||
if [ "$name" = "bunker1" ] && ls "$ROOT/tools_pc/golden"/*.png >/dev/null 2>&1; then
|
|
||||||
echo "$ROOT/tools_pc/golden"; return
|
|
||||||
fi
|
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,52 +175,49 @@ restore_ini() {
|
|||||||
if [ -n "$INI_BAK" ]; then cp "$INI_BAK" "$INI" && rm -f "$INI_BAK"
|
if [ -n "$INI_BAK" ]; then cp "$INI_BAK" "$INI" && rm -f "$INI_BAK"
|
||||||
elif [ "$INI_CREATED" = 1 ]; then rm -f "$INI"; fi
|
elif [ "$INI_CREATED" = 1 ]; then rm -f "$INI"; fi
|
||||||
}
|
}
|
||||||
trap 'restore_ini; KILL' EXIT INT TERM
|
trap 'restore_ini; kill_ours' EXIT INT TERM
|
||||||
|
|
||||||
# --- run one level -> sets globals: R_STATUS R_FRAMES R_SYM R_BRIEF -------
|
# --- run one level -> sets globals: R_STATUS R_FRAMES R_SYM R_BRIEF -------
|
||||||
run_one() {
|
run_one() {
|
||||||
local name="$1" num="$2" dump="$3" watchdog="${4:-90}"
|
local name="$1" num="$2" dump="$3" watchdog="${4:-90}"
|
||||||
local capdir; capdir=$(mktemp -d)
|
local capdir; capdir=$(mktemp -d)
|
||||||
|
[ "$USER_DUMP" = 1 ] || dump=$(golden_dump_for "$name")
|
||||||
|
local script="$SCRIPT"
|
||||||
|
[ "$USER_SCRIPT" = 1 ] || script=$(golden_script_for "$name")
|
||||||
R_STATUS=""; R_FRAMES=0; R_SYM=""; R_BRIEF=""
|
R_STATUS=""; R_FRAMES=0; R_SYM=""; R_BRIEF=""
|
||||||
|
|
||||||
rm -f "$ROOT/ge007.crash.log"
|
rm -f "$ROOT/ge007.crash.log"
|
||||||
KILL; sleep 0.3
|
|
||||||
|
|
||||||
# Speed-up: a level only needs to render its intro pan + spawn-in once --
|
# Quit right after the last requested GE_PCDUMP frame via GE_QUITFRAME (an
|
||||||
# everything after the last requested GE_PCDUMP frame is wasted wall-clock
|
# orderly D344 quit), instead of idling out the watchdog; the watchdog stays
|
||||||
# (the process used to always idle out the full watchdog, ~30s/level of
|
# the safety net for a level that never gets there (hang / flake).
|
||||||
# nothing on top of the ~15s that actually matters -- 21 levels x that
|
local lo hi step lastframe quitframe=""
|
||||||
# waste is most of a sweep's runtime). Poll for the last frame's PPM (or a
|
|
||||||
# crash) and kill the instant it lands; `watchdog` remains the safety net
|
|
||||||
# for a level that never gets there (hang / load-sensitivity flake).
|
|
||||||
local lo hi step lastframe target=""
|
|
||||||
IFS='-:' read -r lo hi step <<< "$dump"
|
IFS='-:' read -r lo hi step <<< "$dump"
|
||||||
if [[ "$lo" =~ ^-?[0-9]+$ && "$hi" =~ ^-?[0-9]+$ ]]; then
|
if [[ "$lo" =~ ^-?[0-9]+$ && "$hi" =~ ^-?[0-9]+$ ]]; then
|
||||||
[[ "$step" =~ ^[0-9]+$ && "$step" -gt 0 ]] || step=1
|
[[ "$step" =~ ^[0-9]+$ && "$step" -gt 0 ]] || step=1
|
||||||
lastframe=$(( lo + step * ( (hi - lo) / step ) ))
|
lastframe=$(( lo + step * ( (hi - lo) / step ) ))
|
||||||
target=$(printf "%s/ppm/frame_%06d.ppm" "$ROOT" "$lastframe")
|
quitframe=$(( lastframe + 2 ))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
( cd "$ROOT" && export GE_PCDUMP="$dump"
|
( cd "$ROOT" && export GE_PCDUMP="$dump"
|
||||||
[ -n "$SCRIPT" ] && export GE_INPUTSCRIPT="$SCRIPT"
|
[ -n "$quitframe" ] && export GE_QUITFRAME="$quitframe"
|
||||||
if [ -n "$target" ]; then "$EXE" "-level_$num"; else timeout "$watchdog" "$EXE" "-level_$num"; fi ) \
|
[ -n "$script" ] && export GE_INPUTSCRIPT="$script"
|
||||||
>"$capdir/run.log" 2>&1 &
|
export GE_RSEED="$GOLDEN_SEED"
|
||||||
|
exec "$EXE" "-level_$num" ) >"$capdir/run.log" 2>&1 &
|
||||||
local gamepid=$!
|
local gamepid=$!
|
||||||
|
GAMEPID=$gamepid
|
||||||
|
|
||||||
if [ -n "$target" ]; then
|
local ticks=0 max_ticks=$(( watchdog * 5 )) # 0.2s ticks
|
||||||
local ticks=0 max_ticks=$(( watchdog * 5 )) # 0.2s ticks
|
while kill -0 "$gamepid" 2>/dev/null && [ "$ticks" -lt "$max_ticks" ]; do
|
||||||
while [ "$ticks" -lt "$max_ticks" ]; do
|
sleep 0.2
|
||||||
if [ -f "$target" ] || [ -f "$ROOT/ge007.crash.log" ]; then
|
ticks=$((ticks + 1))
|
||||||
sleep 0.2 # let the just-written frame's fclose() settle
|
done
|
||||||
break
|
if kill -0 "$gamepid" 2>/dev/null; then
|
||||||
fi
|
echo " watchdog: $name still running after ${watchdog}s, stopping it" >&2
|
||||||
kill -0 "$gamepid" 2>/dev/null || break # exited/crashed on its own
|
kill_ours
|
||||||
sleep 0.2
|
|
||||||
ticks=$((ticks + 1))
|
|
||||||
done
|
|
||||||
KILL
|
|
||||||
fi
|
fi
|
||||||
wait "$gamepid" 2>/dev/null
|
wait "$gamepid" 2>/dev/null
|
||||||
|
GAMEPID=""
|
||||||
mv "$ROOT"/ppm "$capdir/ppm" 2>/dev/null || mkdir -p "$capdir/ppm"
|
mv "$ROOT"/ppm "$capdir/ppm" 2>/dev/null || mkdir -p "$capdir/ppm"
|
||||||
|
|
||||||
R_FRAMES=$(ls "$capdir/ppm"/*.ppm 2>/dev/null | wc -l)
|
R_FRAMES=$(ls "$capdir/ppm"/*.ppm 2>/dev/null | wc -l)
|
||||||
@@ -284,13 +302,21 @@ except Exception:
|
|||||||
# exit 1 = a real per-frame threshold fail (pixel/cell/hash delta).
|
# exit 1 = a real per-frame threshold fail (pixel/cell/hash delta).
|
||||||
# exit 2 = a structural mismatch (missing/size) -- most commonly the
|
# exit 2 = a structural mismatch (missing/size) -- most commonly the
|
||||||
# golden's frame stems don't line up with this run's --dump window
|
# golden's frame stems don't line up with this run's --dump window
|
||||||
# (today's only golden, level_09, was captured at 640-1120:240; a
|
# (goldens are captured at GOLDEN_DUMP; a
|
||||||
# sweep using a different stride has nothing to compare against
|
# sweep using a different stride has nothing to compare against
|
||||||
# rather than a real regression). Only exit 1 is a verdict fail.
|
# rather than a real regression). Only exit 1 is a verdict fail.
|
||||||
case "$fexit" in
|
case "$fexit" in
|
||||||
1) status=REGRESSION; note="framediff vs $gdir" ;;
|
1) status=REGRESSION; note="framediff vs $gdir" ;;
|
||||||
2) note="golden frame stems don't match --dump window ($dump) for $gdir" ;;
|
2) note="golden frame stems don't match --dump window ($dump) for $gdir" ;;
|
||||||
esac
|
esac
|
||||||
|
local tolpct; tolpct=$(golden_tolpct_for "$name")
|
||||||
|
if [ "$fexit" = 0 ] && [ -n "$tolpct" ]; then
|
||||||
|
local xout; xout=$(python3 "$ROOT/tools_pc/framediff.py" "$CAPDIR/ppm" --golden "$gdir" --exact --tol 2 --tol-pct "$tolpct" 2>&1)
|
||||||
|
if [ $? -eq 1 ]; then
|
||||||
|
status=REGRESSION
|
||||||
|
note="per-pixel: $(echo "$xout" | grep -m1 FAIL | sed 's/^FAIL //') (limit ${tolpct}%)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
note="no golden for $name/$PLATFORM yet (Step 3)"
|
note="no golden for $name/$PLATFORM yet (Step 3)"
|
||||||
fi
|
fi
|
||||||
@@ -327,7 +353,7 @@ case "$MODE" in
|
|||||||
name="${resolved%%:*}"; num="${resolved##*:}"
|
name="${resolved%%:*}"; num="${resolved##*:}"
|
||||||
[ "${resolved%%:*}" = num ] && name=$(name_for_num "$num")
|
[ "${resolved%%:*}" = num ] && name=$(name_for_num "$num")
|
||||||
pin_ini_640x480 2>/dev/null || true
|
pin_ini_640x480 2>/dev/null || true
|
||||||
verify_level "$name" "$num" "${DUMP:-640-1120:240}"
|
verify_level "$name" "$num" "${DUMP:-$GOLDEN_DUMP}"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
sweep)
|
sweep)
|
||||||
@@ -339,7 +365,7 @@ case "$MODE" in
|
|||||||
for e in "${entries[@]}"; do
|
for e in "${entries[@]}"; do
|
||||||
n="${e%%:*}"; num="${e##*:}"
|
n="${e%%:*}"; num="${e##*:}"
|
||||||
pin_ini_640x480 2>/dev/null || true
|
pin_ini_640x480 2>/dev/null || true
|
||||||
out=$(verify_level "$n" "$num" "${DUMP:-80-400:40}" 45)
|
out=$(verify_level "$n" "$num" "${DUMP:-$GOLDEN_DUMP}" 60)
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -ne 0 ] && RC=1
|
[ "$rc" -ne 0 ] && RC=1
|
||||||
# Pre-existing bug (found alongside D244's diagnostics): `head -1` here
|
# Pre-existing bug (found alongside D244's diagnostics): `head -1` here
|
||||||
@@ -375,7 +401,7 @@ case "$MODE" in
|
|||||||
[ -z "$resolved" ] && { echo "unknown level '$LEVEL_ARG'" >&2; exit 2; }
|
[ -z "$resolved" ] && { echo "unknown level '$LEVEL_ARG'" >&2; exit 2; }
|
||||||
name="${resolved%%:*}"; num="${resolved##*:}"
|
name="${resolved%%:*}"; num="${resolved##*:}"
|
||||||
pin_ini_640x480 2>/dev/null || true
|
pin_ini_640x480 2>/dev/null || true
|
||||||
run_one "$name" "$num" "${DUMP:-640-1120:240}" 90
|
run_one "$name" "$num" "${DUMP:-$GOLDEN_DUMP}" 90
|
||||||
if [ "$R_STATUS" != PASS ]; then
|
if [ "$R_STATUS" != PASS ]; then
|
||||||
emit_verdict "$name" "$R_STATUS" "$R_FRAMES" "" "$R_SYM"
|
emit_verdict "$name" "$R_STATUS" "$R_FRAMES" "" "$R_SYM"
|
||||||
rm -rf "$CAPDIR"; exit 1
|
rm -rf "$CAPDIR"; exit 1
|
||||||
|
|||||||