forked from Ports/goldeneye-pc-port
Gameplay and rendering fixes D463-D468; D441 pointer-width sites
Zero-line tile texrect guard and texture-import skip (D463), stage-bank red zone checker (D464), Dam/Caverns water slide (D465, #127), extended draw distance no longer changing gameplay (D466, #125), player matrix pointers and Model/PROMOTE/vtxstore rebase (D457 c/d), AI view clamp for ultrawide (D468), fire-sound path audit (D433, D467 #130), italoarruda's #107 pointer-width sites (two sites, credited). Refs: D463 D464 D465 D466 D467 D468 D457 D433 #125 #127 #130 #107 Co-authored-by: italoarruda <3868006+italoarruda@users.noreply.github.com>
This commit is contained in:
1 parent
00fdd94c8d
commit
9dd98bdb20
27 files changed
+972
-39
No files matched your search
+40
-38
@@ -60,8 +60,9 @@ The port is **feature complete** when all of these hold:
|
||||
difficulty, plus a split-screen session, on a build with the §3
|
||||
verification debt cleared.
|
||||
|
||||
Where things stand (2026-09-29): 1–7 hold for NTSC-U, with the small
|
||||
accuracy gaps listed in §2. 8 and 9 are open.
|
||||
Where things stand (2026-10-01): 1–7 hold for NTSC-U, with the small
|
||||
accuracy gaps listed in §2. 8 is merged on `release/v0.4.1` (2P verified on
|
||||
every MP map; 3P/4P with real pads owed). 9 is open (Stage B).
|
||||
|
||||
**Parity rule (decided 2026-09-30):** every platform we release for
|
||||
carries the same sign-off gate: a full 21-level sweep plus a campaign
|
||||
@@ -102,9 +103,9 @@ below).
|
||||
|
||||
| Issue | Impact | Workaround | Tracking |
|
||||
|---|---|---|---|
|
||||
| Multiplayer isn't available yet | Missing feature | — | #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 |
|
||||
| `All unlocked` may already have written fake unlocks into a save from an older build | Save corruption | **Fixed on `release/v0.4.1` for new saves** (D387). Back up `data/ge007.eep` before first use; fakes an older build wrote are not repaired; cheats earned while ON are not kept. Delete this row when it ships. | 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 |
|
||||
| F10 overlay stretches in native widescreen (menus pillarbox correctly) | Cosmetic | — | D335b, §5a |
|
||||
| Rareware logo shows a subtle texture-filtering artifact | Cosmetic | — | D75, §2 |
|
||||
@@ -119,7 +120,7 @@ below).
|
||||
|
||||
| Item | Status | Refs | Notes |
|
||||
|---|---|---|---|
|
||||
| **2–4 player split-screen multiplayer** | parked | #99, D416–D423, branch `feat/mp-splitscreen-99` | The largest missing N64 feature. 2P has been live-tested (M&K + pad, respawn, 16:9 HUD). Before merge: Rule-2 review of `PROP__PORT_SIGNED` (D417) and a golden gate run on the maintainer's console. Open on the branch: D422 (no centred aim for P2+), and D428's sway accumulator is player-0-only. D420 (Scores page big number) FIXED, D421 closed (moot), D423 closed (faithful level shadow), all 2026-10-01; pause/unpause, watch-menu pages, exit-confirm and the F10 overlay during a match run clean headlessly in 2P/3P/4P. Untested: pads-only mode, MP audio, most stages, and the full N64 MP setup screens (scenarios, handicaps, teams). Needs per-pad seats (§5). An outside restore exists for reference: birdturtle `feature/local-mp-input`. |
|
||||
| **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). |
|
||||
| **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. |
|
||||
| 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. |
|
||||
@@ -128,16 +129,19 @@ below).
|
||||
|
||||
| Item | Status | Refs | Notes |
|
||||
|---|---|---|---|
|
||||
| Auto-fire gunshot sound slightly differs from 1964/GEPD | decision | 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| Third-person Bond offset residual | partial | D173, D292 | Mostly fixed. A small drift remains about 1 time in 10. |
|
||||
| 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. |
|
||||
| Crash on save slots 1/3/4, plus a host BSOD (fltmgr.sys) | open | #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). Close as stale if there's still no reply, and reproduce locally rather than asking the reporter again. |
|
||||
| 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. Known D466 residual: chrs in the extended band take the visible animation / WAYMODE_MAGIC path. |
|
||||
| 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). |
|
||||
| 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. |
|
||||
| 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 | parked | D323 | Distant textures use driver mips instead of Rare's hand-authored chain (heaviest on Statue/Aztec). Parked as a fidelity enhancement. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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). |
|
||||
|
||||
## 3. Verification debt (fixed; a live check is owed)
|
||||
|
||||
@@ -149,6 +153,8 @@ A single campaign pass with these on the checklist would clear most of them.
|
||||
| Probe strip (2026-09-30) | D245, D302, D318 | Frigate or Dam water/sky A/B against the previous build (the s16 sky path was removed); one Facility playthrough to confirm the D318 watchdog stays silent. |
|
||||
| Infra fixes | D179, build-pc.sh, crash.c, CI | The 2026-09-30 re-exec no-op (PATHEXT=.CPL in the inherited env) is fixed and verified from the agent shell (see AGENTS.md). `./build-pc.sh` from a genuine MSYS2 login shell; the next CI run (pinned actions, workflow input check). `GE_CRASHTEST=6` shows resolved frames. The romdata `VirtualAlloc` failure path now fails boot instead of falling back to the heap copy: confirm you want that. |
|
||||
| Light-fixture hit-type reads (impact sound + sparks) | D434, #119 | Shoot fixtures on Bunker and Caverns. |
|
||||
| Dam/Caverns water animation | D465, #127 | Fixed 2026-10-01 (16-byte `Gfx` raw-index write). Maintainer: still "slightly faster" than 1964 on Dam; measured rate equals N64 within 0.1%. Optional objective check: time ten shimmer cycles on both (expect ~26 s). |
|
||||
| End credits after Cradle | #129 | Did not reproduce on `release/v0.4.1` 2026-10-01 (maintainer finished Cradle on Agent, credits played, clean exit). Close #129 as not reproducing when the release ships. |
|
||||
| v0.4.1 batch (unreleased; fixes live on `release/v0.4.1`) | D424–D432; #114 fire rate, #115 tank-crush audio loop, #116 tank movement, #117 sway, #118 GL recoil, #119 light fixtures | All user-verified against GEPD/1964 on 2026-09-29. Close the issues when it ships. |
|
||||
| FOV-scale edge culling | D222 | Max FOV, pan across NPCs at the screen edge. |
|
||||
| Water pulse, Surface 2 | D229 | Frigate is verified. Only the Surface 2 by-eye check is owed. |
|
||||
@@ -192,13 +198,13 @@ Background research lives in the maintainer's local notes (gitignored):
|
||||
| Item | Status | Refs | Notes |
|
||||
|---|---|---|---|
|
||||
| "Original N64" preset: pillarbox at the original aspect | verify | D440, D447 | Landed 2026-09-30 (D447): `Video.AspectMode` (Window/Original), set by the Original N64 preset. Exact 4:3, or 16:9 while the game's watch Ratio is 16:9. Letterboxes in taller windows. `Crop overscan` no longer eats the game's Wide/Cinema letterbox. Measured: 160 px bars at 1280x720. Window-mode frame diff is within the run-to-run noise floor (dmean 0.93 vs base-vs-base 0.85). **Owed:** compare 4:3 and 16:9 against 1964; Wide/Cinema with crop on; menu mouse alignment in Original mode; MSAA 4/8/16, fullscreen toggle, Linux build. F10 full-window is deferred to an F10 polish pass. Known: with crop off, the D246 one-unit edge line shows at the rect edges. |
|
||||
| `All unlocked` rework: RAM override instead of save patching | open | D387, D259 | **Approved 2026-09-30.** Levels are already RAM-only (the game's `debug_enable_all_levels_flag`/`debug_007_unlock_flag`, `port/src/main.c`). Replace the cheat half, a block-4 EEPROM read patch plus the D387 write merge in `port/src/libultra.c`, with one opt-in `#ifdef PORT` hook at the cheat-unlocked check (`src/game/file2.c` `fileGetIsCheatUnlocked`, and confirm every caller including `front.c` `frontCheckIfCheatIsUnlocked`). The save is then never touched: earned cheats and times persist normally, OFF restores real progress, and the D259 silent-volume quirk goes away. This is a `src/game` edit for an opt-in port feature, so document it as such (maintainer-approved). Then remove the D387 merge machinery. Already-persisted fakes from older builds stay unrepaired. |
|
||||
| Settings units and values cleanup | open | D357 ([plan](dev/D357-SETTINGS-VALUES-PLAN.md)) | **Approved 2026-09-30 with changes** (recorded in the plan's §0): FOV in degrees; FPS cap presets 30/60/90/120/144/240/Uncapped **plus** a free custom value; MSAA adds 16×; mouse invert stays a separate toggle; the texture filter keeps "N64 3-point" (the plan's rename rests on a wrong premise). Refresh the plan's stale "Now" column first (the deadzone is split L/R; draw/LOD defaults are 250). |
|
||||
| Restart button in the settings menus | open | — | QoL (2026-09-30): next to Quit to desktop, a "Restart game" action so that restart-required changes (MSAA, etc.) apply in one click. Relaunch the same exe with the same args after an orderly quit (the D344 path, since hard exits have BSOD'd). Offer it wherever a changed row is flagged "restart". |
|
||||
| Front-end PC Options: Previous/Next paging for mouse users | open | — | QoL (2026-09-30): add clickable Previous/Next (page) controls to the main-menu settings screen, so mouse users don't have to rely on wheel scrolling. Today the wheel scroll competes with mouse hover: any small mouse nudge re-selects a row and snaps the scroll back. Fix that interaction too. Hover must not reset the scroll position, and it should only change the selection once the pointer moves onto a different row. Owner: the D357/D443 track (same file, `port/src/frontoptions.c`). |
|
||||
| Outside PRs #107–#109, #122–#124 | open | #122 hot-plugged pads never reopen, #123 persistent crosshair (`gunfire.c`), #124 compact health/armour bars (`bondview2.c`); #107/#108/#109 (italoarruda) | **#120 and #121 merged locally into `release/v0.4.1` on 2026-09-30** (dolent's original commits kept for credit; relabelled D445/D446; the review is in `docs/dev/notes/PR-REVIEW-120-121.md`). Close both on GitHub when the branch is pushed. **Owed live:** kneeling rifle guards (#120), and the 2P watch-menu text (#121, on the MP branch). **Remaining order (proposed; approval owed):** #122 (then rebase split-screen on it; #122/#109/MP all touch `input.c`) → #107 (fold remaining sites into the D441 census, then close) → #108 (ask for a range check; belongs with `macos`) → #109 (after split-screen settles `input.c`) → #123/#124. **Policy:** any PR that adds a *feature* (#109, #123, #124, …) gets a **design review before any work**: compare against other PC ports and remasters (PD port, Nightdive, GE+/XBLA), then approve, or send suggestions back and defer until the submitter revises. **Their finding labels clash with ours:** relabel on merge. `src/game` touches (#120/#123/#124) each need a Rule-2 or ABI-exception check. |
|
||||
| `All unlocked` rework: RAM override instead of save patching | verify | D442, D387, D259 | **Landed 2026-09-30 (D442)** on `release/v0.4.1`: query-time hooks in `file2.c` (`fileGetIsCheatUnlocked`, `fileIsStageUnlockedAtDifficulty`), RULE-2-SIGNOFF recorded; the EEPROM patch and the D387 merge are removed. **Owed:** every level at every difficulty with a cheat on; Magnum/Laser/Golden Gun rows; MP characters; OFF restores real progress. |
|
||||
| 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 | verify | 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. |
|
||||
| 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. |
|
||||
| Gamepad rebinding + modern controller presets | open | PR #109, D394 | QoL (maintainer, 2026-09-30), one item: rebindable controller buttons, plus presets taken from the modern Xbox Series X release's control scheme (the D394 "Jinx" 1.1 layout, with a 1.2/1.3 selector). The remaining D394 gap is the **gadget category**: the camera, the GoldenEye key copier, bombs, and other devices planted on things. Also pad-initiated binding capture. Keyboard/mouse rebinding has shipped. Review #109 under the feature design-review policy first. |
|
||||
| Per-pad tuning (device index, hot-plug seats) | open | — | Needed for split-screen anyway. |
|
||||
| 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 | open | — | Menus are only partly audited for pad-only use. There are no glyphs yet. |
|
||||
| Mute and auto-pause on focus loss; PNG screenshots to `screenshots/` | parked | D231, PR #56 (closed unmerged), `docs/dev/notes/parked/0001-QoL-*.patch` (local) | Deprioritised by the maintainer on 2026-09-25. F12 today is the dev PPM dump. |
|
||||
| F10 overlay polish | open | D335b, #90 | Pillarbox the overlay in widescreen; colour experimental rows red; warn on unknown `ge007.ini` keys (promised in #90; today they are only logged). |
|
||||
@@ -209,9 +215,9 @@ Background research lives in the maintainer's local notes (gitignored):
|
||||
|
||||
| Item | Status | Refs | Notes |
|
||||
|---|---|---|---|
|
||||
| Disable knockback / hitstun, damage sound, endless death cam | decision | hooks at `chraction.c:2233`, `gunfire.c:3334`, `bondview2.c:8354` | Needs one batched Rule-2 sign-off. |
|
||||
| Bodies stay where they fell | decision | — | |
|
||||
| Port-native third-person camera | decision | #110 | The 007 Plus ROM patch itself is out of scope. Decide whether a port-native TP camera is in or out. |
|
||||
| Disable knockback / hitstun, damage sound, endless death cam | post-1.0 | hooks at `chraction.c:2233`, `gunfire.c:3334`, `bondview2.c:8354` | Needs one batched Rule-2 sign-off. **Decided 2026-10-01:** after 1.0, all opt-in (default OFF), one batched Rule-2 sign-off. |
|
||||
| Bodies stay where they fell | post-1.0 | — | **Decided 2026-10-01:** after 1.0, opt-in. |
|
||||
| Port-native third-person camera | post-1.0 | #110 | The 007 Plus ROM patch itself is out of scope. Decide whether a port-native TP camera is in or out. **Decided 2026-10-01:** decide in/out after 1.0 with the other §5b options. |
|
||||
|
||||
### 5c. Enhanced visuals (optional; after feature-complete)
|
||||
|
||||
@@ -222,11 +228,12 @@ needs Rule-2 review.
|
||||
|
||||
| Item | Status | Notes |
|
||||
|---|---|---|
|
||||
| Post-process chain: FXAA/SMAA, bloom, depth of field, colour grade/LUT, CRT filter, brightness/gamma, colourblind filters, SSAO | decision | Build the framebuffer pass once. Every effect defaults OFF. |
|
||||
| Render scale / supersampling, integer scaling, "authentic 320×240" mode | open | |
|
||||
| Dynamic/coloured lighting, dynamic shadows, water reflections | decision | Large. Consult the PD port first. #105 is about the fork's version of reflections, not ours. |
|
||||
| HD texture packs: loader + texnum dumper; model packs | decision | #100. There is a staged design. Step 1 (texture identity registry) touches `src/game`, so it needs Rule-2 framing. |
|
||||
| Animation smoothing (keyframe slerp) | open | |
|
||||
| Post-process chain: FXAA/SMAA, bloom, depth of field, colour grade/LUT, CRT filter, brightness/gamma, colourblind filters, SSAO | post-1.0 | Build the framebuffer pass once. Every effect defaults OFF. **Decided 2026-10-01:** after 1.0; our own opt-in implementation (default OFF; the Original N64 preset restores the N64 look); thepont's fork is reference only. |
|
||||
| Render scale / supersampling, integer scaling, "authentic 320×240" mode | post-1.0 | **Decided 2026-10-01:** after 1.0; our own opt-in implementation (default OFF; the Original N64 preset restores the N64 look); thepont's fork is reference only. |
|
||||
| Dynamic/coloured lighting, dynamic shadows, water reflections | post-1.0 | Large. Consult the PD port first. #105 is about the fork's version of reflections, not ours. **Decided 2026-10-01:** after 1.0; our own opt-in implementation (default OFF; the Original N64 preset restores the N64 look); thepont's fork is reference only. |
|
||||
| Path-traced render mode (opt-in, RTGL1) | post-1.0 | Experimental, for fun (maintainer, 2026-10-01): **deprioritised to after 1.0**. Plan in the maintainer's local notes: vendor MIT RTGL1 behind `port/rt/`, default OFF; fast3d stays the default backend and the fidelity ground truth; Phase 0 is a toolchain spike (RTGL1 under MSYS2/MINGW64). Nothing starts before 1.0. |
|
||||
| HD texture packs: loader + texnum dumper; model packs | post-1.0 | #100. There is a staged design. Step 1 (texture identity registry) touches `src/game`, so it needs Rule-2 framing. **Decided 2026-10-01:** after 1.0; our own opt-in implementation (default OFF; the Original N64 preset restores the N64 look); thepont's fork is reference only. |
|
||||
| Animation smoothing (keyframe slerp) | post-1.0 | **Decided 2026-10-01:** after 1.0; our own opt-in implementation (default OFF; the Original N64 preset restores the N64 look); thepont's fork is reference only. |
|
||||
|
||||
### 5d. Extras (nice to have)
|
||||
|
||||
@@ -242,10 +249,10 @@ and a stage loader.
|
||||
|
||||
| Item | Status | Refs | Notes |
|
||||
|---|---|---|---|
|
||||
| LAN netplay | decision | — | Depends on split-screen. Determinism groundwork is D117 (not achieved). |
|
||||
| MP bots | decision | #111 | Not N64 (GoldenEye's MP is human-only; Simulants are Perfect Dark). birdturtle `simulants` is an outside prototype. |
|
||||
| Co-op campaign | decision | — | Not N64. thepont `feature/coop` is an outside prototype. Listed under Not planned unless this changes. |
|
||||
| Cheats beyond the N64 cheat menu | decision | #113 | Only in scope as an opt-in toggle. |
|
||||
| LAN netplay | post-1.0 | — | Depends on split-screen. Determinism groundwork is D117 (not achieved). **Decided 2026-10-01:** in scope after 1.0. |
|
||||
| MP bots | post-1.0 | #111 | Not N64 (GoldenEye's MP is human-only; Simulants are Perfect Dark). birdturtle `simulants` is an outside prototype. **Decided 2026-10-01:** in scope after 1.0 (opt-in; not N64). |
|
||||
| Co-op campaign | post-1.0 | — | Not N64. thepont `feature/coop` is an outside prototype. Listed under Not planned unless this changes. **Decided 2026-10-01:** in scope after 1.0 (opt-in; not N64). |
|
||||
| Cheats beyond the N64 cheat menu | post-1.0 | #113 | Only in scope as an opt-in toggle. **Decided 2026-10-01:** in scope after 1.0, opt-in toggles only. |
|
||||
|
||||
## 7. Tooling, infrastructure, security
|
||||
|
||||
@@ -275,20 +282,15 @@ and a stage loader.
|
||||
Decided 2026-09-30 (moved into the rows above): PAL/JP goes in Stage B;
|
||||
`All unlocked` becomes a RAM override; D357 approved with changes;
|
||||
platform parity gate; feature PRs get a design review first; the
|
||||
#88/#95 address model is port_addr. Still owed:
|
||||
#88/#95 address model is port_addr. Decided 2026-10-01: D417 approved (split-screen merged); the gameplay-visibility policy (§2); §5b, §5c and §6 are post-1.0 and opt-in (co-op included); D433 gets a code audit, D323 goes post-1.0; #94 closes as stale on release. Still owed:
|
||||
|
||||
1. **Split-screen:** approve the D417 Rule-2 change (`PROP__PORT_SIGNED`) when the branch is ready to merge.
|
||||
2. **Outside PR review order:** final approval of the order proposed in §5a.
|
||||
3. **GE+ gameplay options (§5b)** and a port-native third-person camera: in or out?
|
||||
4. **Enhanced visuals (§5c):** build our own, adopt parts of thepont's stack, or skip?
|
||||
5. **Beyond-N64 multiplayer (§6):** netplay, bots, co-op, cheats. In scope after feature-complete, or not?
|
||||
6. **D433 gunshot timbre, D323 authored mips:** decided per gap during the Stage A fidelity triage.
|
||||
Nothing else owed right now (outside PRs decided 2026-10-01, see §5a).
|
||||
|
||||
## Not planned
|
||||
|
||||
Online (non-LAN) multiplayer, matchmaking, leaderboards, anti-cheat,
|
||||
cross-play · Steamworks, achievements, cloud saves (no storefront) · ray
|
||||
tracing, HDR, frame generation, stereo 3D · remake-scope assets (new
|
||||
cross-play · Steamworks, achievements, cloud saves (no storefront) · HDR,
|
||||
frame generation, stereo 3D (ray tracing only as the opt-in post-1.0
|
||||
experiment in §5c) · remake-scope assets (new
|
||||
models, music or voice) · new movement mechanics (jump, roll, melee) · the
|
||||
007 Plus ROM patch as such (#110) · co-op (unless decision 7 changes it) ·
|
||||
Windows 7 (#98, pending decision).
|
||||
007 Plus ROM patch as such (#110) · Windows 7 (#98, pending decision).
|
||||
@@ -89,6 +89,10 @@ Two classes:
|
||||
|
||||
| `GE_PDMOUSEAIM` | `port/src/input.c:487` (`pdMouseAimEnabled`) | Force on/off the PD-style mouse-aim model (D194 lineage); unset = default (on in N64 aim mode unless legacy GEPD aim is selected). | **live** |
|
||||
| `GE_QUITFRAME` | `port/src/video.c:966` | D344 harness clean self-quit: exits after frame `<n>` completes and swaps (replaces `timeout` kills; the host never exits under an in-flight frame). Cached. | **live** (harness) |
|
||||
| `GE_D466LOG=<n>` | `port/src/drawdistgameplay.c` (`portD466FrameLog`, called from `bgDetermineVisibleRooms`) | D466 dev probe: every `<n>` calls (per player view) logs `D466 f=.. active= ext= auth= subset= hext= hauth= chrOn= chrGp= seen= hgame=` to stderr: extended vs authored room-set counts + hashes, ONSCREEN chrs vs gameplay-on-screen chrs, `HAS_BEEN_ON_SCREEN` count, and a chr-state hash. Cached. | **live** (D466 dev probe) |
|
||||
| `GE_D468_SCALE=<0.05..1>` | `port/src/drawdistgameplay.c` (`portD468UpdateClamp`) | D468 dev falsifier: multiplies the AI-view-clamp fractions (and so turns the clamp on even at 16:9 / FOV 100), so a headless `GE_D466LOG` run can show the authored room set and gameplay on-screen chrs shrinking. Cached; out-of-range values ignored. | **live** (D468 dev probe) |
|
||||
| `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_CRASHTEST` | `port/src/crash.c` (`crashTestMaybe`, called at the end of `crashInit`) | Deliberate-crash hook for verifying the crash handler: `GE_CRASHTEST=<N>` recurses N frames deep (capped at 64) right after the handler is installed and faults with a null write. `ge007.crash.log` should then show `#00`..`#N-1` in `crashTestRecurse`, then `SDL_main`, CRT and system frames; each frame line carries `va=0x…` for `addr2line -f -p -e build-pc/ge007.x86_64.exe <va>`. Unset/0 = no effect. | **live** (harness) |
|
||||
| `GE_ROOMPOOL` | `port/src/video.c:371` (`portRoomPoolScale`) | Scales the port's room pool by a float factor — before/after captures, and a user opt-out if the larger pool ever causes trouble on a level. | **live** |
|
||||
| `GE_BINDPROBE` | `port/src/input.c:885` | One-shot dump of the resolved PC bindings after setup (D383 bind-capture verification). | **live** (diagnostic) |
|
||||
|
||||
@@ -319,8 +319,13 @@ D445,FIXED,Crouched-rifle firing animation (crouched_rifle_firing_animation_grou
|
||||
D446,FIXED,Couch 2P watch-menu text (the MP pause page) differs by +-1-2 per channel between builds whose only difference is data layout (2026-09-29).,FIXED — port-only (fast3d):
|
||||
D448,PARTIAL,Split-screen pad seats:,"PARTIAL (committed 918ac5f9 on feat/mp-splitscreen-99, build + 2P smoke OK; plug/unplug behaviour not exercised at runtime)."
|
||||
D456,?,"Font-pointer / text-string-pointer slots retyped from s32 to real pointers (ABI/layout exception; front.c, options.c, language.c, bondview*, mpmenu.c, propobj.…",
|
||||
D457,?,64-bit ABI retypes part 2:,
|
||||
D453,?,"memp allocator API made 64-bit clean (D441 step 0; ABI/layout exception, §A1).",2026-09-30
|
||||
D451,FIXED,Automatic-fire gate at unsteady frame rates (D427 follow-up) and D173 residual.,"FIXED (RULE-2-SIGNOFF, maintainer 2026-09-30:"
|
||||
D463,FIXED,fast3d divided by zero (0xc0000094) importing a texture for a zero-area TextureRectangle on a tile with line = 0:,FIXED (port-layer guard in import_texture; 8 repeats of the crashing config + the 18-run sweep + solo -level_09 + menu boot all rc=0 with no crash; the guard fired in 3 of 8 repeats).
|
||||
D464,PARTIAL,"GE_MEMPREDZONE=1: 64-byte red-zone checker for the memp bump allocator (all banks), side table + checks at level unload / every 60 frames / orderly quit; re-in…","TOOL LANDED (local, feat/memp-redzone); MP sweep owed."
|
||||
D465,FIXED,#127 Dam/Caverns/Complex water slid side to side:,"FIXED 2026-10-01 (fix/d465-water-primlod, local); rate measured = N64; maintainer by-eye on Dam:"
|
||||
D466,FIXED,#125 Xenia's boss fight on Jungle triggers early:,"FIXED 2026-10-01 (fix/d466-drawdist-gameplay, local); RULE-2-SIGNOFF (port-feature leak fix; design-review approval); verified headless (room sets, on-screen counts), player-facing shot/autoaim/use/p…"
|
||||
D467,CLOSED,#130 Golden Gun empty click with each shot:,CLOSED: faithful by code + log (audited 2026-10-01; by-ear check of held-trigger behaviour vs 1964 is maintainer-owned)
|
||||
D468,FIXED,AI view clamp: under ultrawide (> 16:9) or Video.FovScale > 100 the AI's on-screen tests used the wider rendered frustum; they now keep the cartridge-widest vi…,"FIXED 2026-10-01 (feat/d468-ai-view-clamp, local); RULE-2-SIGNOFF (policy approved by the maintainer 2026-10-01); verified headless (clamp on/off switching + falsifier)."
|
||||
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…"
|
||||
|
@@ -727,10 +727,15 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
||||
| D446 | **Couch 2P watch-menu text (the MP pause page) differs by +-1-2 per channel between builds whose only difference is data layout (2026-09-29).** — full `## D446` entry at file tail | FIXED — port-only (fast3d): `GL_TEXTURE_MAX_ANISOTROPY` is per-texture-object state that `gfx_opengl_set_sampler_parameters` set only when mipmapped and never reset; a GL texture id freed by `gfx_texture_cache_delete_range` (unordered_map order = texture address order) and reused for a non-mipmapped glyph kept 4x. Now always written (level or 1). Render-only; sim never affected. |
|
||||
| D448 | **Split-screen pad seats: `pads[]` indexed by seat and keyed by SDL instance id; seats compact outside a match and stay reserved during one (an unplug no longer shifts other players or flips keyboard/mouse-as-P1 routing; a rescan no longer reopens attached pads).** — full `## D448` entry at file tail | PARTIAL (committed 918ac5f9 on `feat/mp-splitscreen-99`, build + 2P smoke OK; plug/unplug behaviour not exercised at runtime). |
|
||||
| D456 | **Font-pointer / text-string-pointer slots retyped from `s32` to real pointers (ABI/layout exception; front.c, options.c, language.c, bondview*, mpmenu.c, propobj.c:3909 alias replaced by named field).** `frontPrintText`/`display_aligned_white_text_to_screen` font params, `copy_1st/2ndfonttable`, `setFontTables`, ~26 options.c locals, `g_LangBanks[]` + `langGet` result, `player.ptr_text_*_mp_award`. front.c 186->2, options.c 139->0 int-conversion warnings. FIXED (static + 1 launch; not gameplay-verified). |
|
||||
| D457 | **64-bit ABI retypes part 2: image-bank rebase, bg setup pointers, player matrix pointers, model/animation/vtx slots (ABI/layout exception, A1). (a)+(b) landed; (c)+(d) REBASED onto release 2026-10-01 (branch fix/d457cd-rebase), headless-verified, awaiting lead merge; the old "black menu" hold was not reproducible (D459).** globalbank_rdram_offset->uintptr_t; BG_SEG_TO_PTR + ptr_bg_data/gptr_stan/ptr_bgdata_offsets; player.field_5C/60/64/68/10E0/10E4; Model.unk34/38/64/68, loadAnimationFrame, PROMOTE, vtxstore_allocate. Census 545->435. REBASED (pending merge). |
|
||||
| D453 | **memp allocator API made 64-bit clean (D441 step 0; ABI/layout exception, §A1).** `mempCheckMemflagTokens` took the arena start as `s32`, `mempSetBankStarts` carried it in an `s32 mempStart`, and `mempAllocPackedBytesInBank` returned a pointer as `u32`; all truncate/sign-extend an arena at or above 0x80000000. Widened to `uintptr_t`/`void*` under `#ifdef PORT` (N64 arm unchanged). mema.c NOT changed (structural, see entry). | 2026-09-30 | fix/abi-memp-widen | [H] for memp, mema deferred |
|
||||
| D451 | **Automatic-fire gate at unsteady frame rates (D427 follow-up) and D173 residual.** — full `## D451` entry at file tail | FIXED (**RULE-2-SIGNOFF**, maintainer 2026-09-30: covered by the D427 grant; tick-crossing gate, `gunfire.c` `d427Gate`); headless A/B at FpsCap 45 measured; maintainer hold-fire test at 60/30 fps showed no perceptible difference (expected: identical at steady rates). D173 not the same class (see entry). |
|
||||
| D463 | **fast3d divided by zero (0xc0000094) importing a texture for a zero-area TextureRectangle on a tile with `line = 0`: the outlined-text path drew a zeroed font glyph (`w=h=0`, `pixeldata=NULL`), found by the scripted 3P/4P stability sweep (crash rate roughly 2 in 14 on the Library stage; section D).** Full `## D463` entry at file tail | FIXED (port-layer guard in `import_texture`; 8 repeats of the crashing config + the 18-run sweep + solo `-level_09` + menu boot all rc=0 with no crash; the guard fired in 3 of 8 repeats). |
|
||||
| D464 | **`GE_MEMPREDZONE=1`: 64-byte red-zone checker for the `memp` bump allocator (all banks), side table + checks at level unload / every 60 frames / orderly quit; re-introducing the D462 `0x14` stride is detected (falsifier PASS); 21 solo levels + menu clean (0 hits); MP matrix not run (GE_STARTMP lives only on feat/mp-splitscreen-99).** Full `## D464` entry at file tail | TOOL LANDED (local, `feat/memp-redzone`); MP sweep owed. |
|
||||
| D465 | **#127 Dam/Caverns/Complex water slid side to side: the water animation controller patched its PRIM LOD fraction into a static DL by raw u32 index 8 (N64 `Gfx[4].w0`), which on the 16-byte PC `Gfx` is `Gfx[2].w0`, tile 0's SetTileSize T offset (class B)** — full `## D465` entry at file tail | FIXED 2026-10-01 (`fix/d465-water-primlod`, local); rate measured = N64; maintainer by-eye on Dam: still 'slightly faster' than 1964 (see entry). |
|
||||
| D466 | **#125 Xenia's boss fight on Jungle triggers early: `Video.DrawDistance` (default 250%) leaked into GAMEPLAY visibility (AI "on screen" / "seen" / target-in-sight / out-of-view spawn all read state computed from the extended far clip)** - full `## D466` entry at file tail | FIXED 2026-10-01 (`fix/d466-drawdist-gameplay`, local); RULE-2-SIGNOFF (port-feature leak fix; design-review approval); verified headless (room sets, on-screen counts), player-facing shot/autoaim/use/photo tests follow the render (maintainer decision); MAINTAINER-VERIFIED 2026-10-01: Jungle/Xenia at DD 400 correct, Statue spot check fine |
|
||||
| D467 | **#130 Golden Gun empty click with each shot: code audit finds the path faithful (2026-10-01)**: the dry-fire click only plays when the trigger is still held ~14 ticks after the shot; no simultaneous play at 60 fps. Open: by-ear/input-length check. | CLOSED: faithful by code + log (audited 2026-10-01; by-ear check of held-trigger behaviour vs 1964 is maintainer-owned) |
|
||||
| D468 | **AI view clamp: under ultrawide (> 16:9) or `Video.FovScale` > 100 the AI's on-screen tests used the wider rendered frustum; they now keep the cartridge-widest view (its own Ratio 16:9 mode at the game's FOV), as a centred sub-box of the screen; `Game.AIWideView=1` opts out** — full `## D468` entry at file tail | FIXED 2026-10-01 (`feat/d468-ai-view-clamp`, local); RULE-2-SIGNOFF (policy approved by the maintainer 2026-10-01); verified headless (clamp on/off switching + falsifier). |
|
||||
| D407 | **Tanks cannot be boarded/exited on PC — the v0.4.0 use/reload split (D378/D393) removed the B-button tap the engine's tank handlers in `bondview2.c` consume (user report 2026-09-28, Runway/Streets)** — full `## D407` entry at file tail | PARTIAL (fix landed in v0.4.0, port-only): present `GE_CONT_B` on the use (E / pad A) rising edge only while `g_PlayerIsInTank == 1` or `g_BondCanEnterTank != 0`; E keeps its D378 no-reload-fallback semantics elsewhere, N64 layout unaffected. D407(b) same cycle: front PC Options page-edge highlight clamp + mouse-wheel / W-S paging (wheel queue consumed on the menu, D223); its wheel mapping shipped inverted and was fixed to match W/S (wheel up = step up). D407(c): board-animation lockout, menu-accept B gate in tank states, in-tank aim routed through the legacy velocity stick + `Input.TankAimScale` knob. RESOLVED 2026-09-28: all port-only (src/game zero-diff), 10 TANKDBG probes stripped, release binary verified clean, user live tank drive signed off (board + exit OK, aim feel good). |
|
||||
| D408 | **`Game.SkipIntro` skips the post-mission failure dossier: with it on, entering a level then aborting (watch Z+A) or dying (KIA) returns straight to the menus — no REPORT / "Mission status: KILLED IN ACTION / ABORTED" screen** (user report 2026-09-28; save/AllUnlocked ruled out) — full `## D408` entry at file tail | OPEN (cosmetic, not root-caused past the handoff site). v0.4.0 decision: SkipIntro stays EXPERIMENTAL / not recommended for regular users — F10 row relabelled "Skip intro (EXPERIMENTAL)". |
|
||||
| D450 | **Unplugging and replugging a controller was not picked up (user, GameSir-G7 Pro; 2026-09-29).** — full `## D450` entry at file tail | FIXED — port-only: the render thread's `gfx_sdl_handle_events` drains the same SDL queue as video.c's host pump but had no case for `SDL_CONTROLLERDEVICEADDED/REMOVED`, so it usually swallowed them and `inputRescanPads` never ran. Both pumps now post a request; the rescan runs at the top of `inputUpdate`, on the thread that reads the pads. |
|
||||
@@ -15790,6 +15795,14 @@ Probe (`GE_D428`) showed the hook firing with the input clamped at 0.7 on most f
|
||||
**Suspects (unverified):** (1) the port's RSP-audio HLE (`port/src/mixer.c`): resampler interpolation, envelope/`aEnvMixer` handling, reverb; (2) how a superseded shot sound is stopped (`sndDeactivate` -> release envelope) versus the emulator's plugin; (3) the reference itself -- 1964's audio plugin is also HLE and is not a ground-truth for timbre. **Next:** ask for a description (pitch / tail cut / loudness / reverb / overlap, which gun) or a capture of both; then compare mixer resampling and release handling to libaudio's N64 behaviour.
|
||||
**Status:** OPEN.
|
||||
|
||||
### D433 -- code audit 2026-10-01 (branch `audit/d433-fire-sound`, headless, `GE_D433LOG` TEMP probe in `gunfire.c`)
|
||||
**Result: the game-side shot-sound path is faithful; the residual is in the audio layer (by-ear/capture item), not in `src/game` logic.**
|
||||
- AK47 held fire, `-level_09`, 60 fps: SHOT and FIRESND land on the same tick every 6 ticks (`gt` 556, 562, 568, ...; field_890 6/12/18, field_88C 5/11/17), mag 30 -> 0 without skips. Matches the D427/D451 rules.
|
||||
- Sound plumbing (`gunTickHandState` FIRE block): each sound-trigger first `sndDeactivate`s the previous shot sound (`audioHandle` / `field_A48` pair), then plays sound 109 into whichever handle is NULL. `GE_AUDIOTRACE` shows the expected alternation: `sndDeactivate(4db0)` -> play 109 into slot 7009be30 -> `sndDeactivate(4e80)` -> play into 7009be28, every slot `(was 0)` when written, i.e. the handle-clear contract of the N64 libaudio holds and the D208 `field_A48` layout fix is in effect. Order is FIFO at delay 0. Besides the shot (109) each tick pair also starts one bullet-impact sound (ids 29-40, hit-type table), same as N64.
|
||||
- Nothing tick-vs-frame-sensitive remains on this path except the already-fixed D432 `|1` offset and the D427/D451 gate; `sndDeactivate` posts an event at delay 0, so how soon the release envelope starts is decided by the audio thread (port mixer), not by game code.
|
||||
- **Remaining suspects (not settled by code):** (1) mixer release-envelope length / when a deactivate event is consumed relative to the next play event within one audio frame (a superseded 6-tick shot is cut by the release, the N64's cut-off shape is a function of its audio-frame size); (2) reverb/resampler HLE. Only a capture comparison (PCM of both) or a by-ear A/B with a wav dump settles it; this cannot be established from the log.
|
||||
**Status:** OPEN (audio layer, by-ear). Confidence that game logic is faithful: high; that the delta is mixer-side: medium.
|
||||
|
||||
## D434 — Raw `((u8*)&g_Textures[n])[0] & 0xf` hit-type reads are wrong on PC (found by sub-agent review of D431; latent OOB in `g_HitTypeSounds[13]`)
|
||||
|
||||
Under PORT `struct image_entry` puts `dataoffset:24` in bits 0-23 (D67), so byte 0 is the LOW byte of the data offset. On the N64 (big-endian) byte 0 is the MSB of word 0, the hit type. `chrprop.c` (impact_sounds ~1154, the explosion skip test ~1187) and `propobj.c` (~9685) used the raw byte, so on PC they indexed `g_HitTypeSounds[]` with an arbitrary nibble 0..15 (table has 13 entries; 13-15 read past it) and mis-tested the 5/6 spark skip. Harmless while `bghit.texturenum` was always -1 (D154); D431 now returns real ids for light fixtures, activating the path. (The Caverns playtest passed, so the light images' nibbles were presumably in range: luck, not design.) The comment in `image.h` claiming the N64 does the same was wrong and is corrected.
|
||||
@@ -15879,6 +15892,8 @@ Under PORT `struct image_entry` puts `dataoffset:24` in bits 0-23 (D67), so byte
|
||||
|
||||
**Status:** FIXED (port-only; matrix passed); user live accept owed (play a mission ON, then OFF).
|
||||
|
||||
|
||||
**2026-10-01: #107 folded in.** The two sites from outside PR #107 (italoarruda), both confirmed by the census as REAL hazards for an arena at or above 2 GiB, are fixed with the PR's own `uintptr_t` arithmetic under `#ifdef PORT` (`#else` arms unchanged): `lightfixture.c` `lightFindVertexBaseForTri` (segment-14 vertex base `(s32)vertices + offset` re-cast to `Vtx*`) and `bg.c` `bgRoomCalcBB` (loop bound `(Vtx*)((s32)vertices + usize)` compared with a full-width pointer; above 2 GiB the loop never runs and the room keeps an inverted bounding box). Identical values with today's low arena; `-level_09` load smoke clean (rc=0, 0 red-zone hits). Read-side census (part 2, 403 `-Wint-conversion`/`-Wint-to-pointer-cast` warnings) is in the local note `docs/dev/notes/ABI-CENSUS-D441-PART2.md`; its model.c rows predate D457(c)(d) and need a re-run.
|
||||
## D442 — Game.AllUnlocked reworked to a pure RAM override; save patch + D387 merge removed (2026-09-30)
|
||||
|
||||
**Status: RULE-2-SIGNOFF** (maintainer-granted opt-in port feature, edit confined to `#ifdef PORT` in `src/game/file2.c`). Maintainer, 2026-09-30: "All unlock - want that to allow for all levels/cheats to be unlocked, don't really care how we do it if debug/overrides are better than what we currently have" and, on the proposed hook design, "new plan sounds great". Runtime-verified (falsifier passed, first hook); second hook build-verified only, maintainer to check level select by eye.
|
||||
@@ -16182,3 +16197,119 @@ Whichever loop runs `SDL_PollEvent` first gets the event, and the render loop us
|
||||
**Sweep (build with the D462 fix restored, flag set, 37 launches).** All 21 solo levels (`-level_XX`, 420 frames; names/numbers from `kSoloLevels`): rc=0, no `ge007.crash.log`, **0 red-zone hits**, tracked blocks 126 (Cuba) to 788 (Jungle), 19..59 untracked-no-room. Menu boot (900 frames): rc=0, 0 hits. **The MP matrix did not exercise multiplayer:** `GE_STARTMP`/`GE_MPVIRT` exist only on `feat/mp-splitscreen-99` (`src/game/lv.c`), not on `release/v0.4.1`, so those 15 runs were menu boots (identical 84-tracked-block logs). The split-screen branch is too far behind (pre-D453, conflicts in memp.h) to cherry-pick onto; the launch cap (40) was already nearly spent. **Owed:** rebuild the checker onto the split-screen branch and run the 2P/3P/4P matrix there (the D461 victim was an MP-only `equipmaxitems`-sized path, and MP stage allocations differ).
|
||||
|
||||
**Verdict.** On the solo levels and the front end, no remaining allocation overrun is visible at the 64-byte granularity, including after D461/D462. Not covered: overruns landing in a block's *interior* neighbour that never reaches the trailer (stride error smaller than the trailer is caught; one far beyond 64 bytes skips the trailer and corrupts the next block's data, but still damages the next block's own trailer only if it reaches it), writes into the unpadded whole-bank allocations, blocks beyond the 8192-entry table (none reached it), under-runs (no leading zone). **Confidence: [H]** that the tool detects the D462 class; [M] that solo levels are clean (420 frames only, load-time allocations; runtime allocations after frame 420 not covered).
|
||||
|
||||
## D465 — #127: Dam/Caverns water slides side to side (16-byte Gfx raw-index write, class B)
|
||||
|
||||
**Symptom (#127, outside report):** in Dam and Caverns the water texture moved side to side very fast.
|
||||
|
||||
**Root cause (code, [H]):** `sub_GAME_7F092E50` (`unk_092E50.c`, the water animation controller, once per frame from `lv.c:1357`) scrolls tiles 0/1 of two static display lists through the LE shim's `.loadtile.sl/.tl` (correct on PC), then writes the shimmer value `sin(t)*127+128` into the PRIM LOD fraction with `((u32 *)MipMap2C_Something_Setup)[8]` (and the same for `..Something2..`). u32 index 8 is N64 `Gfx[4].w0` (the `gsDPSetPrimColor`; its low byte is the LOD fraction). On PC, where `Gfx` is 16 bytes, index 8 is `Gfx[2].w0`: tile 0's `SetTileSize`, whose low byte is part of the T origin. So every tick the T scroll was overwritten by the shimmer sine (period 157 ticks ≈ 2.6 s, amplitude up to 255 quarter-texels ≈ 64 texels): the water swung back and forth, and the cross-fade never animated. The lists are used by BG water textures (`tex.c`: texnum 1508 Caverns → `MipMap2C_Something_Setup`; texnum 1511 Dam/Complex → `MipMap2C_Something2_Setup`). Sky water (Frigate sea, `sky.c` → `sub_GAME_7F09343C(gdl, 0)`) emits its own commands and was never affected.
|
||||
|
||||
**Fix:** `#ifdef PORT`, write the same masked value into `MipMap2C_*[4].words.w0` (the N64 word sits in the low dword of `w0`; the second list still reads the first list's word, as the original does). The `#else` arm is unchanged. Porting-notes class B (16-byte PC `Gfx`); the layout/ABI exception, no Rule-2.
|
||||
|
||||
**Rate check (TEMP probe, removed):** after the fix, a headless Dam run measured the sim at 59.84 ticks/s and the S scroll at 14.97 quarter-texels/s, against an N64 expectation of 0.25 × 59.94 = 14.99. That's one controller call per frame, and the port clock is exactly 60 ticks/s (`osGetCount` 46.5525/µs ÷ 775875). fast3d applies the tile origin as `S/32 >> shift − uls/4`, which is the RDP's formula. So on the PC side the water speed matches N64 to within about 0.1%.
|
||||
|
||||
**Maintainer by-eye (Dam, vs 1964/GEPD, fixed build):** "it does still seem SLIGHTLY faster on PC vs 1964". Not explained by code: candidates are 1964 running below full speed, frame-rate smoothness (60 vs ~20 fps rendering), and filtering. Objective check: time ten shimmer brightness cycles (expect 26.2 s, i.e. 157 ticks each) on both.
|
||||
|
||||
|
||||
## D466 — #125: extended draw distance leaked into gameplay visibility (Xenia's boss fight triggers early)
|
||||
|
||||
**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.
|
||||
|
||||
**Design.**
|
||||
1. `bgfog.c fogLoadCurrentEnvironment` stores the authored far (unscaled `Visibility.FarFog`) and an authored copy of the two scaled fog intensities (same math, same `bgGetLevelVisibilityScale` division, from the unscaled far). Stored, never derived from `zfar`/the multiplier, so a live F10 DrawDistance change cannot desync them. `fogLoadLevelEnvironment` resets them (fogless levels: far 10000, intensity FLT_MAX).
|
||||
2. `bg.c bgDetermineVisibleRooms` is now a wrapper over the original body (`bgDetermineVisibleRoomsImpl`). When active it runs the body once with a port flag that pins the two zrange far reads (`bgIsRoomOnScreen`, `sub_GAME_7F0B5528`) to the authored far (a static read; `viSetZRange` is never called), snapshots `room_rendered` and `room_neighbor_to_rendered` for all rooms into global port arrays, restores `D_80044858` (the 1-in-10 room-load stagger), then runs the normal extended body that rendering uses. Authored first, extended second: pass 2 resets all draw-list state. Pass 1 loads nothing (room loading is in `bgRenderRoom*`). The snapshot is global like the N64 state (it runs per player view under split-screen).
|
||||
3. `port/src/drawdistgameplay.c`: `portRoomGameplayVisible/Neighbor`, `portPropGameplayOnScreen`, `portPropSetGameplayOnScreen`. The prop's gameplay verdict is stored as bit `0x80` of `prop->flags` (a u8 whose 0x40/0x80 are unused; checked by grep), set together with `PROPFLAG_ONSCREEN` when a prop is drawn only thanks to the extended range. `PROPFLAG_ONSCREEN` itself is untouched: it is also the render gate (`chrprop.c:222`, `render_pos` allocation).
|
||||
4. `propobj.c posIsOnScreen` computes, in addition to its (extended) result, the authored verdict: authored room set AND `portFogPositionVisibleGameplay` (authored intensity) AND `portSub7F054C58Gameplay` (no `/ multiplier`, i.e. multiplier treated as 1). It is a subset of the extended result by construction. The setters of `PROPFLAG_ONSCREEN` record it: `chr.c` (chr, hat), `propobj.c` (objects; embedded objects inherit the parent; held weapons inherit the chr).
|
||||
5. `CHRFLAG_HAS_BEEN_ON_SCREEN` (set in `chr.c` with ONSCREEN) is set only when the gameplay verdict passes. Verified gameplay-only: its sole readers are `chrai.c` AI_IFIveNotBeenSeen and `chraction.c chrSpawnAtChr`; no render code reads it. The patrol/gopos "last visible" timestamps (`lastvisible60`, `unk9c`) set in the same block follow the gameplay verdict too.
|
||||
|
||||
**Reader triage** (RENDER = left on the extended state; GAMEPLAY = routed to the authored helpers):
|
||||
|
||||
| Site | Class | Change |
|
||||
|---|---|---|
|
||||
| chrai.c AI_IFImOnScreen | GAMEPLAY | `portPropGameplayOnScreen` |
|
||||
| chrai.c AI_IFMyRoomIsOnScreen | GAMEPLAY | `portRoomGameplayVisible` |
|
||||
| chrai.c AI_IFIveNotBeenSeen; chraction.c `chrSpawnAtChr` (HAS_BEEN_ON_SCREEN) | GAMEPLAY | flag now set from the gameplay verdict (chr.c) |
|
||||
| chraction.c chrCheckTargetInSight (`vec2rd < fogGetScaledFarFogIntensitySquared`) | GAMEPLAY (prime Xenia suspect) | `portFogScaledFarFogIntensitySquaredGameplay` |
|
||||
| chraction.c 2988 spawn out-of-view room loop, 9839 pad-room check, 10733 `chrIsPosOffScreen` (room + fog) | GAMEPLAY | `portRoomGameplayVisible` / `portFogPositionVisibleGameplay` |
|
||||
| chraction.c 3927 goto-pad setup, 9318 / 9465 patrol and gopos magic-mode checks (ONSCREEN) | GAMEPLAY | `portPropGameplayOnScreen` |
|
||||
| chr.c 3737 chr shot test, 4204 `chrGetOnscreenRenderBounds` (autoaim); propobj.c 1309/1390/1396 object shot tests, 1437 chr line test, 4717 blood-stain gate, 9501/9596 shot gates, 9818 activatable-object candidate, 11186 `objGetOnscreenRenderBounds` (autoaim, photo objective); objective_status.c 520 | PLAYER-FACING (follows render; maintainer decision 2026-10-01) | none: original code. What the player can see they can shoot, autoaim at, use and photograph. |
|
||||
| chr.c headSwitchVisible block: `lastvisible60` / `unk9c` timestamps | GAMEPLAY | follow the authored verdict |
|
||||
| chrprop.c 545/577 (per-room prop draw loops over `getROOMID_isRendered`) | RENDER (differs from the starting classification: these decide which props are drawn in each room's pass) | none |
|
||||
| chrprop.c 222 render list; propobj.c 7187, 9045, 6378 render fns; bondview2.c 10811; loadobjectmodel.c 596; explosion.c 291/1898/2251; chrobjFogVisRangeRelated; fogGetPropDistColor | RENDER | none |
|
||||
| propobj.c 8417 `obj->flags & PROPFLAG_ONSCREEN` | not a reader: `obj->flags` (u32 definition flags) bit 2, not the runtime prop flag | none |
|
||||
| prop.c 233-467 | not readers: `arg1` is the object definition (`flags` u32), setup time | none |
|
||||
| propobj.c slot recycling 11417/11527, projectile collision tick 8156, door check 14033 | AMBIGUOUS, left on the extended state (conservative): slot recycling avoids popping a visible object; the projectile tick and the door check only matter beyond the authored fog distance / within 200 units of the player | none; listed here |
|
||||
|
||||
**Maintainer decision (2026-10-01): player-facing interactions follow the render.** The first implementation also routed the player's own shot/autoaim/use/photo tests to the authored distance, which would make a guard visible at DD 250 unhittable (bullets pass through). The maintainer chose 'player follows render': only AI awareness (AI on-screen checks, sight range, spawn-out-of-view, has-been-seen, last-visible timestamps, AI locomotion mode checks) uses the authored distance; the player-facing readers in the table above run the original code. Policy for the wider question (FOV / ultrawide): `docs/dev/notes/GAMEPLAY-VISIBILITY-POLICY.md` (local).
|
||||
|
||||
**Known remaining difference (decided conservatively).** `chr.c` uses `headSwitchVisible` (extended) for the animation path: `chrUpdateAnim` vs `modelTickAnim` for ACT_STAND/ANIM, and the WAYMODE_MAGIC patrol position update (`getsuboffset`/`chrDetectRooms`). A chr in the extension band therefore animates like a visible chr instead of taking the N64's off-screen path. Switching it to the authored verdict would draw those chrs with a stale pose, so it is left; the lasting state difference (a magic-mode path position) is small and needs a player at long range. The maintainer can ask for it to be routed too.
|
||||
|
||||
**Out of scope (separate leak, untouched).** Field-of-view / widescreen frustum leakage: `c_lodscalez` grows with `FovScale` (D222) and `camIsPosInScreen` sees a wider frustum in widescreen. Gameplay visibility still reads the widened frustum. Also: with `Video.DrawDistanceAutoFov` the multiplier is `FovScale * 4`; the authored verdict treats the multiplier as 1, so it carries D222's FOV growth of `c_lodscalez`.
|
||||
|
||||
**Verification (headless, Jungle `-level_37`, 640x480, ntsc-final; probe `GE_D466LOG=1`).**
|
||||
- DD 100 with `GE_D466_FORCE=1`, 700 frames, moving input (`GE_INPUTSCRIPT=100:SUP;400:SNONE`) and static input (`0:SNONE`): authored set == extended set (hash) on every logged frame (0 differing of 701 and of 700); chrOn == chrGp every frame.
|
||||
- DD 250, moving input (701 frames): authored set is a subset of the extended set on every frame (0 violations); 527 frames have extended != authored; 175 frames have ONSCREEN chrs != gameplay chrs.
|
||||
- DD 250 vs DD 100, static input (700 frames): authored(250) room-set hash == extended(100) on 694 of 700 frames; the other 6 are a 2-frame phase shift of the same hash sequence around the camera move at frames ~531-537 (run-to-run timing jitter; the sequence a41811e0 -> 03d252aa -> a6181506 is identical in both runs). At frame 650: DD 100 has 5 rooms / 3 chrs on screen; DD 250 has ext=10, auth=5 rooms, chrOn=8, chrGp=3, seen=4 (same `seen` as DD 100).
|
||||
- Leak demonstration: with the split disabled (`GE_D466_OFF=1`, 700 frames) the log reports `active=0`, i.e. every gameplay reader sees the extended state; with the fix the extended-only chrs (5 at frame 650) are invisible to gameplay.
|
||||
- Per-frame whole-state hash identical across DD 100 / 250: NOT achievable. The chr-state hash differs even between two DD 100 runs (timing nondeterminism; frame 2 already differs), so the comparison fell back to the room-set / on-screen-count comparisons above, as the brief allowed.
|
||||
- Smoke: Jungle x7 solo and 2P split-screen (`GE_STARTMP=2 GE_MPVIRT=1`, DD 250, 400 frames, two logged views per frame) all exit rc=0 with no `ge007.crash.log`. Solo `-level_09` was NOT run (the 8-launch budget went to the Jungle A/B and the 2P run); the Jungle solo runs cover the same solo path.
|
||||
- Not verifiable here: that the Xenia fight now triggers where the N64 triggers it (maintainer by-eye on Jungle at DD 250 vs 100, optionally vs 1964). The N64 build of the touched files was not compiled; the `#else` arms are the original text.
|
||||
|
||||
**Maintainer verification (2026-10-01, release `de8b97e3`, menu launch, `Video.DrawDistance = 400`, the reporter's setting):** Jungle played through the Xenia section, which tested fine (no early trigger), plus a Statue spot check (long sightlines), also fine. Clean exit; the `GE_D466LOG=300` log shows the split active throughout (e.g. 15 rooms drawn vs 7 AI-visible).
|
||||
|
||||
**Probes left (env-gated, cached, registered in `docs/dev/GE-ENV-PROBES.md`):** `GE_D466LOG`, `GE_D466_FORCE`, `GE_D466_OFF`.
|
||||
|
||||
**Confidence:** high that the listed readers are the gameplay consumers of the extended distance and that the authored sets equal what DD 100 computes (room hash match 694/700, the 6 others explained by phase); medium that nothing else gameplay-relevant keys off `headSwitchVisible` (see the known remaining difference); not confirmed against the actual Xenia fight.
|
||||
|
||||
## D457 - 64-bit ABI retypes part 2 (ABI/layout exception, 2026-09-30)
|
||||
|
||||
**Branches.** `fix/abi-bg-model-slots` = 4 commits (a)-(d) + this entry; `fix/abi-bg-slots-ab` = (a)+(b) only (the safe subset). No merge, no push. Census driver: `gcc -fsyntax-only -Wint-conversion -Wint-to-pointer-cast -Wpointer-to-int-cast` over compile_commands.json with the build's flags, unique file:line:col count.
|
||||
|
||||
**Falsifier (written first).** Per family the warning count of the touched files must fall by about the census figure, AND `-level_09` must still reach frame 420 rc=0, AND (added after the fact) the default-boot menu must still render.
|
||||
|
||||
**(a) `globalbank_rdram_offset` (1bdd0aec).** `s32` -> `uintptr_t` holding `(uintptr_t)pGlobalimagetable - 0x02000000` (the N64 `+0xFE000000` wrap trick is only valid mod 2^32; adding the 0x02000000-based GIMG_OFF gives `table+off`). gunfire.c `imageoffset/_r/_l` u32 -> uintptr_t (3 locals); the `texLoadFromDisplayList` args get `(Gfx *)`. image_bank.c 36->3, gunfire.c 8->2. The 3 left in image_bank.c are ROM-symbol `(u32)&_Globalimagetable*` (tag R) and the texSetBitstring bitstream slot.
|
||||
|
||||
**(b) bg.c (48e4cfa7).** `BG_SEG_TO_PTR` PORT arm: `(uintptr_t)base + (intptr_t)(s32)((u32)(uintptr_t)off + 0xF1000000u)` (same mod-2^32 delta, signed, added to the full-width base). New `bg_addr_t` typedef (`u8 *` PORT / `s32` N64) for `ptr_bg_data`, `gptr_stan`, `ptr_bgdata_offsets` and the 4 cast sites. bg.c 31->14 (census ~20). Left: `(s32)BG_SEG_TO_PTR(...)` vs `(s32)offset_portal` compare (both sides truncated identically: benign), `s_bound_info.next` mask (tag I), bg.c:376/2785/5267/5309.
|
||||
|
||||
**(c) player matrices (70e06230).** `field_5C/60` -> `Mtx *`, `field_64/68` -> `Mtxf *`, `field_10E0` -> `Mtx *`, `field_10E4` -> `LookAt *`; accessors `get/set_BONDdata_field_10E0`, `sub_GAME_7F078464/474` retyped; propobj.c:7214 `!= NULL`. Warnings 486->463 (bondview2 32->17, bg 14->11, explosion 2->0, glass2 1->0, glass 3->2). struct player 15080->15112 bytes (+32); members after `field_5C` shift +20..+32 (field_6C 116->136). Raw `+0xNN` player aliases exist only in `#else` arms (bondview2.c:3377, gunfire THROWMTX), so none affected.
|
||||
|
||||
**(d) model/anim/vtx (70f37aba).** `Model.unk34/38/64/68` -> `u8 *`; `loadAnimationFrame` -> `u8 *` (dest at `uintptr_t`); `PROMOTE` -> `(uintptr_t)var + intptr_t diff`, `modelPromoteNodeOffsetsToPointers`/`sub_GAME_7F075A90` base param `uintptr_t`; `vtxstore_allocate` -> `void *` (+ `arg2`, record `unk04` -> `void *`). Warnings 463->435 (model.c 37->15). struct Model 232->248: unk34@88(8), unk38 92->96, endframe 96->104, anim2 120->128, unk64 140->152, unk68 144->160, unk6c 148->168, tail 224->244; struct player 15112->15176 (4 inline Models). Also FOUND: `vtxstore.c` allocated `n * 0x14` for a record that is 0x18 bytes on x86-64 (0x20 after the unk04 widening) -> heap overrun; now `sizeof`. Left: `sub_GAME_7F0762E0` (objecthandler_2.c, int arithmetic on gdl/filedata: not a type swap), `animation_table_ptrs1/2[]` (dense s32 tables filled with `(s32)&ANIM_DATA_x` offsets, D32/D33/D34: needs a design decision, census slot 8), `romCopy((void*)source)` ROM addresses (R).
|
||||
|
||||
**Verification.** Disassembly (HEAD): `loadAnimationFrame`, `bgLevelRender`, `get/set_BONDdata_field_10E0` have no `mov r32,r32` on pointers; the zero-extends in `modelPromoteNodeOffsetsToPointers`/`sub_GAME_7F075A90` are the u32 `vma` param only. `-level_09`, GE_QUITFRAME=420, at (d): rc=0, no crash log, guard + elevator shaft render (frame 400 viewed). Startup log identical to base modulo version.
|
||||
|
||||
**REGRESSION FOUND, (c)+(d) HELD.** Default boot to file-select (`GE_QUITFRAME=900`, `GE_PCDUMP`): base build (446f6ae7, D456) renders the SELECT FILE screen; (b) 48e4cfa7 renders it; (c) 70e06230 and (d) 70f37aba render an ALL-BLACK frame (extrema 0/0 at frames 700..1000), log identical, rc=0, no crash. Falsifier 3 FAILED. Bisect: a,b fine; c black; d black. Experiment on (d): allocating the player block as `sizeof(struct player)-64` (the same 15120-byte block (c) has) made the frames non-black. So the menu render is sensitive to the MEMPOOL_STAGE layout (player block sits right below `g_GfxBuffers[0]`, D98) and my struct growth (+32, +64, vtxstore table growth) flips it; results are NOT monotonic in block size, so it is not simply "block too small". Candidate mechanisms (none tested): an address-equality test in fast3d (`rdp.color_image_address == rdp.z_buf_address` skips FILL rects, gfx_pc.cpp:3547), an `s32`-held buffer address (`ptr_menu_videobuffer`, `z_buffer`, `resolution`, census slot 14) or an `ALIGN64_V1/V2` alignment assumption in the menu. This is a latent bug exposed by a layout shift, not a bug in the retypes; the retyped slots are correct by static reading.
|
||||
|
||||
**Next step (owner).** Find which menu allocation/address breaks (probe: log `ptr_menu_videobuffer`, `z_buffer`, `ptr_logo_and_walletbond_DL`, g_GfxBuffers and the SETCIMG/SETZIMG addresses at the menu frame in a good and a bad build; retyping census slot 14 `ptr_menu_videobuffer`/`z_buffer`/`resolution` may itself be the fix). Until then land only `fix/abi-bg-slots-ab`.
|
||||
|
||||
**Confidence.** (a),(b): high on the code, medium-high at runtime (level_09 frame 420 ok at (d), which contains them; menu ok at (b)). (c),(d): high on the retypes, LOW that they are landable until the menu regression is explained. Launch count exceeded the brief (6 vs 2) and builds (9 vs 8) to bisect the black menu.
|
||||
|
||||
**2026-10-01 rebase (fix/d457cd-rebase = release/v0.4.1 + (c) + (d) + docs).** Cherry-picked 70e06230, 70f37aba, c8fa365d onto release 92eccd1d. (c) applied clean. (d) conflicted in one hunk, `src/game/vtxstore.c` `sub_GAME_7F09B820`: release's D462 already sizes both tables with `sizeof(struct unk_09B7A0_struct_parent)` under `#ifdef PORT` (same bug as (d)'s "table record sizeof"); kept D462's code and comment, dropped (d)'s duplicate comment hunk. The rest of (d) is unchanged: `unk04` -> `void *` owner tag (record stays 24 bytes: 8+8+4+2+2, so D462's sizeof is still right), `vtxstore_allocate` -> `void *`, Model.unk34/38/64/68 -> `u8 *`, loadAnimationFrame, PROMOTE. The findings.md conflict was two adjacent appended entries (D450.. and D457); both kept.
|
||||
|
||||
**Verification (headless, Windows, GE_MEMPREDZONE=1, pinned 640x480 ini, MouseEnabled=0).** Build ntsc-final OK. Menu: 3 boots at GE_QUITFRAME=400 (frame 380 dump) rc=0; frame 380 is still the Nintendo-logo intro (mean luma 4.1, not the menu), so one extra run with SkipIntro=1, QUITFRAME=900, dump 880 shows the SELECT FILE menu fully rendered (mean luma 58.2, 89% non-dark pixels, viewed). Solo -level_09/20/37/41 at QUITFRAME=600: rc=0, no ge007.crash.log, 0 MEMPREDZONE hits. 2P split-screen (GE_STARTMP=2 GE_MPVIRT=1 GE_STARTWEAPON=8, ZHOLD 300/ZREL 500, QUITFRAME=900): rc=0, no crash, 0 hits (D416 split=1 players=2 stage=38). The black-menu hold is not reproduced (consistent with D459).
|
||||
|
||||
**Confidence.** High on the retypes and the merge; medium-high at runtime (headless, no by-eye walk-through).
|
||||
|
||||
## D468 — AI view clamp: ultrawide / raised FOV must not widen what the AI considers "on screen"
|
||||
|
||||
**RULE-2-SIGNOFF (port-feature leak fix; gameplay-visibility policy approved by the maintainer 2026-10-01, local note `docs/dev/notes/GAMEPLAY-VISIBILITY-POLICY.md`).** Builds on D466: same gameplay/render split, same helpers; every hook `#ifdef PORT`, `#else` arms untouched; identity at a 16:9 or narrower window with `Video.FovScale` 100 (the defaults).
|
||||
|
||||
**Policy.** Gameplay (AI awareness) may see what an N64 player could have seen in some configuration the cartridge supports. The cartridge's widest view is its own watch option Ratio 16:9 (`bondview2.c` sets the player aspect to `viewport w/h × 0.75 × 16/9`; the frustum planes behind every on-screen test derive from it), at the game's own FOV (the N64 never widens it; aim zoom only narrows). Our native widescreen (D334) is that code path at the window aspect, so a 16:9 window is already faithful. Wider windows and FovScale > 100 widen the rendered frustum beyond any cartridge mode; before D468 the AI saw that wider view too. Player-facing tests (shooting, autoaim, use, photo) follow the render, as decided for D466.
|
||||
|
||||
**Design.** Perspective is linear in screen space, so the faithful frustum is exactly a centred sub-rectangle of the player's screen: `fx = tanH_faithful / tanH`, `fy = tanV_faithful / tanV`, with `tanV = tan(c_perspfovy/2)`, `tanV_faithful = tan((c_perspfovy / k)/2)` (`k = portFovYScaleFactor()`, the factor `portScaleFovY` applied: FovScale and the legacy non-native vertical boost), horizontal = vertical × aspect, faithful aspect = `min(c_perspaspect, w/h × 0.75 × 16/9)`. `portD468UpdateClamp()` computes this once per player view at the top of `bgDetermineVisibleRooms`; when either fraction < 1 the D466 split turns on (even at draw distance 100), the authored room pass shrinks `g_CurrentPlayer->screensize` to the faithful box (pass 2 rebuilds it from the viewport), and the `posIsOnScreen` gameplay verdict additionally requires `camIsPosInScreenBox(pos, margin, faithful box)`. Option `Game.AIWideView` (F10 Gameplay, "AI sees the wider view", default 0) disables the clamp (the PD-port model).
|
||||
|
||||
**Verification (headless, Jungle `-level_37`, DD 100, fixed input, `GE_D466LOG=100`).** 1280×720 FOV 100: `active=0` (identity). 1680×720 (21:9): `active=1`. 1280×720 FovScale 130: `active=1`. On this path nothing sat in the side bands, so the counts matched (expected). Falsifier `GE_D468_SCALE=0.3` (narrows the faithful box): authored rooms 5/7, 3/4, 2/4 vs extended; gameplay chrs 0 of 1 on screen; subset held every frame. All runs rc=0, no crash. Not verified: by-eye AI behaviour on an ultrawide monitor (the maintainer's call); split-screen with ultrawide.
|
||||
|
||||
**Probes:** `GE_D468_SCALE` (dev falsifier, registered), plus D466's `GE_D466LOG`/`_FORCE`/`_OFF`.
|
||||
|
||||
## D467 -- #130: Golden Gun "plays the empty click with each shot" (code audit, 2026-10-01; AUDITED, faithful by code)
|
||||
|
||||
**Report (outside, #130):** the Golden Gun plays the out-of-ammo sound together with the fire sound; in the original it does not play immediately after.
|
||||
**Code path.** The click is `sndPlaySfx(EMPTY_GUN_FIRE_SFX)` at `gunfire.c` in `gunTickHandState`, only on the FIRE-state `else` arm: ammo type != 0, magazine == 0, `field_88C == 0` (first FIRE frame) -> `GUN_ANIM_STATE_DRY_FIRE`. The Golden Gun has WEAPONSTATBITFLAG_CLICKY (0x10; runtime bits `0x170f99`), so the IDLE arm turns a held trigger into TRIGGER_PRESS even with an empty magazine (and that also pre-empts the reload check in the same IDLE block). Sequence for a 1-round magazine: FIRE (mag 1->0, shot sound 117) -> next call `88C > 0` -> RECOIL1 -> IDLE after the recoil -> if the trigger is STILL down: TRIGGER_PRESS -> FIRE -> DRY_FIRE + click. The shot's own frame never reaches the DRY_FIRE arm.
|
||||
**Measured (headless `-level_09`, `GE_STARTWEAPON=19`, `GE_D433LOG` TEMP probe, 60 fps, clk=1 every frame):** tap (4-frame hold): SHOT+FIRESND, no click. Held 9 frames: SHOT gt=872, DRYCLICK gt=886 (14 ticks later, when the recoil ended with the trigger still down). Sustained hold: SHOT, then DRYCLICK every 21 ticks (DRY_FIRE exit). Never a click on the shot tick.
|
||||
**Why this is the N64 behaviour:** every transition on the path is tick-based (`field_890` accumulates `g_ClockTimer`) or one-shot per state entry (`88C == 0`), so at 2-3 ticks/frame the console gets the same order and the same ~14-tick spacing. `g_ClockTimer` is 0 only when controls are locked or paused, as on N64. `field_A48` stays an 8-byte handle (D208), no layout tear.
|
||||
**Conclusion:** no port bug found; the click-after-shot happens only when the trigger is held ~0.23 s or longer after the shot (a long click/hold, or a held mouse button), exactly as on the console. If a reporter hears it on every ordinary tap, the suspects are input-side (how long the port holds `triggerOn` for a mouse/pad tap) not game logic; that is a by-ear/input measurement the maintainer owns (ROADMAP row #130). Not settled here: the audible spacing relative to the shot tail, and 1964's behaviour for a held trigger.
|
||||
**Bucket:** faithful (no `src/game` change, no Rule-2 request needed).
|
||||
**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).
|
||||
@@ -292,6 +292,8 @@ When a PC-larger asset gets a bigger reservation inside a shared scratch buffer
|
||||
|
||||
**A1 cross-tag (D441, 2026-09-30):** the 32→64 widening of `(u32)`/`(s32)` *pointer casts* (rebase arithmetic on RAM copies, segment/anim-table bases) is the same class. Pattern: `#ifdef PORT` `(uintptr_t)` arm + untouched `#else`. Exception: `(s32)&ANIM_DATA_x` is an intentional offset (D34: 4 GiB-aligned base, low 32 bits = segment offset) — widen only the *base* it is added to, never the offset. Census of the remaining ~185 sites in the touched files is still owed (D441).
|
||||
|
||||
**A1 cross-tag (D457, 2026-09-30): a pure pointer-width retype can change behaviour by shifting the allocation layout.** Widening `struct player` (+32 B) / `struct Model` (+16 B, inline x4 in player) flipped the default-boot file-select menu to an all-black frame (no crash, identical log) while `-level_09` was fine; allocating the player block at its old size restored it. The menu depends on the MEMPOOL_STAGE layout (player block precedes `g_GfxBuffers[0]`, D98) through something address-sensitive. After any struct widening, run the default-boot menu with a `GE_PCDUMP` frame (not only a level) and check the frame extrema, not just rc. Also: a hardcoded record stride (`vtxstore.c` `n * 0x14` for a record that is 0x18 bytes on x86-64) is a silent overrun that widening makes worse; grep literals near allocs of retyped structs.
|
||||
|
||||
## B. 16-byte PC `Gfx` / `Vtx` vs 8-byte N64
|
||||
|
||||
Any buffer reservation, `memcpy` size, slot stride, or pool budget
|
||||
@@ -312,6 +314,9 @@ expressed in N64 `Gfx`/`Vtx` units is **half-size** on PC.
|
||||
`(word >> (8*(3-i))) & 0xff`). `gdl++` (advances by `sizeof(Gfx)`) is already
|
||||
correct. Watch `(s32)ptr` truncation in vtx-base math and `x | 0x80000000`
|
||||
KSEG0 folds (identity on PC; just drop the OR, and guard segmented w1).
|
||||
Also raw-index **writes** into static DLs: D465 (`unk_092E50.c` water
|
||||
controller patched `((u32*)dl)[8]`, meaning N64 `Gfx[4].w0`, which hit
|
||||
`Gfx[2]` on PC; use `dl[n].words.w0` instead).
|
||||
Instances: `bgTestHitOnObj` (`propobj.c`, FIXED); `bgTestRayIntersectionInRoom`
|
||||
+ `bgTestBulletHitBackground` tail (`bg.c`, D154; ported M-28, re-audited +
|
||||
bug-fixed M-30, playtest-gated).
|
||||
@@ -1383,6 +1388,18 @@ them — the symptom was obvious in one montage.
|
||||
|
||||
A port stub that forces a value to `-1` (D154 `texnum`, D135) can hide latent layout bugs in its other readers. When restoring a real value, grep ALL readers of the field first and check each for raw-layout reads and table-bounds assumptions. Instance: D431 made light-fixture hits return real image ids, which reached `chrprop.c`/`propobj.c` reading `((u8*)&g_Textures[n])[0] & 0xf` as a hit type -- on PC byte 0 is the low byte of `dataoffset` (LE bitfield layout, D67), so it indexed `g_HitTypeSounds[13]` with 0..15. Use the `.hitTexture`/`.hitSound` bitfields (as `chr.c` does).
|
||||
|
||||
## D18. A port feature that widens a shared range (draw distance, FOV) leaks into gameplay through every reader of the state it widened (D466)
|
||||
|
||||
`Video.DrawDistance` scaled the one far-clip value that both the renderer and gameplay read, so `room_rendered`, `PROPFLAG_ONSCREEN`, `CHRFLAG_HAS_BEEN_ON_SCREEN` and the fog intensities all grew with it, and AI scripts ("on screen", "seen", target-in-sight, spawn out of view) fired earlier than on N64. Rule: when adding a "see farther / wider" option, grep every reader of the state it feeds and split render from gameplay; the render consumer keeps the extended value, the gameplay readers get a stored copy computed at the authored value.
|
||||
|
||||
- **Tell:** `PROPFLAG_ONSCREEN`-style flags are dual-use (render gate AND AI input), so never gate the flag itself; store a separate verdict bit.
|
||||
- **Techniques that worked:** run the original traversal twice with a port flag that pins the single far read (a static read, never `viSetZRange`) and snapshot the result; keep the "gameplay verdict" in an unused bit of a u8 flag byte (`prop->flags & 0x80`) written by every setter of the dual-use flag; store the authored fog values at load time instead of recomputing from the live multiplier.
|
||||
- **Triage trap:** check whether a `PROPFLAG_*` hit is on `prop->flags` (runtime u8) or on `obj->flags` / pdef flags (u32 definition bits) before treating it as a reader (`propobj.c:8417`, `prop.c` 233-467 are the latter).
|
||||
- **Remaining class member:** FOV/widescreen widening of `camIsPosInScreen` and `c_lodscalez` (D222).
|
||||
|
||||
## 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.
|
||||
|
||||
## E. Process / method notes
|
||||
|
||||
- **Never `exit()` while another thread may be inside the GL driver (D344).**
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef _DRAWDISTGAMEPLAY_H_
|
||||
#define _DRAWDISTGAMEPLAY_H_
|
||||
|
||||
/* D466 (#125): Video.DrawDistance must not change GAMEPLAY visibility.
|
||||
* Rendering keeps the extended range; gameplay readers (AI "on screen",
|
||||
* "has been seen", out-of-view spawn checks, shot tests, ...) see exactly
|
||||
* what the N64 would at the level's authored FarFog. Design + reader triage:
|
||||
* docs/dev/findings.md D466. Every hook is a no-op when the draw-distance
|
||||
* multiplier is 1, unless GE_D466_FORCE=1; GE_D466_OFF=1 disables the split
|
||||
* (reproduces the leak, for A/B). */
|
||||
|
||||
struct PropRecord;
|
||||
|
||||
/* prop->flags is a u8 and bit 0x80 is unused by the game. Set (together with
|
||||
* PROPFLAG_ONSCREEN) when a prop is drawn only thanks to the extended range. */
|
||||
#define PORT_PROPFLAG_EXTONLY 0x80
|
||||
|
||||
int portD466Active(void); /* split in effect right now */
|
||||
void portD466SetAuthoredPass(int on); /* bgDetermineVisibleRooms pass 1 */
|
||||
int portD466InAuthoredPass(void);
|
||||
void portD466StoreRoom(int room, int rendered, int neighbor);
|
||||
void portD466FrameLog(void); /* GE_D466LOG, called once per rendered frame */
|
||||
|
||||
int portRoomGameplayVisible(int room); /* = getROOMID_isRendered at mult 1 */
|
||||
int portRoomGameplayNeighbor(int room);
|
||||
int portPropGameplayOnScreen(struct PropRecord *prop);
|
||||
void portPropSetGameplayOnScreen(struct PropRecord *prop, int gameplayVisible);
|
||||
|
||||
/* posIsOnScreen records the authored-distance verdict of its last call here. */
|
||||
void portD466SetLastPosVerdict(int gameplayVisible);
|
||||
int portD466LastPosVerdict(void);
|
||||
|
||||
/* D468: AI view clamp (cartridge-widest view = 16:9 at the game's FOV). */
|
||||
void portD468UpdateClamp(void); /* per player view, bgDetermineVisibleRooms */
|
||||
int portD468ClampActive(void);
|
||||
void portD468ShrinkBox(float *minmax); /* {min.x,min.y,max.x,max.y} */
|
||||
|
||||
/* authored-distance fog (bgfog.c) */
|
||||
float portD466AuthoredFar(void);
|
||||
int portFogPositionVisibleGameplay(float *pos3, float range);
|
||||
float portFogScaledFarFogIntensitySquaredGameplay(void);
|
||||
|
||||
#endif
|
||||
@@ -476,7 +476,7 @@ s16 viGetX();
|
||||
s16 viGetY();
|
||||
void viSetColorMode16Bit();
|
||||
void viSetColorMode32Bit();
|
||||
s32 vtxstore_allocate();
|
||||
void *vtxstore_allocate(); /* D457: was s32 */
|
||||
Gfx* watchRenderController();
|
||||
Gfx * watchRenderControllerOpaque();
|
||||
u32 weaponLoadProjectileModels();
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
/* drawdistgameplay.c -- D466 (#125): keep Video.DrawDistance out of gameplay.
|
||||
*
|
||||
* See port/include/drawdistgameplay.h and docs/dev/findings.md D466. This file
|
||||
* only holds port state + helpers; the game-file hooks (all #ifdef PORT) are
|
||||
* in bg.c (two room sets), bgfog.c (authored fog), chr.c/propobj.c (verdict
|
||||
* recording) and the gameplay readers.
|
||||
*
|
||||
* Env (dev probes, see docs/dev/GE-ENV-PROBES.md):
|
||||
* GE_D466_FORCE=1 run the split even at multiplier 1 (identity test)
|
||||
* GE_D466_OFF=1 disable the split entirely (reproduces the #125 leak)
|
||||
* GE_D466LOG=<n> log a line every n rendered frames (room-set + chr counts)
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "PR/os.h"
|
||||
#include "bondtypes.h"
|
||||
#include "bondconstants.h"
|
||||
#include "bg.h"
|
||||
#include "chrai.h"
|
||||
#include "chr.h"
|
||||
#include "bondview.h"
|
||||
#include "port_math.h"
|
||||
#include "drawdistgameplay.h"
|
||||
|
||||
extern f32 portDrawDistanceMultiplier(void);
|
||||
|
||||
static u8 s_authRendered[MAXROOMCOUNT];
|
||||
static u8 s_authNeighbor[MAXROOMCOUNT];
|
||||
static int s_authoredPass;
|
||||
static int s_lastVerdict = 1;
|
||||
|
||||
static int d466Env(const char *name, int *cache)
|
||||
{
|
||||
if (*cache < 0) { *cache = getenv(name) != NULL; }
|
||||
return *cache;
|
||||
}
|
||||
|
||||
/* D468: AI view clamp. The cartridge's widest gameplay view is its own Ratio
|
||||
* 16:9 mode at the game's FOV; with a wider window (ultrawide) or
|
||||
* Video.FovScale > 100 the rendered frustum is wider than that. Perspective is
|
||||
* linear in screen space, so the faithful frustum is exactly a centred
|
||||
* sub-rectangle of the current player's screen: fx = tanH_faithful / tanH,
|
||||
* fy = tanV_faithful / tanV. Computed once per player view in
|
||||
* bgDetermineVisibleRooms (portD468UpdateClamp) and used by the authored room
|
||||
* pass (shrunken screen box) and the posIsOnScreen gameplay verdict. Off when
|
||||
* Game.AIWideView=1 (PD-port model) or when the view is already faithful. */
|
||||
extern struct player *g_CurrentPlayer;
|
||||
extern f32 portFovYScaleFactor(void);
|
||||
extern int portAIWideView(void);
|
||||
static int s_clampOn;
|
||||
static float s_clampFx = 1.0f, s_clampFy = 1.0f;
|
||||
|
||||
void portD468UpdateClamp(void)
|
||||
{
|
||||
struct player *p = g_CurrentPlayer;
|
||||
s_clampOn = 0; s_clampFx = s_clampFy = 1.0f;
|
||||
if (portAIWideView() || p == NULL || p->c_halfheight <= 0.0f || p->c_halfwidth <= 0.0f ||
|
||||
p->c_perspfovy <= 0.0f || p->c_perspaspect <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
const float halfRad = 3.14159265f / 360.0f; /* degrees -> half-angle radians */
|
||||
float k = portFovYScaleFactor();
|
||||
float tanV = tanf(p->c_perspfovy * halfRad);
|
||||
float tanVf = tanf((p->c_perspfovy / k) * halfRad);
|
||||
/* Ratio 16:9 mode: aspect = viewport w/h * 0.75 * 16/9 (bondview2.c). */
|
||||
float aspF = (p->c_halfwidth / p->c_halfheight) * 0.75f * (16.0f / 9.0f);
|
||||
float asp = p->c_perspaspect;
|
||||
if (aspF > asp) { aspF = asp; }
|
||||
if (tanV <= 0.0f) { return; }
|
||||
s_clampFy = tanVf / tanV;
|
||||
s_clampFx = (tanVf * aspF) / (tanV * asp);
|
||||
{ /* GE_D468_SCALE=<0.05..1> (dev falsifier, registered): narrow the
|
||||
* faithful box further so a headless run can prove the clamp bites. */
|
||||
static float dbg = -1.0f;
|
||||
if (dbg < 0.0f) {
|
||||
const char *e = getenv("GE_D468_SCALE");
|
||||
dbg = e ? (float)atof(e) : 0.0f;
|
||||
if (dbg != 0.0f && (dbg < 0.05f || dbg > 1.0f)) { dbg = 0.0f; }
|
||||
}
|
||||
if (dbg > 0.0f) { s_clampFx *= dbg; s_clampFy *= dbg; }
|
||||
}
|
||||
if (s_clampFx > 1.0f) { s_clampFx = 1.0f; }
|
||||
if (s_clampFy > 1.0f) { s_clampFy = 1.0f; }
|
||||
s_clampOn = (s_clampFx < 0.999f || s_clampFy < 0.999f);
|
||||
}
|
||||
}
|
||||
|
||||
int portD468ClampActive(void) { return s_clampOn; }
|
||||
|
||||
/* Shrink a screen box (f[0]=min x,y; f[1]=max x,y) to its centred faithful part. */
|
||||
void portD468ShrinkBox(float *minmax)
|
||||
{
|
||||
float cx, cy, hx, hy;
|
||||
if (!s_clampOn) { return; }
|
||||
cx = (minmax[0] + minmax[2]) * 0.5f; hx = (minmax[2] - minmax[0]) * 0.5f * s_clampFx;
|
||||
cy = (minmax[1] + minmax[3]) * 0.5f; hy = (minmax[3] - minmax[1]) * 0.5f * s_clampFy;
|
||||
minmax[0] = cx - hx; minmax[2] = cx + hx;
|
||||
minmax[1] = cy - hy; minmax[3] = cy + hy;
|
||||
}
|
||||
|
||||
int portD466Active(void)
|
||||
{
|
||||
static int force = -1, off = -1;
|
||||
if (d466Env("GE_D466_OFF", &off)) { return 0; }
|
||||
if (portDrawDistanceMultiplier() > 1.0f || s_clampOn) { return 1; }
|
||||
return d466Env("GE_D466_FORCE", &force);
|
||||
}
|
||||
|
||||
void portD466SetAuthoredPass(int on) { s_authoredPass = on; }
|
||||
int portD466InAuthoredPass(void) { return s_authoredPass; }
|
||||
|
||||
void portD466StoreRoom(int room, int rendered, int neighbor)
|
||||
{
|
||||
if (room >= 0 && room < MAXROOMCOUNT) {
|
||||
s_authRendered[room] = (u8)rendered;
|
||||
s_authNeighbor[room] = (u8)neighbor;
|
||||
}
|
||||
}
|
||||
|
||||
int portRoomGameplayVisible(int room)
|
||||
{
|
||||
if (portD466Active() && room >= 0 && room < MAXROOMCOUNT) {
|
||||
return s_authRendered[room];
|
||||
}
|
||||
return getROOMID_isRendered(room);
|
||||
}
|
||||
|
||||
int portRoomGameplayNeighbor(int room)
|
||||
{
|
||||
if (portD466Active() && room >= 0 && room < MAXROOMCOUNT) {
|
||||
return s_authNeighbor[room];
|
||||
}
|
||||
return getROOMID_isNeighborToRendered(room);
|
||||
}
|
||||
|
||||
int portPropGameplayOnScreen(struct PropRecord *prop)
|
||||
{
|
||||
PropRecord *p = (PropRecord *)prop;
|
||||
if (!(p->flags & PROPFLAG_ONSCREEN)) { return 0; }
|
||||
if (portD466Active() && (p->flags & PORT_PROPFLAG_EXTONLY)) { return 0; }
|
||||
return 1;
|
||||
}
|
||||
|
||||
void portPropSetGameplayOnScreen(struct PropRecord *prop, int gameplayVisible)
|
||||
{
|
||||
PropRecord *p = (PropRecord *)prop;
|
||||
if (gameplayVisible || !portD466Active()) {
|
||||
p->flags &= ~PORT_PROPFLAG_EXTONLY;
|
||||
} else {
|
||||
p->flags |= PORT_PROPFLAG_EXTONLY;
|
||||
}
|
||||
}
|
||||
|
||||
void portD466SetLastPosVerdict(int v) { s_lastVerdict = v; }
|
||||
int portD466LastPosVerdict(void) { return s_lastVerdict; }
|
||||
|
||||
/* ---- GE_D466LOG ------------------------------------------------------- */
|
||||
|
||||
extern s32 g_MaxNumRooms;
|
||||
extern s32 getPlayerCount(void);
|
||||
|
||||
void portD466FrameLog(void)
|
||||
{
|
||||
static int period = -1;
|
||||
static unsigned frame;
|
||||
if (period < 0) {
|
||||
const char *e = getenv("GE_D466LOG");
|
||||
period = e ? atoi(e) : 0;
|
||||
if (e && period <= 0) { period = 1; }
|
||||
}
|
||||
if (period == 0) { return; }
|
||||
if ((frame++ % (unsigned)period) != 0) { return; }
|
||||
{
|
||||
int i, ext = 0, auth = 0, sub = 1;
|
||||
unsigned he = 2166136261u, ha = 2166136261u;
|
||||
for (i = 0; i < g_MaxNumRooms && i < MAXROOMCOUNT; i++) {
|
||||
int e = getROOMID_isRendered(i) != 0;
|
||||
int a = s_authRendered[i] != 0;
|
||||
ext += e; auth += a;
|
||||
if (e) { he = (he ^ (unsigned)i) * 16777619u; }
|
||||
if (a) { ha = (ha ^ (unsigned)i) * 16777619u; }
|
||||
if (a && !e) { sub = 0; }
|
||||
}
|
||||
int c, nOn = 0, nGp = 0, nSeen = 0;
|
||||
unsigned hg = 2166136261u;
|
||||
for (c = 0; c < g_NumChrSlots; c++) {
|
||||
ChrRecord *chr = &g_ChrSlots[c];
|
||||
PropRecord *pr = chr->prop;
|
||||
unsigned v;
|
||||
int gp;
|
||||
if (pr == NULL || chr->model == NULL) { continue; }
|
||||
gp = portPropGameplayOnScreen((struct PropRecord *)pr);
|
||||
nOn += (pr->flags & PROPFLAG_ONSCREEN) != 0;
|
||||
nGp += gp;
|
||||
nSeen += (chr->chrflags & CHRFLAG_HAS_BEEN_ON_SCREEN) != 0;
|
||||
v = (unsigned)chr->chrnum;
|
||||
v = v * 31u + (unsigned)chr->actiontype;
|
||||
v = v * 31u + (unsigned)(int)(pr->pos.f[0] + 0.5f);
|
||||
v = v * 31u + (unsigned)(int)(pr->pos.f[1] + 0.5f);
|
||||
v = v * 31u + (unsigned)(int)(pr->pos.f[2] + 0.5f);
|
||||
v = v * 31u + (unsigned)gp;
|
||||
v = v * 31u + (unsigned)((chr->chrflags & CHRFLAG_HAS_BEEN_ON_SCREEN) != 0);
|
||||
hg = (hg ^ v) * 16777619u;
|
||||
}
|
||||
fprintf(stderr, "D466 f=%u active=%d ext=%d auth=%d subset=%d hext=%08x hauth=%08x chrOn=%d chrGp=%d seen=%d hgame=%08x\n",
|
||||
frame - 1, portD466Active(), ext, auth, sub, he, ha, nOn, nGp, nSeen, hg);
|
||||
}
|
||||
}
|
||||
@@ -310,6 +310,9 @@ static struct Row rows[] = {
|
||||
/* D232: the community "no damage flash" toggle (suppresses the red/green
|
||||
* hit-flash overlay in bondview2). */
|
||||
{ .key="Game.NoHitFlash", .label="No hit flash", .kind=ROW_TOGGLE, .step=1, .names=kOnOff },
|
||||
/* D468: off = AI awareness keeps the cartridge-widest view (16:9 at the
|
||||
* game's FOV) under ultrawide or a raised FOV; on = AI sees the full view. */
|
||||
{ .key="Game.AIWideView", .label="AI sees the wider view", .kind=ROW_TOGGLE, .step=1, .names=kOnOff },
|
||||
/* D257: everything-unlocked goodie. The C initializers (port/src/video.c)
|
||||
* start it OFF (0) -- the old "default ON" note was stale and is
|
||||
* corrected by D356. Consumed at startup by main.c -- applies from the
|
||||
|
||||
@@ -263,6 +263,28 @@ f32 portScaleFovY(f32 fovy, s32 isTitleScreen)
|
||||
return fovy;
|
||||
}
|
||||
|
||||
/* D468: the factor portScaleFovY applies to an in-level fovy (clamps
|
||||
* ignored), so gameplay can recover the game's own FOV from the rendered one.
|
||||
* 1.0f at the defaults (native widescreen on, FovScale 100). */
|
||||
f32 portFovYScaleFactor(void)
|
||||
{
|
||||
f32 k = 1.0f;
|
||||
if (cfgWidescreenAuto && !cfgNativeWidescreen &&
|
||||
gfx_current_dimensions.aspect_ratio > 0.01f) {
|
||||
k *= sqrtf(gfx_current_dimensions.aspect_ratio / (4.0f / 3.0f));
|
||||
}
|
||||
if (portFovScale > 0.4f && portFovScale < 2.01f && portFovScale != 1.0f) {
|
||||
k *= portFovScale;
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
/* D468: Game.AIWideView -- 0 (default) = AI awareness keeps the cartridge's
|
||||
* widest view (16:9 at the game's own FOV) when the window is wider or
|
||||
* FovScale > 100; 1 = AI sees the full rendered view (the PD-port model). */
|
||||
static int cfgAIWideView = 0;
|
||||
int portAIWideView(void) { return cfgAIWideView; }
|
||||
|
||||
/* D443 (D357): horizontal FOV in degrees for a Video.FovScale percent, for the
|
||||
* settings display only. Mirrors portScaleFovY + the gameplay projection:
|
||||
* vertical FOV = FOV_Y_F (60) * [WidescreenAuto stretch-era boost] * pct/100,
|
||||
@@ -446,6 +468,7 @@ PD_CONSTRUCTOR static void videoConfigInit(void)
|
||||
configRegisterFloat("Game.ScreenShakeIntensity", &portScreenShakeScale, 0.0f, 10.0f);
|
||||
configRegisterInt("Game.SkipIntro", &portSkipIntro, 0, 1);
|
||||
configRegisterInt("Game.NoHitFlash", &portNoHitFlash, 0, 1);
|
||||
configRegisterInt("Game.AIWideView", &cfgAIWideView, 0, 1); /* D468 */
|
||||
configRegisterInt("Video.CrosshairHide", &portCrosshairHide, 0, 1); /* v0.4.0 M2 (D373) */
|
||||
configRegisterInt("Video.CrosshairColor", &cfgCrosshairColor, 0, 8); /* 8 = custom; old ini values unchanged */
|
||||
configRegisterInt("Video.CrosshairRed", &cfgCrosshairRed, 0, 255);
|
||||
|
||||
@@ -1589,8 +1589,14 @@ typedef union
|
||||
* Offset 0x32.
|
||||
*/
|
||||
s16 frameb;
|
||||
#ifdef PORT
|
||||
/* D457: animation-frame bitstream pointers (loadAnimationFrame result), were s32. */
|
||||
u8 *unk34;
|
||||
u8 *unk38;
|
||||
#else
|
||||
s32 unk34;
|
||||
s32 unk38;
|
||||
#endif
|
||||
f32 endframe;
|
||||
|
||||
f32 speed; /*0x40*/
|
||||
@@ -1616,8 +1622,13 @@ typedef union
|
||||
*/
|
||||
s16 frame2b;
|
||||
|
||||
#ifdef PORT
|
||||
u8 *unk64;
|
||||
u8 *unk68;
|
||||
#else
|
||||
s32 unk64;
|
||||
s32 unk68;
|
||||
#endif
|
||||
f32 unk6c;
|
||||
// 0x70
|
||||
f32 speed2;
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
#include "debugmenu_handler.h"
|
||||
#include "decompress.h"
|
||||
#include "bgfog.h"
|
||||
#ifdef PORT
|
||||
#include "drawdistgameplay.h"
|
||||
#endif
|
||||
#include "lv.h"
|
||||
#include "math_ceil.h"
|
||||
#include "matrixmath.h"
|
||||
@@ -1479,6 +1482,11 @@ bool bgIsRoomOnScreen(s32 roomID, struct rectbbox *screenbox)
|
||||
count_bottom = 0;
|
||||
|
||||
viGetZRange(zrange);
|
||||
#ifdef PORT
|
||||
/* D466: pass 1 of bgDetermineVisibleRooms asks "what would the N64 admit
|
||||
* at the authored far distance". Static read only -- never viSetZRange. */
|
||||
if (portD466InAuthoredPass()) { zrange[1] = portD466AuthoredFar(); }
|
||||
#endif
|
||||
|
||||
zrange[1] = zrange[1] / mCurrentLevelVisibilityScale;
|
||||
|
||||
@@ -1600,6 +1608,10 @@ s32 sub_GAME_7F0B5528(s32 portalnum, f32 arg1, coord3d *arg2)
|
||||
matrix = camGetWorldToScreenMtxf();
|
||||
allbehind = 1;
|
||||
viGetZRange(zrange);
|
||||
#ifdef PORT
|
||||
/* D466: see bgIsRoomOnScreen. */
|
||||
if (portD466InAuthoredPass()) { zrange[1] = portD466AuthoredFar(); }
|
||||
#endif
|
||||
zrange[1] /= mCurrentLevelVisibilityScale;
|
||||
|
||||
for (i = 0; i < g_BgPortals[portalnum].offset_portal->numPoints; i++) {
|
||||
@@ -4863,7 +4875,11 @@ void *sub_GAME_7F0B8A24(s32 *pc)
|
||||
/**
|
||||
* Address: 7F0B8A6C
|
||||
*/
|
||||
#ifdef PORT
|
||||
static void bgDetermineVisibleRoomsImpl(void)
|
||||
#else
|
||||
void bgDetermineVisibleRooms(void)
|
||||
#endif
|
||||
{
|
||||
f32 screenbounds[4];
|
||||
s32 var_s0;
|
||||
@@ -4875,6 +4891,10 @@ void bgDetermineVisibleRooms(void)
|
||||
s32 i;
|
||||
|
||||
bgUpdateCurrentPlayerScreenMinMax();
|
||||
#ifdef PORT
|
||||
/* D468: the authored (gameplay) pass sees only the cartridge-widest view. */
|
||||
if (portD466InAuthoredPass()) { portD468ShrinkBox(&g_CurrentPlayer->screensize.min.x); }
|
||||
#endif
|
||||
|
||||
screenbounds[0] = g_CurrentPlayer->screensize.min.x;
|
||||
screenbounds[1] = g_CurrentPlayer->screensize.min.y;
|
||||
@@ -4985,6 +5005,42 @@ void bgDetermineVisibleRooms(void)
|
||||
}
|
||||
|
||||
|
||||
#ifdef PORT
|
||||
/**
|
||||
* D466 (#125): Video.DrawDistance must not change gameplay visibility.
|
||||
* When the draw-distance multiplier is > 1 (or GE_D466_FORCE), run the
|
||||
* original body TWICE: first with the zrange far reads pinned to the level's
|
||||
* authored FarFog (what the N64 would admit), snapshotting room_rendered /
|
||||
* room_neighbor_to_rendered into port arrays that the gameplay readers use
|
||||
* (portRoomGameplayVisible); then the normal extended pass, which rendering
|
||||
* uses. Order matters: pass 2 resets every piece of draw-list state, so the
|
||||
* extended result is exactly what it was before this change. Pass 1 loads
|
||||
* nothing (room loading happens in bgRenderRoom*, gated on D_80044858, which
|
||||
* is restored). The authored set is a subset of the extended set.
|
||||
*/
|
||||
void bgDetermineVisibleRooms(void)
|
||||
{
|
||||
portD468UpdateClamp(); /* D468: per player view, before portD466Active() */
|
||||
if (portD466Active()) {
|
||||
s32 savedStagger = D_80044858;
|
||||
s32 i;
|
||||
|
||||
portD466SetAuthoredPass(1);
|
||||
bgDetermineVisibleRoomsImpl();
|
||||
portD466SetAuthoredPass(0);
|
||||
|
||||
for (i = 0; i < MAXROOMCOUNT; i++) {
|
||||
portD466StoreRoom(i, g_BgRoomInfo[i].room_rendered, g_BgRoomInfo[i].room_neighbor_to_rendered);
|
||||
}
|
||||
D_80044858 = savedStagger;
|
||||
}
|
||||
|
||||
bgDetermineVisibleRoomsImpl();
|
||||
portD466FrameLog();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* Address 0x7F0B8D78.
|
||||
*/
|
||||
@@ -5306,7 +5362,14 @@ void bgRoomCalcBB(s32 room) // canonical name
|
||||
limits.maxY = -0x7fff;
|
||||
limits.maxZ = -0x7fff;
|
||||
|
||||
#ifdef PORT
|
||||
/* D441 (#107, italoarruda): the induction variable is a full host pointer;
|
||||
* an (s32) bound sign-extends/truncates above 2 GiB, the loop never runs and
|
||||
* the room keeps an inverted bounding box. Same byte arithmetic. */
|
||||
for (; vertices < (Vtx *) ((uintptr_t) g_BgRoomInfo[room].vertices + g_BgRoomInfo[room].usize_point_index_binary); vertices++)
|
||||
#else
|
||||
for (; vertices < (Vtx *) ((s32) g_BgRoomInfo[room].vertices + g_BgRoomInfo[room].usize_point_index_binary); vertices++)
|
||||
#endif
|
||||
{
|
||||
for (j = 0; j < 3; j++)
|
||||
{
|
||||
|
||||
@@ -280,6 +280,70 @@ f32 fogGetScaledFarFogIntensitySquared(void)
|
||||
}
|
||||
|
||||
|
||||
#ifdef PORT
|
||||
/* D466 (#125): AUTHORED-distance copy of the fog state -- what the two
|
||||
* g_Scaled*FarFogIntensity globals, and the far clip distance, would be at
|
||||
* Video.DrawDistance = 100%. Computed in fogLoadCurrentEnvironment with the
|
||||
* exact original math from the unscaled Visibility.FarFog, and STORED (never
|
||||
* derived from zfar / the multiplier), so a live F10 DrawDistance change
|
||||
* cannot desync it. Gameplay readers use these; rendering keeps the extended
|
||||
* values above. */
|
||||
static f32 g_PortAuthoredFarFog = 10000.0f;
|
||||
static f32 g_PortAuthoredScaledFarFogIntensity = FLT_MAX;
|
||||
static f32 g_PortAuthoredScaledDifferenceFromFarFogIntensity = 0.0f;
|
||||
|
||||
/* Unscaled far clip distance (before the level visibility-scale division). */
|
||||
float portD466AuthoredFar(void)
|
||||
{
|
||||
return g_PortAuthoredFarFog;
|
||||
}
|
||||
|
||||
float portFogScaledFarFogIntensitySquaredGameplay(void)
|
||||
{
|
||||
extern int portD466Active(void);
|
||||
if (!portD466Active()) {
|
||||
return fogGetScaledFarFogIntensitySquared();
|
||||
}
|
||||
return g_PortAuthoredScaledFarFogIntensity * g_PortAuthoredScaledFarFogIntensity;
|
||||
}
|
||||
|
||||
/* fogPositionIsVisibleThroughFog() with the authored far intensity. */
|
||||
int portFogPositionVisibleGameplay(float *pos3, float range)
|
||||
{
|
||||
extern int portD466Active(void);
|
||||
coord3d sp24;
|
||||
f32 ff;
|
||||
coord3d *player_pos;
|
||||
Mtxf *player_mtx;
|
||||
|
||||
if (!portD466Active()) {
|
||||
return fogPositionIsVisibleThroughFog((coord3d *) pos3, range);
|
||||
}
|
||||
|
||||
if (g_FogSkyIsEnabled == 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
player_pos = bondviewGetCurrentPlayersPosition();
|
||||
player_mtx = camGetWorldToScreenMtxf();
|
||||
|
||||
sp24.f[0] = pos3[0] - player_pos->f[0];
|
||||
sp24.f[1] = pos3[1] - player_pos->f[1];
|
||||
sp24.f[2] = pos3[2] - player_pos->f[2];
|
||||
|
||||
ff = (((sp24.f[0] * player_mtx->m[0][0]) + (sp24.f[1] * player_mtx->m[0][1]) + (sp24.f[2] * player_mtx->m[0][2])));
|
||||
|
||||
if (ff > (g_PortAuthoredScaledFarFogIntensity + range))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* Address 0x7F0BA758.
|
||||
*/
|
||||
@@ -333,6 +397,13 @@ void fogLoadCurrentEnvironment(EnvironmentRecord *arg0)
|
||||
|
||||
g_ScaledFarFogIntensity = ((zrange[1] - zrange[0]) * g_FarFogIntensity) + zrange[0];
|
||||
g_ScaledDifferenceFromFarFogIntensity = ((zrange[1] - zrange[0]) * g_DifferenceFromFarFogIntensity) + zrange[0];
|
||||
#ifdef PORT
|
||||
/* D466: same math from the authored (unscaled) far value. zrange[0]
|
||||
* (znear / scale) does not depend on Video.DrawDistance. */
|
||||
g_PortAuthoredFarFog = arg0->Visibility.FarFog;
|
||||
g_PortAuthoredScaledFarFogIntensity = (((arg0->Visibility.FarFog / temp_f0) - zrange[0]) * g_FarFogIntensity) + zrange[0];
|
||||
g_PortAuthoredScaledDifferenceFromFarFogIntensity = (((arg0->Visibility.FarFog / temp_f0) - zrange[0]) * g_DifferenceFromFarFogIntensity) + zrange[0];
|
||||
#endif
|
||||
|
||||
g_CurFogDetails.g_CurFogDetails = (arg0->Visibility.BlendMultiplier / temp_f0);
|
||||
pk0 = g_CurFogDetails.g_CurFogDetails;
|
||||
@@ -452,6 +523,11 @@ void fogLoadLevelEnvironment(s32 level_id, s32 arg1)
|
||||
|
||||
g_ScaledFarFogIntensity = FLT_MAX;
|
||||
g_ScaledDifferenceFromFarFogIntensity = 0.0f;
|
||||
#ifdef PORT
|
||||
g_PortAuthoredScaledFarFogIntensity = FLT_MAX;
|
||||
g_PortAuthoredScaledDifferenceFromFarFogIntensity = 0.0f;
|
||||
g_PortAuthoredFarFog = 10000.0f;
|
||||
#endif
|
||||
|
||||
if (arg1)
|
||||
{
|
||||
|
||||
@@ -816,11 +816,19 @@ Mtx *currentPlayerGetProjectionMatrix(void) {
|
||||
return g_CurrentPlayer->projmatrix;
|
||||
}
|
||||
|
||||
#ifdef PORT
|
||||
void set_BONDdata_field_10E0(Mtx *arg0) {
|
||||
#else
|
||||
void set_BONDdata_field_10E0(s32 arg0) {
|
||||
#endif
|
||||
g_CurrentPlayer->field_10E0 = arg0;
|
||||
}
|
||||
|
||||
#ifdef PORT
|
||||
Mtx *get_BONDdata_field_10E0(void) {
|
||||
#else
|
||||
s32 get_BONDdata_field_10E0(void) {
|
||||
#endif
|
||||
return g_CurrentPlayer->field_10E0;
|
||||
}
|
||||
|
||||
@@ -866,11 +874,19 @@ Mtxf *currentPlayerGetMatrix10EC(void) {
|
||||
return g_CurrentPlayer->field_10EC;
|
||||
}
|
||||
|
||||
#ifdef PORT
|
||||
void sub_GAME_7F078464(LookAt *arg0) {
|
||||
#else
|
||||
void sub_GAME_7F078464(s32 arg0) {
|
||||
#endif
|
||||
g_CurrentPlayer->field_10E4 = arg0;
|
||||
}
|
||||
|
||||
#ifdef PORT
|
||||
LookAt *sub_GAME_7F078474(void)
|
||||
#else
|
||||
s32 sub_GAME_7F078474(void)
|
||||
#endif
|
||||
{
|
||||
return g_CurrentPlayer->field_10E4;
|
||||
}
|
||||
|
||||
@@ -369,11 +369,20 @@ struct player
|
||||
* Used as parameter to gbi macro.
|
||||
* Offset 0x005c.
|
||||
*/
|
||||
#ifdef PORT
|
||||
/* D457: host matrix pointers (dynAllocateMatrix), were s32. */
|
||||
Mtx *field_5C;
|
||||
|
||||
/* 0x0060 */ Mtx *field_60;
|
||||
/* 0x0064 */ Mtxf *field_64;
|
||||
/* 0x0068 */ Mtxf *field_68;
|
||||
#else
|
||||
s32 field_5C;
|
||||
|
||||
/* 0x0060 */ s32 field_60;
|
||||
/* 0x0064 */ s32 field_64;
|
||||
/* 0x0068 */ s32 field_68;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Collision / clipping related.
|
||||
@@ -1211,8 +1220,13 @@ struct player
|
||||
Mtxf* viewtoworldmtxf;
|
||||
Mtx* projmatrix;
|
||||
Mtxf* projmatrixf;
|
||||
#ifdef PORT
|
||||
Mtx *field_10E0; // ptr (D457: was s32)
|
||||
LookAt *field_10E4; // ptr (D457: was s32)
|
||||
#else
|
||||
s32 field_10E0; // ptr
|
||||
s32 field_10E4; // ptr
|
||||
#endif
|
||||
Mtxf* field_10E8;
|
||||
Mtxf* field_10EC;
|
||||
f32 c_scalelod60; // canonical name
|
||||
@@ -2882,8 +2896,13 @@ f32 bondviewGetPlayerYawRadians(void);
|
||||
Mtxf *camGetWorldToScreenMtxf(void);
|
||||
void transformAndNormalizeByLength2Dto3D(struct coord2d *in, coord3d *out, f32 value);
|
||||
void bondviewTransformManyPosToViewMatrix(RenderPosView *arg0, s32 arg1);
|
||||
#ifdef PORT
|
||||
LookAt *sub_GAME_7F078474(void);
|
||||
Mtx *get_BONDdata_field_10E0(void);
|
||||
#else
|
||||
s32 sub_GAME_7F078474(void);
|
||||
s32 get_BONDdata_field_10E0(void);
|
||||
#endif
|
||||
Mtx *currentPlayerGetProjectionMatrix(void);
|
||||
Gfx *bondviewRenderProp(PropRecord *arg0, Gfx *arg1, s32 arg2);
|
||||
f32 getPlayer_c_lodscalez(void);
|
||||
|
||||
@@ -8451,8 +8451,13 @@ void bondviewUpdateCameraMatrices(coord3d* cam_pos, coord3d* cam_look_dir, coord
|
||||
|
||||
g_CurrentPlayer->field_5C = dynAllocateMatrix();
|
||||
g_CurrentPlayer->field_60 = dynAllocateMatrix();
|
||||
#ifdef PORT
|
||||
g_CurrentPlayer->field_64 = (Mtxf *)dynAllocateMatrix();
|
||||
g_CurrentPlayer->field_68 = (Mtxf *)dynAllocateMatrix();
|
||||
#else
|
||||
g_CurrentPlayer->field_64 = dynAllocateMatrix();
|
||||
g_CurrentPlayer->field_68 = dynAllocateMatrix();
|
||||
#endif
|
||||
|
||||
lookat = dynAllocateLights(2);
|
||||
|
||||
@@ -8507,7 +8512,11 @@ void bondviewUpdateCameraMatrices(coord3d* cam_pos, coord3d* cam_look_dir, coord
|
||||
}
|
||||
|
||||
guMtxF2L((f32 (*)[4]) &sp60, temp_s0);
|
||||
#ifdef PORT
|
||||
set_BONDdata_field_10E0(temp_s0);
|
||||
#else
|
||||
set_BONDdata_field_10E0((s32) temp_s0);
|
||||
#endif
|
||||
|
||||
scale = bgGetLevelVisibilityScale();
|
||||
|
||||
@@ -8520,7 +8529,11 @@ void bondviewUpdateCameraMatrices(coord3d* cam_pos, coord3d* cam_look_dir, coord
|
||||
currentPlayerSetMatrix10CC((Mtxf* ) g_CurrentPlayer->field_64);
|
||||
currentPlayerSetViewToWorldMtxf((Mtxf* ) g_CurrentPlayer->field_68);
|
||||
|
||||
#ifdef PORT
|
||||
sub_GAME_7F078464(lookat);
|
||||
#else
|
||||
sub_GAME_7F078464((s32) lookat);
|
||||
#endif
|
||||
bondviewUpdateFrustumPlanes();
|
||||
store_BONDdata_curpos_to_previous();
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#ifdef PORT
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "drawdistgameplay.h"
|
||||
/* GE_D193A=1 — per-second locomotion telemetry for one/all scripted chrs
|
||||
* (D193: "AI travels slower than N64" — the frame clock was ruled out in
|
||||
* M-80, so measure the anim-root-motion path directly). For each ticked
|
||||
@@ -2465,6 +2466,9 @@ s32 chrTick(PropRecord *prop)
|
||||
ChrRecord *chr;
|
||||
Model *model;
|
||||
s32 headSwitchVisible;
|
||||
#ifdef PORT
|
||||
s32 gpVisible = 0; /* D466: headSwitchVisible at the authored draw distance */
|
||||
#endif
|
||||
s32 headVisible;
|
||||
s32 tickamount;
|
||||
#ifdef PORT
|
||||
@@ -2568,6 +2572,9 @@ s32 chrTick(PropRecord *prop)
|
||||
if (chr->chrflags & CHRFLAG_HIDDEN)
|
||||
{
|
||||
headSwitchVisible = 0;
|
||||
#ifdef PORT
|
||||
gpVisible = 0;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2615,6 +2622,9 @@ s32 chrTick(PropRecord *prop)
|
||||
if (((prop->type == PROP_TYPE_VIEWER) && (g_playerPointers[getPlayerPointerIndex(prop)]->cameramode == 1)) || (chr->chrflags & CHRFLAG_CULL_USING_HITBOX))
|
||||
{
|
||||
headSwitchVisible = 1;
|
||||
#ifdef PORT
|
||||
gpVisible = 1;
|
||||
#endif
|
||||
|
||||
if (((chr->actiontype == ACT_ANIM) && (chr->act_anim.unk02c == 0)) && (chr->act_anim.noTranslate != 0))
|
||||
{
|
||||
@@ -2633,6 +2643,9 @@ s32 chrTick(PropRecord *prop)
|
||||
if (((chr->actiontype == ACT_PATROL) && (chr->act_patrol.waydata.mode == WAYMODE_MAGIC)) || ((chr->actiontype == ACT_GOPOS) && (chr->act_gopos.waydata.mode == WAYMODE_MAGIC)))
|
||||
{
|
||||
headSwitchVisible = posIsOnScreen(prop, &prop->pos, getinstsize(model), 1);
|
||||
#ifdef PORT
|
||||
gpVisible = portD466LastPosVerdict(); /* D466 */
|
||||
#endif
|
||||
|
||||
if (headSwitchVisible)
|
||||
{
|
||||
@@ -2651,9 +2664,26 @@ s32 chrTick(PropRecord *prop)
|
||||
{
|
||||
chrUpdateAnim(chr, tickamount);
|
||||
headSwitchVisible = posIsOnScreen(prop, &prop->pos, getinstsize(model), 1);
|
||||
#ifdef PORT
|
||||
gpVisible = portD466LastPosVerdict(); /* D466 */
|
||||
#endif
|
||||
|
||||
if (headSwitchVisible)
|
||||
{
|
||||
#ifdef PORT
|
||||
/* D466: gameplay timestamp -- authored-distance verdict only. */
|
||||
if (gpVisible)
|
||||
{
|
||||
if (chr->actiontype == ACT_PATROL)
|
||||
{
|
||||
chr->act_patrol.lastvisible60 = g_GlobalTimer;
|
||||
}
|
||||
else if (chr->actiontype == ACT_GOPOS)
|
||||
{
|
||||
chr->act_gopos.unk9c = g_GlobalTimer;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (chr->actiontype == ACT_PATROL)
|
||||
{
|
||||
chr->act_patrol.lastvisible60 = g_GlobalTimer;
|
||||
@@ -2662,12 +2692,16 @@ s32 chrTick(PropRecord *prop)
|
||||
{
|
||||
chr->act_gopos.unk9c = g_GlobalTimer;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((chr->actiontype == ACT_ANIM) && (chr->act_anim.unk02c == 0))
|
||||
{
|
||||
headSwitchVisible = posIsOnScreen(prop, &prop->pos, getinstsize(model), 1);
|
||||
#ifdef PORT
|
||||
gpVisible = portD466LastPosVerdict(); /* D466 */
|
||||
#endif
|
||||
|
||||
if (headSwitchVisible && (chr->act_anim.noTranslate == 0))
|
||||
{
|
||||
@@ -2681,6 +2715,9 @@ s32 chrTick(PropRecord *prop)
|
||||
else if (chr->actiontype == ACT_STAND)
|
||||
{
|
||||
headSwitchVisible = posIsOnScreen(prop, &prop->pos, getinstsize(model), 1);
|
||||
#ifdef PORT
|
||||
gpVisible = portD466LastPosVerdict(); /* D466 */
|
||||
#endif
|
||||
|
||||
if (headSwitchVisible || (chr->chrflags & CHRFLAG_INIT))
|
||||
{
|
||||
@@ -2703,6 +2740,9 @@ s32 chrTick(PropRecord *prop)
|
||||
}
|
||||
|
||||
headSwitchVisible = posIsOnScreen(prop, &prop->pos, getinstsize(model), 1);
|
||||
#ifdef PORT
|
||||
gpVisible = portD466LastPosVerdict(); /* D466 */
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2775,7 +2815,18 @@ after_position_update:
|
||||
if (get_debug_chrnum_flag()) {}
|
||||
|
||||
prop->flags |= PROPFLAG_ONSCREEN;
|
||||
#ifdef PORT
|
||||
/* D466 (#125): ONSCREEN stays the (extended) render gate; the
|
||||
* gameplay-only "has been seen" flag and the gameplay on-screen bit
|
||||
* follow the authored-draw-distance verdict. Identity at mult 1. */
|
||||
portPropSetGameplayOnScreen(prop, gpVisible);
|
||||
if (gpVisible)
|
||||
{
|
||||
chr->chrflags |= CHRFLAG_HAS_BEEN_ON_SCREEN;
|
||||
}
|
||||
#else
|
||||
chr->chrflags |= CHRFLAG_HAS_BEEN_ON_SCREEN;
|
||||
#endif
|
||||
|
||||
#ifdef BUGFIX_R1
|
||||
if (cheatIsActive(12))
|
||||
@@ -2921,6 +2972,9 @@ after_position_update:
|
||||
hatmodel = hatobj->model;
|
||||
|
||||
chr->handle_positiondata_hat->flags |= PROPFLAG_ONSCREEN;
|
||||
#ifdef PORT
|
||||
portPropSetGameplayOnScreen(chr->handle_positiondata_hat, gpVisible); /* D466 */
|
||||
#endif
|
||||
|
||||
renderdata.basemtx = modelFindNodeMtx(model, hatmodel->attachedto_objinst, 0);
|
||||
renderdata.mtxlist = dynAllocate(hatmodel->obj->numMatrices * (sizeof(Mtxf)));
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#include <bondaicommands.h>
|
||||
#include <bondgame.h>
|
||||
#include <bondconstants.h>
|
||||
#ifdef PORT
|
||||
#include "drawdistgameplay.h"
|
||||
#endif
|
||||
#include "chraction.h"
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
@@ -2985,7 +2988,11 @@ s32 chrlvStanRoomRelated(ChrRecord *self, coord3d *arg1, StandTile *tile)
|
||||
{
|
||||
for (i=0; i<tile_something; i++)
|
||||
{
|
||||
#ifdef PORT
|
||||
if (portRoomGameplayVisible(sp48[i]) != 0) /* D466 */
|
||||
#else
|
||||
if (getROOMID_isRendered(sp48[i]) != 0)
|
||||
#endif
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -3924,7 +3931,11 @@ void set_actor_on_path(ChrRecord *self, struct patrol_path *path)
|
||||
|
||||
pad = chrlvGetNextPatrolStepPad(self);
|
||||
|
||||
#ifdef PORT
|
||||
if (portPropGameplayOnScreen(self->prop) == FALSE) /* D466 */
|
||||
#else
|
||||
if ((self->prop->flags & PROPFLAG_ONSCREEN) == FALSE)
|
||||
#endif
|
||||
{
|
||||
if (chrlvStanRoomRelatedPad(self, pad) != 0)
|
||||
{
|
||||
@@ -4381,7 +4392,11 @@ bool chrCheckTargetInSight(ChrRecord *self)
|
||||
)
|
||||
)
|
||||
{
|
||||
#ifdef PORT
|
||||
if (vec2rd < portFogScaledFarFogIntensitySquaredGameplay()) /* D466: authored far fog */
|
||||
#else
|
||||
if (vec2rd < fogGetScaledFarFogIntensitySquared())
|
||||
#endif
|
||||
{
|
||||
distance = (s32)((sqrtf(vec2rd) * 30.0f) / 16000.0f);
|
||||
|
||||
@@ -9315,7 +9330,11 @@ void chrlvTickGoPos(ChrRecord *self)
|
||||
chrlvActGoposRelated(self, &sp58, &sp54);
|
||||
|
||||
if ((sp74 == 0)
|
||||
#ifdef PORT
|
||||
&& (portPropGameplayOnScreen(self_prop) || (chrlvStanRoomRelated(self, &sp58, sp54) == 0))) /* D466 */
|
||||
#else
|
||||
&& ((self_prop->flags & PROPFLAG_ONSCREEN) || (chrlvStanRoomRelated(self, &sp58, sp54) == 0)))
|
||||
#endif
|
||||
{
|
||||
chrlvActGoposSetTargetPosRelated(self);
|
||||
self->act_gopos.unk9c = g_GlobalTimer;
|
||||
@@ -9462,7 +9481,11 @@ void chrlvTickPatrol(ChrRecord *self)
|
||||
if (self->act_patrol.waydata.mode == WAYMODE_MAGIC)
|
||||
{
|
||||
if ((sp34 == 0)
|
||||
#ifdef PORT
|
||||
&& (portPropGameplayOnScreen(self_prop) || (chrlvStanRoomRelatedPad(self, temp_v0) == 0))) /* D466 */
|
||||
#else
|
||||
&& ((self_prop->flags & PROPFLAG_ONSCREEN) || (chrlvStanRoomRelatedPad(self, temp_v0) == 0)))
|
||||
#endif
|
||||
{
|
||||
self->act_patrol.lastvisible60 = g_GlobalTimer;
|
||||
chrlvSetNextActPatrolStepPadPos(self);
|
||||
@@ -9836,7 +9859,11 @@ bool check_if_room_for_preset_loaded(ChrRecord *self, s32 padnum)
|
||||
|
||||
if (padstan)
|
||||
{
|
||||
#ifdef PORT
|
||||
return portRoomGameplayVisible(getTileRoom(padstan)); /* D466 */
|
||||
#else
|
||||
return getROOMID_isRendered(getTileRoom(padstan));
|
||||
#endif
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
@@ -10730,7 +10757,11 @@ s32 chrIsPosOffScreen(coord3d *arg0, StandTile *tile)
|
||||
|
||||
offscreen = TRUE;
|
||||
|
||||
#ifdef PORT
|
||||
if (portRoomGameplayVisible(getTileRoom(tile)) && portFogPositionVisibleGameplay(arg0->f, 0.0f)) /* D466 */
|
||||
#else
|
||||
if (getROOMID_isRendered(getTileRoom(tile)) && fogPositionIsVisibleThroughFog(arg0, 0.0f))
|
||||
#endif
|
||||
{
|
||||
if (bgGet2dBboxByRoomId(getTileRoom(tile), &box))
|
||||
{
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#include "chrai.h"
|
||||
#ifdef PORT
|
||||
#include "drawdistgameplay.h"
|
||||
#endif
|
||||
#include "bg.h"
|
||||
#include "bgfog.h"
|
||||
#include "bondinv.h"
|
||||
@@ -1913,7 +1916,11 @@ void ai(PropDefHeaderRecord *Entityp, PROP_TYPE EntityType)
|
||||
case AI_IFImOnScreen:
|
||||
{
|
||||
AiIFImOnScreenRecord *ai = AiListp + Offset;
|
||||
#ifdef PORT
|
||||
if (portPropGameplayOnScreen(ChrEntityp->prop)) /* D466: authored-distance on-screen */
|
||||
#else
|
||||
if ((ChrEntityp->prop->flags & PROPFLAG_ONSCREEN))
|
||||
#endif
|
||||
{
|
||||
Offset = chraiGoToLabel(AiListp, Offset, ai->GOTOLABEL);
|
||||
}
|
||||
@@ -1927,7 +1934,11 @@ void ai(PropDefHeaderRecord *Entityp, PROP_TYPE EntityType)
|
||||
{
|
||||
AiIFMyRoomIsOnScreenRecord *ai = AiListp + Offset;
|
||||
|
||||
#ifdef PORT
|
||||
if (portRoomGameplayVisible(getTileRoom(ChrEntityp->prop->stan))) /* D466: authored-distance room set */
|
||||
#else
|
||||
if (getROOMID_isRendered(getTileRoom(ChrEntityp->prop->stan))) // embedded func to match, must be s32 not u8
|
||||
#endif
|
||||
{
|
||||
Offset = chraiGoToLabel(AiListp, Offset, ai->GOTOLABEL);
|
||||
}
|
||||
|
||||
@@ -219,7 +219,13 @@ Vtx *lightFindVertexBaseForTri(Gfx *gfx, s32 room_index)
|
||||
|
||||
if (((s32) ret & 0xFF000000) == 0x0E000000)
|
||||
{
|
||||
#ifdef PORT
|
||||
/* D441 (#107, italoarruda): keep the full-width vertex base; the (s32)
|
||||
* re-cast sign-extends/truncates a host pointer at or above 2 GiB. */
|
||||
ret = (Vtx *)((uintptr_t)g_BgRoomInfo[room_index].vertices + ((uintptr_t) ret & 0xFFFFFF));
|
||||
#else
|
||||
ret = (s32)g_BgRoomInfo[room_index].vertices + ((s32) ret & 0xFFFFFF);
|
||||
#endif
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -6204,8 +6204,23 @@ u32 *sub_GAME_7F07549C(void *arg0, f32 *arg1, f32 *arg2, ModelNode **nodeptr)
|
||||
* Address 7F0754BC.
|
||||
* Copy animation from ROM to RAM
|
||||
*/
|
||||
#ifdef PORT
|
||||
u8 *loadAnimationFrame(ModelAnimation* anim, s32 frame, ModelSkeleton* unused)
|
||||
#else
|
||||
s32 loadAnimationFrame(ModelAnimation* anim, s32 frame, ModelSkeleton* unused)
|
||||
#endif
|
||||
{
|
||||
#ifdef PORT
|
||||
/* D457: the result is a frame-buffer address (host pointer), and the copy
|
||||
* destination is computed at pointer width (was s32 ret / u32 dest). */
|
||||
u8 *ret;
|
||||
s32 source;
|
||||
s32 frameSize;
|
||||
uintptr_t dest;
|
||||
u32 size;
|
||||
|
||||
ret = NULL;
|
||||
#else
|
||||
s32 ret;
|
||||
s32 source;
|
||||
s32 frameSize;
|
||||
@@ -6213,18 +6228,28 @@ s32 loadAnimationFrame(ModelAnimation* anim, s32 frame, ModelSkeleton* unused)
|
||||
u32 size;
|
||||
|
||||
ret = 0;
|
||||
#endif
|
||||
frameSize = anim->unk0E >> 3; // divide by 8
|
||||
|
||||
if (anim->address & 0x80000000) // If animation's address is in RAM
|
||||
{
|
||||
// Load that frame from RAM
|
||||
#ifdef PORT
|
||||
ret = (u8 *)(uintptr_t)(u32)(anim->address + (frame * frameSize));
|
||||
#else
|
||||
ret = anim->address + (frame * frameSize);
|
||||
#endif
|
||||
}
|
||||
else if (D_80036414 != NULL) // should never be NULL after initAnimationsBuffer is called
|
||||
{
|
||||
// Get dest from this D_80036414 which points to an array. Align to 16 bytes.
|
||||
#ifdef PORT
|
||||
dest = ((uintptr_t) (D_80036414->animBufferPtr2 + 15) >> 4) * 16;
|
||||
ret = (u8 *)dest;
|
||||
#else
|
||||
dest = ((u32) (D_80036414->animBufferPtr2 + 15) >> 4) * 16;
|
||||
ret = dest;
|
||||
#endif
|
||||
|
||||
// Get source of this animation in ROM with the offset of the frame we'll load
|
||||
source = anim->address + (frame * frameSize);
|
||||
@@ -6246,7 +6271,11 @@ s32 loadAnimationFrame(ModelAnimation* anim, s32 frame, ModelSkeleton* unused)
|
||||
|
||||
// Set this to point to the end of the copied frame
|
||||
// This allows to copy another frame after this one
|
||||
#ifdef PORT
|
||||
D_80036414->animBufferPtr2 = (char *)(dest + size);
|
||||
#else
|
||||
D_80036414->animBufferPtr2 = dest + size;
|
||||
#endif
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -6266,9 +6295,19 @@ void modelResetAnimationsScratchBuffer(void)
|
||||
}
|
||||
|
||||
|
||||
#ifdef PORT
|
||||
/* D457: rebase at pointer width. The N64 form wraps the sum at 32 bits
|
||||
* (`(u32)var + diff`), which truncates any file base at or above 4 GiB.
|
||||
* `diff` is now an intptr_t (fileramaddr - vma) and `var` is read at full
|
||||
* width (model slots hold the raw vma zero-extended). */
|
||||
#define PROMOTE(var) \
|
||||
if (var) \
|
||||
var = (void *)((uintptr_t)var + diff)
|
||||
#else
|
||||
#define PROMOTE(var) \
|
||||
if (var) \
|
||||
var = (void *)((u32)var + diff)
|
||||
#endif
|
||||
|
||||
#ifdef PORT
|
||||
/* PC port (D43/D45): Vertex.LinkedTo is a raw vma (u32), not a pointer —
|
||||
@@ -6278,9 +6317,15 @@ void modelResetAnimationsScratchBuffer(void)
|
||||
var = (u32)((u32)var + diff)
|
||||
#endif
|
||||
|
||||
#ifdef PORT
|
||||
void modelPromoteNodeOffsetsToPointers(ModelNode *node, u32 vma, uintptr_t fileramaddr)
|
||||
{
|
||||
intptr_t diff = (intptr_t)(fileramaddr - (uintptr_t)vma);
|
||||
#else
|
||||
void modelPromoteNodeOffsetsToPointers(ModelNode *node, u32 vma, u32 fileramaddr)
|
||||
{
|
||||
s32 diff = fileramaddr - vma;
|
||||
#endif
|
||||
s32 i;
|
||||
|
||||
while (node)
|
||||
@@ -6482,8 +6527,13 @@ void modelPromoteNodeOffsetsToPointers(ModelNode *node, u32 vma, u32 fileramaddr
|
||||
/**
|
||||
* Address 7F075A90.
|
||||
*/
|
||||
#ifdef PORT
|
||||
void sub_GAME_7F075A90(ModelFileHeader *header, s32 vma, uintptr_t addr) {
|
||||
intptr_t diff = (intptr_t)(addr - (uintptr_t)(u32)vma);
|
||||
#else
|
||||
void sub_GAME_7F075A90(ModelFileHeader *header, s32 vma, u32 addr) {
|
||||
s32 diff = addr - vma;
|
||||
#endif
|
||||
s32 i;
|
||||
|
||||
for(i = 0;i < header->numSwitches;i++)
|
||||
|
||||
@@ -108,10 +108,19 @@ void sub_GAME_7F074514(s32 param_1,struct Model *param_2,struct ModelNode *param
|
||||
void sub_GAME_7F074524(Gfx *param_1,struct Model *param_2, struct ModelNode *param_3);
|
||||
void sub_GAME_7F074534(ModelRenderData* data, Model* model, ModelNode* node);
|
||||
void subdraw(ModelRenderData *arg0, struct Model *);
|
||||
#ifdef PORT
|
||||
u8 *loadAnimationFrame(ModelAnimation* anim, s32 frame, ModelSkeleton* unused);
|
||||
#else
|
||||
s32 loadAnimationFrame(ModelAnimation* anim, s32 frame, ModelSkeleton* unused);
|
||||
#endif
|
||||
void modelResetAnimationsScratchBuffer(void);
|
||||
#ifdef PORT
|
||||
void modelPromoteNodeOffsetsToPointers(ModelNode *node, u32 vma, uintptr_t fileramaddr);
|
||||
void sub_GAME_7F075A90(ModelFileHeader *header, s32 vma, uintptr_t addr);
|
||||
#else
|
||||
void modelPromoteNodeOffsetsToPointers(ModelNode *node, u32 vma, u32 fileramaddr);
|
||||
void sub_GAME_7F075A90(ModelFileHeader *header, s32 vma, u32 addr);
|
||||
#endif
|
||||
s32 modelCalculateRwDataIndexes(ModelNode *basenode);
|
||||
void modelCalculateRwDataLen(struct ModelFileHeader *objheader);
|
||||
void modelInitRwData(Model *model, ModelNode *startnode);
|
||||
|
||||
@@ -147,7 +147,11 @@ void load_object_fill_header(struct ModelFileHeader *objheader, u8 *name, u8* ds
|
||||
fflush(stderr);
|
||||
}
|
||||
#endif
|
||||
#ifdef PORT
|
||||
sub_GAME_7F075A90(objheader, 0x5000000, (uintptr_t)filedata);
|
||||
#else
|
||||
sub_GAME_7F075A90(objheader, 0x5000000, filedata);
|
||||
#endif
|
||||
sub_GAME_7F0762E0(objheader, name, dst, buffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
#include "vtxstore.h"
|
||||
#ifdef PORT
|
||||
#include "envflag.h" /* cached getenv for per-tick probes (D302) */
|
||||
#include "drawdistgameplay.h"
|
||||
#endif
|
||||
|
||||
|
||||
@@ -3642,6 +3643,10 @@ void sub_GAME_7F0442DC(PropRecord* prop)
|
||||
{
|
||||
mtx = modelFindNodeMtx(model->attachedto, model->attachedto_objinst, 0);
|
||||
prop->flags |= PROPFLAG_ONSCREEN;
|
||||
#ifdef PORT
|
||||
/* D466: an embedded object is visible exactly when its parent is. */
|
||||
portPropSetGameplayOnScreen(prop, prop->parent ? portPropGameplayOnScreen(prop->parent) : 1);
|
||||
#endif
|
||||
model->render_pos = (RenderPosView*)dynAllocate(model->obj->numMatrices << 6);
|
||||
|
||||
matrix_4x4_multiply_homogeneous(mtx, &obj->embedment->matrix, (Mtxf*)model->render_pos);
|
||||
@@ -5876,6 +5881,9 @@ s32 objTick(struct PropRecord *prop)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef PORT
|
||||
portD466SetLastPosVerdict(1); /* D466: forced-visible branches below are also gameplay-visible */
|
||||
#endif
|
||||
if ((obj->type == PROPDEF_TANK) && (get_ptr_for_players_tank() == prop))
|
||||
{
|
||||
var_v1_5 = 1;
|
||||
@@ -5897,6 +5905,9 @@ s32 objTick(struct PropRecord *prop)
|
||||
}
|
||||
|
||||
prop->flags |= PROPFLAG_ONSCREEN;
|
||||
#ifdef PORT
|
||||
portPropSetGameplayOnScreen(prop, portD466LastPosVerdict()); /* D466 */
|
||||
#endif
|
||||
mtxs = dynAllocate(model->obj->numMatrices << 6);
|
||||
model->render_pos = (RenderPosView *) mtxs;
|
||||
|
||||
@@ -7211,7 +7222,11 @@ void sub_GAME_7F04AC20(PropRecord *prop, ModelRenderData *mrData, s32 arg2)
|
||||
|
||||
if (destroyed)
|
||||
{
|
||||
#ifdef PORT
|
||||
destroyed = (get_BONDdata_field_10E0() != NULL);
|
||||
#else
|
||||
destroyed = get_BONDdata_field_10E0();
|
||||
#endif
|
||||
destroyed = destroyed != 0;
|
||||
}
|
||||
|
||||
@@ -12194,6 +12209,9 @@ void chrRenderHeldWeapon(void *renderContext, GUNHAND hand, Gfx **gdl)
|
||||
|
||||
chrModel = chr->model;
|
||||
prop->flags |= PROPFLAG_ONSCREEN;
|
||||
#ifdef PORT
|
||||
portPropSetGameplayOnScreen(prop, portPropGameplayOnScreen(chr->prop)); /* D466: held weapon follows its chr */
|
||||
#endif
|
||||
|
||||
renderData.basemtx = modelFindNodeMtx(chrModel, heldModel->attachedto_objinst, 0);
|
||||
|
||||
@@ -13591,6 +13609,45 @@ bool sub_GAME_7F054C58(coord3d *coord, f32 arg1)
|
||||
}
|
||||
|
||||
|
||||
#ifdef PORT
|
||||
/* D466: sub_GAME_7F054C58 at the authored draw distance -- i.e. WITHOUT the
|
||||
* D222/D218 `/ portDrawDistanceMultiplier()` division (multiplier treated as
|
||||
* 1), which exists only to widen the RENDER range. */
|
||||
static bool portSub7F054C58Gameplay(coord3d *coord, f32 arg1)
|
||||
{
|
||||
bool result = TRUE;
|
||||
coord3d *ptr = (coord3d*)fogGetNearFogValuesP();
|
||||
coord3d tmp;
|
||||
f32 sp20;
|
||||
|
||||
if (ptr != NULL)
|
||||
{
|
||||
coord3d *campos = bondviewGetCurrentPlayersPosition();
|
||||
Mtxf *mtx = camGetWorldToScreenMtxf();
|
||||
|
||||
tmp.x = coord->x - campos->x;
|
||||
tmp.y = coord->y - campos->y;
|
||||
tmp.z = coord->z - campos->z;
|
||||
|
||||
sp20 = tmp.f[0] * mtx->m[0][0] + tmp.f[1] * mtx->m[0][1] + tmp.f[2] * mtx->m[0][2];
|
||||
|
||||
if (sp20 > ptr->z)
|
||||
{
|
||||
f32 scalez = getPlayer_c_lodscalez();
|
||||
sp20 = ((sp20 - ptr->z) * 100 / arg1 + ptr->z) * scalez;
|
||||
|
||||
if (sp20 >= ptr->y)
|
||||
{
|
||||
result = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* Address: 7F054D6C
|
||||
*/
|
||||
@@ -13644,6 +13701,39 @@ bool posIsOnScreen(PropRecord *prop, coord3d *pos, f32 arg2, bool arg3)
|
||||
result = FALSE;
|
||||
}
|
||||
|
||||
#ifdef PORT
|
||||
/* D466 (#125): record whether this position would ALSO be "on screen" at
|
||||
* the level's authored draw distance (authored room set + authored fog,
|
||||
* multiplier treated as 1). `result` (extended) keeps deciding what is
|
||||
* drawn; callers that set gameplay state (chr.c, below) read the verdict.
|
||||
* Gameplay-visible is always a subset of result. */
|
||||
{
|
||||
int gameplayVisible = 1;
|
||||
if (portD466Active()) {
|
||||
gameplayVisible = 0;
|
||||
if (result) {
|
||||
s32 *rp;
|
||||
for (rp = room_ids; *rp >= 0; rp++) {
|
||||
if (portRoomGameplayVisible(*rp)) {
|
||||
gameplayVisible = portFogPositionVisibleGameplay(pos->f, arg2)
|
||||
&& (!arg3 || portSub7F054C58Gameplay(pos, arg2));
|
||||
/* D468: AI awareness stays inside the cartridge-widest
|
||||
* view (16:9 at the game's FOV) under ultrawide or a
|
||||
* raised FovScale: a centred sub-box of the screen. */
|
||||
if (gameplayVisible && portD468ClampActive()) {
|
||||
bbox2d faithful = g_CurrentPlayer->screensize;
|
||||
portD468ShrinkBox(&faithful.min.x);
|
||||
gameplayVisible = camIsPosInScreenBox(pos, arg2, &faithful);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
portD466SetLastPosVerdict(gameplayVisible);
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -265,13 +265,28 @@ void sub_GAME_7F092E50(void)
|
||||
MipMap2C_Something_Setup[2].loadtile.tl = flt_CODE_bss_80079E84;
|
||||
MipMap2C_Something_Setup[3].loadtile.sl = ((s32)flt_CODE_bss_80079E80 + 90) & 0xFF;
|
||||
MipMap2C_Something_Setup[3].loadtile.tl = ((s32)flt_CODE_bss_80079E84 + 150) & 0xFF;
|
||||
#ifdef PORT
|
||||
/* D465 (porting-notes B, 16-byte PC Gfx): u32 index 8 is N64 Gfx[4].w0,
|
||||
* the gsDPSetPrimColor whose low byte is the PRIM LOD fraction (the water
|
||||
* shimmer). On PC index 8 is Gfx[2].w0 -- tile 0's SetTileSize -- so the
|
||||
* shimmer value overwrote the T scroll offset every tick (water sliding
|
||||
* side to side, #127) and the crossfade never animated. Same write, on the
|
||||
* command's own word (the N64 word lives in the low dword of w0). */
|
||||
MipMap2C_Something_Setup[4].words.w0 = (MipMap2C_Something_Setup[4].words.w0 & ~(uintptr_t)0xFF) | (u32) ((sinf(flt_CODE_bss_80079E88) * 127.0f) + 128.0f);
|
||||
#else
|
||||
((u32 *) MipMap2C_Something_Setup)[8] = (((u32 *) MipMap2C_Something_Setup)[8] & ~0xFF) | (u32) ((sinf(flt_CODE_bss_80079E88) * 127.0f) + 128.0f);
|
||||
#endif
|
||||
|
||||
MipMap2C_Something2_Setup[2].loadtile.sl = flt_CODE_bss_80079E80;
|
||||
MipMap2C_Something2_Setup[2].loadtile.tl = flt_CODE_bss_80079E84;
|
||||
MipMap2C_Something2_Setup[3].loadtile.sl = ((s32)flt_CODE_bss_80079E80 + 90) & 0xFF;
|
||||
MipMap2C_Something2_Setup[3].loadtile.tl = ((s32)flt_CODE_bss_80079E84 + 150) & 0xFF;
|
||||
#ifdef PORT
|
||||
/* D465: as above; reads Something_Setup's word, exactly as the original. */
|
||||
MipMap2C_Something2_Setup[4].words.w0 = (MipMap2C_Something_Setup[4].words.w0 & ~(uintptr_t)0xFF) | (u32) ((sinf(flt_CODE_bss_80079E88) * 127.0f) + 128.0f);
|
||||
#else
|
||||
((u32 *) MipMap2C_Something2_Setup)[8] = (((u32 *) MipMap2C_Something_Setup)[8] & ~0xFF) | (u32) ((sinf(flt_CODE_bss_80079E88) * 127.0f) + 128.0f);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
// unsure if these structs are defined as something else, elsewhere
|
||||
struct unk_09B7A0_struct_parent {
|
||||
Vertex* unk00;
|
||||
#ifdef PORT
|
||||
void *unk04; /* D457: owner tag (model->obj), was s32 */
|
||||
#else
|
||||
s32 unk04;
|
||||
#endif
|
||||
s32 unk08;
|
||||
s16 unk0C;
|
||||
s16 unk0E;
|
||||
@@ -312,7 +316,11 @@ void sub_GAME_7F09BBBC(void)
|
||||
* PD name: vtxstore_allocate
|
||||
* Description: Allocation for batches within the storage space
|
||||
*/
|
||||
#ifdef PORT
|
||||
void *vtxstore_allocate(s32 arg0, s32 type, void *arg2, s32 arg3)
|
||||
#else
|
||||
s32 vtxstore_allocate(s32 arg0, s32 type, s32 arg2, s32 arg3)
|
||||
#endif
|
||||
{
|
||||
s16* var_t3;
|
||||
s16 temp_t2;
|
||||
@@ -336,7 +344,11 @@ s32 vtxstore_allocate(s32 arg0, s32 type, s32 arg2, s32 arg3)
|
||||
var_a2 = ((s16 *)&dword_CODE_bss_8007A0DC)[1];
|
||||
break;
|
||||
default:
|
||||
#ifdef PORT
|
||||
return NULL;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
var_v1_2 = 0;
|
||||
@@ -357,7 +369,11 @@ s32 vtxstore_allocate(s32 arg0, s32 type, s32 arg2, s32 arg3)
|
||||
if (var_a2 < var_v0) {
|
||||
sub_GAME_7F09B7A8();
|
||||
sub_GAME_7F09B7E4();
|
||||
#ifdef PORT
|
||||
return NULL;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
// FAKE
|
||||
if (var_v0) {}
|
||||
@@ -390,9 +406,15 @@ s32 vtxstore_allocate(s32 arg0, s32 type, s32 arg2, s32 arg3)
|
||||
} else {
|
||||
*var_t3 -= temp_t2;
|
||||
}
|
||||
#ifdef PORT
|
||||
return (void *)var_t0[var_a1].unk00;
|
||||
}
|
||||
return NULL;
|
||||
#else
|
||||
return (s32)var_t0[var_a1].unk00;
|
||||
}
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
|
||||
void sub_GAME_7F09B820(void);
|
||||
void sub_GAME_7F09BBBC(void);
|
||||
#ifdef PORT
|
||||
void *vtxstore_allocate(s32 arg0, s32 type, void *arg2, s32 arg3);
|
||||
#else
|
||||
s32 vtxstore_allocate(s32 arg0, s32 type, s32 arg2, s32 arg3);
|
||||
#endif
|
||||
void sub_GAME_7F09C044(Vertex* arg0);
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user