forked from Ports/goldeneye-pc-port
Merge main into feat/d216-skip-intro (M-84 + D215 findings rows)
# Conflicts: # docs/dev/QOL-INVENTORY.md # docs/dev/findings-index.csv # docs/dev/findings.md # port/src/video.c
This commit is contained in:
commit
208fcfe27d
14 files changed
+343
-29
No files matched your search
@@ -46,6 +46,7 @@ Two classes:
|
||||
| `GE_D193A` | `src/game/chr.c` (`chrTick` → `d193aSample`, plus `d193aMoved` in `chrUpdateAnim`; `#ifdef PORT`) | Per-wall-second per-ticked-chr locomotion telemetry (D193/D209): `chr=` chrnum, `act=` ACT_TYPE, `speed=` world-units travelled/s, `gtd=` g_GlobalTimerDelta, `tier=` `act_gopos.unk59` SPEED tier (0 walk / 1 run / 2 sprint; `-1` when not ACT_GOPOS), `gspd=` `act_gopos.speed` (**a turn rate, not a travel rate** — 0 means running straight), `typ=` PROP_TYPE (3=CHR, 6=VIEWER), `tick=`/`move=`/`frz=` chrTick entries vs `chrUpdateAnim` calls vs freeze-gated (move « tick = the chr is being visibility-culled out of its own locomotion; `move=0` at high `speed` = WAYMODE_MAGIC), `hid=` CHRFLAG_HIDDEN, then the root-motion scalars `scale` / `xlscale` / `animrate` / `playspeed` / `mspeed` (model->speed) / `endframe` / `anim` ptr / `aidx=` that pointer reverse-resolved to its `animation_table_ptrs1` index (40 walking, 41 sprinting, 42 running, 107 walking_unarmed — map via `src/game/initanitable.c`). `GE_D193A_CHR=<n>` restricts to one chrnum. **This probe found D209**: Cradle showed `tier=1` (RUN) with `aidx=40/107` (walk anims) on every sample. | **live** (D193/D209) |
|
||||
| `GE_D193B` | `src/game/model.c` (`sub_GAME_7F06D3F4`, `#ifdef PORT`) | Rate-limited (1/97) raw anim root-motion decode trace (D193): `joint=` / `flip=` / `base=` mtx channel index / `frame=` / `tmp=` decoded s16 translation triple from the anim bitstream / `angle=` / `anim` ptr / `bd=`+`bs=` the D32 u32 bitDescriptors/bitStream addresses / `njoints`. M-80 Facility: `base=0`, `angle=0` **always** (root-motion facing delta never non-zero — top lead), `tmp` ≈ (±small, ~1086 hip-height, ±small). Compare `tmp`/`angle` magnitudes against a clean N64 decomp build of the same anim+frame. | **live** (D193) |
|
||||
| `GE_D172` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_tri1` — SETCOMBINE dedup log + non-LOD multitex tri dump; `#ifdef PORT`) | Particle magenta/cyan colour (D172). Logs each distinct SETCOMBINE + active cycletype, and — for a 2-cycle tri that consumes TEXEL1 with `tex_lod=0` and a distinct 2nd tile — the tile fast3d *samples* for each texunit vs what the DL *configured*. **Found D172**: 40/40 particle tris configured tile 1 @ tmem 392 (fire) but sampled tile 0 (smoke) → `smoke*smoke`. FIXED (`gfx_lod_tile_offset` `return rdp.tex_lod ? 0 : i`). Probe kept inert. | **live** (D172 FIXED, probe kept) |
|
||||
| `GE_DVM` | `src/game/gunfire.c` (`gunUpdateAndFire` gate dump + `gunRenderFirstPersonGunModels` render dump; `#ifdef PORT`) | 1P weapon viewmodel (D215). `[DVM]` line: the `field_87F` show/hide gate terms + `Gun_hand_without_item` sub-terms + `copy_of_body_obj_header` RootNode/Switches/numRecords. `[DVMr]` line: `87F` + `cmds_emitted` = gfx commands the weapon `subdraw()` produced (healthy ≈ 150; the D215 bug showed **1**). Rate-limited ~1/s per hand. **Found + verified D215** (`renderdata.flags` read 0 → all weapon DL nodes gated off). Probe kept inert. | **live** (D215 FIXED, probe kept) |
|
||||
| `GE_DTEX` | `port/fast3d/gfx_pc.cpp:1001` | Per-`import_texture` param log (dims / line / size / lod / gen_mipmaps). Used to root-cause RC2 and D159. Kept as an inert diagnostic. | **live** (RC2/RC3 not finished) |
|
||||
| `GE_TEXDUMP` | `port/fast3d/gfx_pc.cpp:1020`, `port/fast3d/gfx_opengl.cpp:700` | PPM dump of every uploaded texture + a `fmt/siz/palfmt/palidx/pal[0..3]` line. Root-caused D161 (Depot ceiling). Kept as an inert diagnostic. | **live** (RC3 not finished) |
|
||||
| `GE_D116` | `port/fast3d/gfx_pc.cpp:1710,2362`, `src/game/textrelated.c:265,541` (cached `ge_d116`) | HUD/text "X-mirror" (D114/D116) vbo/uv trace. **D114/D116 CLOSED — NOT A BUG (M-33/D168): the captures were upside-down, not mirrored.** Probe is now dead scaffolding — strip on the next cleanup pass. | dead (D114/D116 closed) |
|
||||
|
||||
@@ -27,7 +27,7 @@ once the game is playable start-to-finish without crashes.
|
||||
| **D182** | SELECT FILE screen degrades after navigating away and back (main menu → file select, or backing out of file select and re-entering). On the fresh screen the 007 gun-barrel spiral / Bond silhouette renders behind the file boxes; after a round-trip **the Bond image stops drawing entirely** and the whole background gains the **comb / interlacing** artifact (horizontal line scramble). File boxes, text and the Copy/Erase icons still draw. | **Observed, screenshot committed** (M-35, user QA; `docs/img/bugs/d182-file-select-backout-20260902.png`). Two symptoms, likely linked: **(1)** background 3D image stops rendering after re-entry → a front-end model/DL whose `render_pos` / segment binding is only valid on first construction (D149 / D75 `constructor_menuXX` family — the dossier/wallet models have the same "renders once" behavior). **(2)** interlacing = **M-36 triage: Family A**, the `import_texture_*` `line` vs `full_image_line` shear (see D176(b) / `docs/dev/M-36-TRIAGE.md`) — **not** the D159 `texSwapAltRowBytes` path (that is a full PORT no-op). Open question: why only *after* a round-trip — first import may land `line == width`, re-import may not, or the tile descriptor is stale on re-entry. Repro: `GE_STARTMENU` to file-select, back out with ESC/B, re-enter; `GE_DTEX` on the spiral texture across both visits. Shares defect (2) with D176(b). |
|
||||
| **D197** | Character face/head textures wrap wrong — the face texture repeats around the side of the head "and continues weird" instead of clamping to the front. Seen on **Silo** (M-49 playtest), intermittent ("sometimes"). Reads as **`G_TX_CLAMP` not honoured** for the character-skin tile in fast3d (→ `GL_REPEAT`, so the face UV region tiles around the skull), or wrong `shifts`/`shiftt` / LOD-tile selection on the head texture, or the D167 non-PoT wrap-period issue (head textures are often non-PoT) hitting skinned models. | **Observed, not investigated** (M-49, user bug report — needs a capture + which chr/level). Distinct from D176(b)/D182(2) (those are *bg wall* pitch shear). Related: RC3/D167 (`Video.WrapFix`, non-PoT wrap period) + the `texWriteTileLods` `shifts=shiftt=lod` note in findings §F "D176(b) → 4. candidate 2". First checks: does `gfx_pc.cpp` map N64 `cms/cmt` `G_TX_CLAMP` → `GL_CLAMP_TO_EDGE` for character DLs; is the head tile getting the base tile vs an LOD tile (D107); try `Video.WrapFix=1` on a repro. |
|
||||
| **D195** | Transparency / alpha-blended surfaces render wrong — reported on **Control** (Severnaya bunker basement — Natalya's console room; heavy on glass partitions and the translucent projected/holographic wall displays). Exact failure mode not yet pinned by the reporter (M-49 playtest): candidates are alpha-blended geometry drawing fully opaque, translucent panels drawing invisible, or draw-order/sort so translucent surfaces occlude what's behind. | **Observed, not investigated** (M-49, user bug report — needs a `GE_PCDUMP` capture + which surfaces). fast3d render-mode / combiner path. Same neighborhood as D161 (CI8 tile + `G_TT_NONE` → garbage), D172 (particle-sprite channel order), D176(b) (texture pitch shear) — but this is a *blend/alpha* defect, not a texture-decode one. Note D128 already touched Control's `specialportalarray` (portal struct stride) — check whether the affected surfaces are portal-adjacent. First checks: does `gfx_pc.cpp` honour `G_RM_*` XLU render modes + `G_AC_THRESHOLD`/`G_CC` alpha for these DLs; is `gDPSetRenderMode` `w1` being parsed (cf. findings §F "zbuf-clear w1 scribble"); is Z-write-vs-blend ordering right for XLU surfaces. Get the reporter to say: opaque-where-should-be-clear, invisible, or wrong edges? |
|
||||
| **1P weapon viewmodel** | In-level first-person weapon models don't draw at all (no gun visible while playing). | **Static research done — `docs/dev/VIEWMODEL-RESEARCH.md`.** Ruled out: arena lifetime (setup + render same frame; `dynSwapBuffers` only at frame end), word-index math (D52 `u32*` pool), segment/render_pos mechanism (bodies + watch-menu weapon models render through it). Ranked suspects: **#1 `field_87F` gate** — the `(Gun_hand_without_item(h) == 0)` clear-term (`gun.c:812`) reads as "hide when holding an item"; a PC init divergence in `hand_item`/`field_2A44`/`hand_invisible` yields exactly this symptom (render loop `continue`s at `gunfire.c:1537`). **#2** `weaponRwPool[192]` capacity vs PC-wide rw records (no bounds check; DEBUG print's 32-word threshold is stale N64). **#3** stale `copy_of_body_obj_header` → NULL `RootNode` → `subdraw` silent no-op (`model.c:5326`). Next: one `GE_DVM=1` probe in the setup block triages all three in a single BUNKER1 run. |
|
||||
| **1P weapon viewmodel** | **RESOLVED (M-83, D215).** In-level first-person weapon models drew nothing. Root cause: `gunRenderFirstPersonGunModels` built its `ModelRenderData` via `*(ModelRenderData*)&D_80035CC0` — a reinterpret across two separately-declared adjacent globals that only lands right on the N64 linker + 4-byte pointers; on x86-64 `renderdata.flags` read 0, gating every weapon DL node off in `modelRenderNodeGundl`. All 3 ranked suspects in VIEWMODEL-RESEARCH.md were ruled out first by the `GE_DVM=1` probe. Fixed `#ifdef PORT` (explicit field init). `subdraw` 1→153 gfx cmds; owed one BUNKER1 eyeball. |
|
||||
|
||||
## Resolved (for reference)
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
# QoL Inventory — PD-vs-GE options diff
|
||||
|
||||
Status: **read-only research, parked** (input to project Phase 4). No code
|
||||
changed. Companion to `WIDESCREEN-FOV-PLAN.md` and `UNLOCKED-FPS-PLAN.md`.
|
||||
Status: **partially implemented.** M-83 landed three port-only quick wins —
|
||||
DisplayFPS (D213), anisotropic filtering (D212), and the FOV slider (D211, via
|
||||
`WIDESCREEN-FOV-PLAN.md` Phase 4). Rebinding / SkipIntro / per-pad tuning /
|
||||
window HiDpi still open. Companion to `WIDESCREEN-FOV-PLAN.md` and
|
||||
`UNLOCKED-FPS-PLAN.md`.
|
||||
|
||||
Method: diffed the config surface of both ports — every `configRegister*`
|
||||
key in `pd_port/port/src/*.c` vs `port/src/config.c` + the F10 overlay
|
||||
@@ -25,10 +28,10 @@ ScreenShakeIntensity. Debug: FrameDump, InputLog.
|
||||
|
||||
| Feature | PD location | Class | Effort | Notes / priority |
|
||||
|---|---|---|---|---|
|
||||
| **Key rebinding** (config-string binds per controller/action) | `input.c:69,627-675,1548` (`bindStrs`, `inputParseBindString`) | P | M | **High.** Explicit Phase 4 debt ("rebinding UI" — note PD has *no* UI either; config-file driven). Port-only in `port/src/input.c`. |
|
||||
| **Key rebinding** _(DONE M-83, D214)_ | `port/src/input.c` `[Input.Bind]` section | P | M | Keyboard only, 12 actions, `Input.Bind.* = Key,Key` (SDL scancode names), file-driven like PD. Defaults = prior FPS layout (verified). Per-controller pad rebinding + a UI still open. |
|
||||
| **SkipIntro** _(DONE M-83, D216)_ | `Game.SkipIntro` -> `src/game/lv.c` | P | S | Boots to the SELECT FILE menu, skipping the legal screen + Nintendo/Rare/GoldenEye logo attract loop. Reuses the game's own post-intro route (sets `is_first_time_on_main_menu=FALSE` + `menu_update=MENU_FILE_SELECT`); one `#ifdef PORT` line in the existing GE_STARTMENU block. Verified headless (screenshots). |
|
||||
| **DisplayFPS** (+ interval) | config `Video.DisplayFPS(Interval)` | P | S | Cheap; useful for FPS-plan verification too. |
|
||||
| **Anisotropic filtering** (≤16×), MipmapFilter, TextureFilter2D | `gfx_opengl` texparam path | P | S–M | **High at 4K.** GE's `gfx_opengl.cpp` has mipmap nY but no aniso (`GL_TEXTURE_MAX_ANISOTROPY_EXT`). Port-only. |
|
||||
| **DisplayFPS** _(DONE M-83, D213)_ | config `Video.DisplayFPS` | P | S | Top-right readout, config-only; ~0.5 s sample window. |
|
||||
| **Anisotropic filtering** _(DONE M-83, D212)_ | `Video.Anisotropy` (1–16, dflt 4) | P | S | fast3d already had the GL hook + a hardcoded 4×; M-83 exposed it as config. MipmapFilter / TextureFilter2D still not surfaced. |
|
||||
| Per-pad tuning: per-stick deadzones, stick scale, rumble scale, device index, swap sticks, C-button mapping | `input.c` padsCfg block | P | M | Medium. Matters for real-controller users; PD pattern is a config section per pad. |
|
||||
| FakeGamepads / FirstGamepadNum / UseHIDAPI | `input.c` | P | S–M | Niche (local MP on PC); defer until MP is actually played. |
|
||||
| Window polish: DefaultFullscreen/Maximize, CenterWindow, AllowHiDpi, ExclusiveFullscreen | `video.c` vid* block | P | S | HiDpi + center are the useful ones on modern Windows. |
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# 1P weapon viewmodel — static research (read-only)
|
||||
|
||||
> **RESOLVED 2026-09-09 (M-83, D215).** None of the three ranked suspects below
|
||||
> was the cause — all were ruled out by the `GE_DVM=1` probe (added this
|
||||
> session). The bug was `renderdata.flags == 0` in `gunRenderFirstPersonGunModels`:
|
||||
> the N64 `*(ModelRenderData*)&D_80035CC0` reinterpret-across-adjacent-globals
|
||||
> breaks under x86-64 pointer widening + no guaranteed global layout. Fixed
|
||||
> `#ifdef PORT` in `src/game/gunfire.c`. See findings.md D215. This doc is kept
|
||||
> for the ruled-out analysis only.
|
||||
|
||||
Companion to `GRAPHICS-BACKLOG.md` ("1P weapon viewmodel") and findings D115
|
||||
item #5 / AUDIT-M6 #5. Goal: narrow the five audit suspects to a ranked,
|
||||
probe-ready list. No code changed by this doc.
|
||||
|
||||
@@ -213,10 +213,17 @@ reason. Whole-feature fallback to A only if B fails verification overall.
|
||||
verification at 4:3 + 16:9, finding entry. Fallback-to-A decisions
|
||||
happen here, per surface, with documented reason.
|
||||
|
||||
### Phase 4 — FOV slider (port-only)
|
||||
### Phase 4 — FOV slider (port-only) — **LANDED (M-83, D211)**
|
||||
|
||||
- [ ] RSP projection post-scale; `Video.FovScale` config + F10 overlay;
|
||||
verify composition with aim zoom at non-100 scale.
|
||||
- [x] RSP projection post-scale (`gfx_apply_fov_scale()` in
|
||||
`port/fast3d/gfx_pc.cpp`, load-path only); `Video.FovScale` config
|
||||
(percent of vertical FOV, 50–150, default 100 = byte-identical) + F10
|
||||
overlay row. Perspective-only (ortho/HUD untouched); columns 0+1 scaled
|
||||
together so aspect is preserved. Build clean, `bunker1` no-op confirmed.
|
||||
- [ ] Owed: interactive eyeball of composition with aim-zoom at scale ≠ 100.
|
||||
|
||||
This phase does **not** depend on the Phase 2/3 game-code exception — it
|
||||
shipped standalone ahead of the rest of the plan.
|
||||
|
||||
### Phase 5 — Verification & sign-off
|
||||
|
||||
|
||||
@@ -53,7 +53,14 @@ D179,FIXED,Packaged build crashes after the logos —,"Diagnosed, not fixed —"
|
||||
D180,PARTIAL,"Native-PC input pass (M-XX QoL run, qol/native-pc-input-menu, port-only).","LANDED, port-only, no #ifdef PORT."
|
||||
D181,PARTIAL,Game.ScreenShakeIntensity —,"LANDED behind config, default = original."
|
||||
D184,PARTIAL,F10 port-layer options overlay —,"LANDED, port-only, no #ifdef PORT in src/."
|
||||
D211,PARTIAL,"PC FOV slider — Video.FovScale (M-83 QoL wave, port-only, WIDESCREEN-FOV-PLAN.md Phase 4).",PARTIAL — M-84 refix:
|
||||
D212,PARTIAL,Anisotropic-filtering config wiring —,"LANDED, port-only. Build clean."
|
||||
D213,PARTIAL,On-screen FPS readout —,"LANDED, port-only. Build clean; bunker1 no-op confirmed."
|
||||
D214,PARTIAL,"Keyboard rebinding — [Input.Bind] config section (M-83, port-only, PD parity, Phase 4 debt).",PARTIAL — M-84: code PASS (alt/ctrl); one doc-only fix.
|
||||
D215,FIXED,1P weapon viewmodel never drew in-level —,"FIXED (src/game/gunfire.c, #ifdef PORT)."
|
||||
D216,PARTIAL,"Game.SkipIntro — boot straight to the file-select menu (M-83, port + one #ifdef PORT game-code line, PD parity, Phase 4 quick win).",LANDED + VERIFIED this session.
|
||||
D217,OPEN,1P weapon viewmodel + third-person Bond model textures render garbled —,"OPEN — observed, not investigated (M-84)."
|
||||
D218,PROPOSED,Wide FOV exposes the per-level draw-distance / fog boundary —,"Logged (M-84), fix proposed + parked."
|
||||
D85,OPEN,room primary/secondary DL → texLoadFromGdl garbage,"OPEN (safety-netted; widen/pool fixes landed, geometry now renders)"
|
||||
D86 · D87,RESOLVED,modelInitRwData truncated ptr · attract-demo BE ramromfilestructure,resolved
|
||||
D88.4,RESOLVED,propDefs polymorphic record stream not byteswapped → setupDoor crash,resolved (d88_propdefs.py); layout audit M-20 → D132
|
||||
|
||||
|
@@ -427,7 +427,14 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
||||
| D180 | **Native-PC input pass (M-XX QoL run, `qol/native-pc-input-menu`, port-only).** Three parts, all config-gated with default = prior behaviour: **(WI-1) `Input.MouseCaptureMode`** (0 = legacy always-grab; 1 = Quake-style click-to-lock) in `port/src/input.c` + `port/src/video.c` — in mode 1 the OS cursor is free until a click lands in the game window (`SDL_MOUSEBUTTONDOWN` → `inputNotifyClick`), and ESC / focus-loss / entering any front-end menu (`current_menu != MENU_RUN_STAGE`) frees it again; re-entering a stage while still "armed" re-locks so unpausing needs no click. `reconcileGrab()` runs once per controller-0 poll. Mouse buttons are suppressed from the game while the cursor is free in-stage (no phantom fire). Controller paths untouched. **(WI-2) absolute-cursor menu tracking** — when capture mode is on and the cursor is free in a menu, the D165/D169 pointer P-controller takes its target from the real cursor's absolute window position mapped onto the live virtual front-end rect (`getPlayer_c_screen*`), giving true 1:1 tracking instead of the relative-delta estimator. Relative path unchanged for legacy/grabbed. **(B3) `Input.MouseAimSpeed` default 25 → 16** (aim mode still overshot at 25 per the backlog). | LANDED, port-only, no `#ifdef PORT`. Build 241/241, `-level_09` framediff 3/3 within threshold (unregressed), `GE_STARTMENU` menu boot crash-free. **Feel-checks owed** (headless can't drive the mouse): click-to-lock ergonomics, absolute menu tracking, the new aim-speed default. |
|
||||
| D181 | **`Game.ScreenShakeIntensity` — first route-(b) `src/` gameplay-cosmetic hook (M-XX QoL run).** `src/fr.c viShake()` gains a single `#ifdef PORT` line — `intensity *= portScreenShakeScale;` (extern `f32`, defined + `configRegisterFloat`'d 0.0–10.0 in `port/src/video.c`) — before the existing clamp. Default `1.0f` ⇒ exact no-op, every headless golden dump byte-unaffected; `0.0` disables explosion/effect screen shake, up to `10.0` exaggerates it. N64 build (no `-DPORT`) keeps the original line verbatim. Precedent per `docs/BACKLOG.md` "Fun features → screen-shake intensity slider" and AGENTS.md #2's documented-exception path. Same policy class as a future FOV hook. | LANDED behind config, default = original. Build green. |
|
||||
| D184 | **F10 port-layer options overlay — the approach-(C) surface from `OPTIONS-MENU-PLAN.md` (M-37).** New `port/src/optionsoverlay.c` + `port/include/optionsoverlay.h`: an immediate-mode overlay drawn as its own fast3d 2D DL, appended after the game DL in `gfx_pc.cpp gfx_run()` (`optionsOverlayEmit()` → NULL when closed ⇒ **zero bytes appended, golden dumps byte-identical**). F10 toggles (`video.c videoPumpEvents`, next to F12); while open, `input.c inputComputePad(0)` returns a neutral pad and routes arrows/enter/wheel/mouse-buttons to `optionsOverlayHandleInput()` (mirrors the WI-1/D180 swallow). ESC closes; `configSave()` on close. `config.c` gains `configForEachOption()` + a `configSetOptionMeta()` label/step/enum side table (no per-key knowledge in config.c). Live knobs (VSync/FpsCap/TextureFilter via `videoRequestLiveConfig()`; mouse + screen-shake via the registered pointer) apply immediately; MSAA tagged "(restart)". v1 rows per plan §3: VSync, FpsCap, MSAA, TextureFilter, MouseAimSpeed, MouseTurnSpeed, MouseInvertY, MouseCaptureMode, ScreenShakeIntensity. | **LANDED, port-only, no `#ifdef PORT` in `src/`.** Build links 241/241. **Runtime verification NOT run this session** — the headless env kills the GUI process after ~8 s and `gfx_opengl_dump_bound_fbo` writes 0-byte PPMs here, so framediff / 60 s soak / overlay-frame eyeball are all owed on an interactive machine. Overlay-closed no-op is code-evident (emit returns NULL before touching the DL buffer). |
|
||||
| D211 | **PC FOV slider — `Video.FovScale` (M-83 QoL wave, port-only, `WIDESCREEN-FOV-PLAN.md` Phase 4).** fast3d applies a multiplicative half-angle scale to every *perspective* projection matrix as it is loaded (`gfx_apply_fov_scale()` in `gfx_pc.cpp gfx_sp_matrix`, on the `G_MTX_PROJECTION | G_MTX_LOAD` path only — GE always reloads its view projection via `guPerspectiveF` at `fr.c:725`, so a relative `G_MTX_MUL` onto an already-scaled matrix cannot compound). Columns 0 and 1 are scaled by the same factor `tan(half_orig)/tan(half_adj)` → H and V FOV widen uniformly, aspect ratio exactly preserved (no stretch), and because the game rewrites fovy every frame for aim-zoom the scale composes with zoom for free. Ortho matrices (`P[3][3]==1`) are left untouched so HUD/menu 2D is unaffected. `Video.FovScale` = percent of the original vertical FOV, 50–150, **default 100 ⇒ `g_fov_scale == 1.0f` ⇒ guarded early-return, zero FP ops on the matrix ⇒ golden dumps byte-identical**. F10 overlay row "FOV scale %". Math checked offline (`s=1.25 → fovy 60°→75°`, aspect held at 1.3333). | **PARTIAL — M-84 refix: game-side FOV scale (`src/fr.c`, `#ifdef PORT`), front-end gated, user-verified.** Original M-83 note follows. LANDED, port-only; build clean; `bunker1` framediff worst_cell 12.219 vs 12.221 baseline (= render noise, no-op confirmed). **M-84 refix — the fast3d hook only scaled *pure* perspective loads; GE draws the world through `g_CurrentPlayer->field_10E0`, a projection × view matrix pre-combined on the CPU (`bondview2.c:8224`) that failed the `gfx_apply_fov_scale` guard, so only the pause/watch model + sky FOV changed (user report).** Moved the scale game-side to the `guPerspectiveF` chokepoint (`fr.c:718`, `#ifdef PORT`, `frFovY *= portFovScale`, clamp 160°), which lands *before* the CPU combine; fast3d `gfx_apply_fov_scale`/`gfx_set_fov_scale` removed; `port/src/video.c` sets `f32 portFovScale`. Also gated to `lvlGetCurrentStageToLoad() != LEVELID_TITLE` — the front end (main menu / file+mission select / briefing) renders through the same path (`lv.c lvlRender`) and its 3D is authored at a fixed FOV. **User-verified: front end unaffected + in-level view widens (Dam tunnel at high FovScale).** Nits: HUD-static / aim-zoom-composes eyeball at ~120. Caveat: `currentPlayerSetCameraScale` frustum-cull / screen↔world math stays at nominal FOV → edge geometry culls a hair early at large scale (cosmetic). Wide FOV also exposes the per-level draw-distance boundary → **D218** (logged, parked). |
|
||||
| D212 | **Anisotropic-filtering config wiring — `Video.Anisotropy` (M-83 QoL wave, port-only).** The fast3d GL backend already implemented `set_anisotropy_level` / `get_max_anisotropy_level` and applied `GL_TEXTURE_MAX_ANISOTROPY` on mipmapped tiles with a hardcoded `current_anisotropy_level = 4`; there was no way to configure it. Added the `gfx_set_anisotropy_level()` extern-C wrapper in `gfx_pc.cpp` (clamps to `[1, GL max]`, `reset_texture_state()` so live changes take), declared in `gfx_api.h`, and `Video.Anisotropy` (1–16) in `port/src/video.c` wired through `videoApplyImageOptions()` on init + live-config. **Default 4 = the value fast3d already applied ⇒ no visual delta at defaults.** F10 overlay row "Anisotropic". | **LANDED, port-only.** Build clean. **Owed:** eyeball at 16× on a grazing surface (Depot roof / Dam walkway) vs 1×. |
|
||||
| D213 | **On-screen FPS readout — `Video.DisplayFPS` (M-83 QoL wave, port-only, PD parity).** `port/src/optionsoverlay.c` samples frame time once per emitted frame (`fpsTick()` via `sysGetMicroseconds()`, ~0.5 s window) and, when enabled, emits a small top-right green "N FPS" DL even while the F10 panel is closed. Config-only knob (kept off the 13-row panel, which is at its documented layout limit — matches PD, whose `DisplayFPS` is also file-only). **Default 0 ⇒ `optionsOverlayEmit()` still returns NULL when the panel is closed ⇒ golden dumps byte-identical.** | **LANDED, port-only.** Build clean; `bunker1` no-op confirmed. **Owed:** eyeball that the counter reads a sane ~30 and doesn't overlap the HUD. |
|
||||
| D214 | **Keyboard rebinding — `[Input.Bind]` config section (M-83, port-only, PD parity, Phase 4 debt).** `port/src/input.c` gains 12 gameplay actions (Forward/Back/StrafeLeft/StrafeRight/TurnLeft/TurnRight/Fire/Aim/Action/Cancel/LeanLeft/Start), each a comma-separated list of up to 4 SDL scancode names (`Input.Bind.Forward = W,Up` …). `inputRebuildBinds()` (called from `inputInit()`, after `configLoad()`) parses them via `SDL_GetScancodeFromName`; unknown names log a warning and are skipped. The old hardcoded `keyDown(ks, SDL_SCANCODE_*)` block in `inputComputePad` is replaced by `actHeld(ks, IA_*)`. Mouse buttons (fire = LMB, aim = RMB) stay hardwired; the mouse-wheel weapon-cycle A-pulse is untouched. **Defaults reproduce the previous FPS layout exactly — verified: all 20 default key names round-trip to the identical `SDL_Scancode` the old code used (`SDL_GetScancodeFromName` test, 0 mismatches).** Config-file only (no in-overlay text entry — matches PD, whose rebinding is also file-driven); a fresh `ge007.ini` gets the full `[Input.Bind]` block with the defaults visible. | **PARTIAL — M-84: code PASS (alt/ctrl); one doc-only fix.** LANDED, port-only, no `#ifdef PORT`; build clean. One doc bug fixed (no code change): `config.c` groups keys by the substring *before the last `.`*, so the section header is **`[Input.Bind]`**, not `[Bind]` — earlier finding/commit/QOL text was wrong. Working syntax: put `Fire = Left Alt` under `[Input.Bind]`, or write a fully-qualified `Input.Bind.Fire = Left Alt` line anywhere (`config.c:307`). Separately: the pause→**watch menu** does not wire Esc→back (still Enter to open/close/back) — pre-existing, `options.c`, minor. |
|
||||
| D215 | **1P weapon viewmodel never drew in-level — `gunRenderFirstPersonGunModels` builds its `ModelRenderData` from a reinterpret across separately-declared adjacent globals that only works on N64 (M-83, D3x/ABI class; retires VIEWMODEL-RESEARCH.md).** The N64 line `renderdata = *(ModelRenderData *)&D_80035CC0;` (`gunfire.c`) casts the lone `u32 D_80035CC0 = 0;` to a whole `ModelRenderData` and reads on into the next declared global, `u32 D_80035CC4[] = {1, 3, 0, 0, …}` (`gun.c`) — yielding `{ basemtx=0, zbufferenabled=1, flags=3, rest=0 }` **only because the N64 linker lays the two globals out contiguously in declaration order and pointers are 4 B**. On x86-64 it breaks twice: `ModelRenderData`'s three pointer members (`basemtx`/`gdl`/`mtxlist`) widen 4→8 B so `flags` is no longer at struct offset 8, and C guarantees nothing about the two globals' relative placement. Measured (`GE_DVM=1`, BUNKER1): `renderdata.flags == 0`, so **every** weapon-model DL node was gated off at `modelRenderNodeGundl` (`(renderdata->flags & 1)`), and `subdraw()` for the weapon emitted just its `gSPSegment` — 1 gfx command instead of ~150. All three VIEWMODEL-RESEARCH.md ranked suspects were ruled out by the same probe first: `field_87F==1` (gate open), `RootNode` valid, `numRecords 22 < 192` (pool fine), node walk visits 24+ valid `DL`/`SWITCH`/`BSP` nodes. **Fix:** `#ifdef PORT` → set the three fields explicitly (`zbufferenabled=TRUE; flags=3;`), N64 line verbatim under `#else`. ABI/layout only, no logic change. | **FIXED (`src/game/gunfire.c`, `#ifdef PORT`).** Build clean. `GE_DVM=1` post-fix: `flags=0x3`, weapon `subdraw()` now emits **153** gfx commands (was 1). Regression sweep crash-free. `GE_DVM` gate+render probes left in, env-gated (cf. `GE_D193*`). **Owed:** one BUNKER1 eyeball that the gun model is actually visible + correctly posed (headless can't see pixels; the DL is now provably generated). The committed `bunker1` golden will need re-baselining — the gun now renders in-frame. |
|
||||
| D216 | **`Game.SkipIntro` — boot straight to the file-select menu (M-83, port + one `#ifdef PORT` game-code line, PD parity, Phase 4 quick win).** New `s32 portSkipIntro` (`port/src/video.c`, `configRegisterInt("Game.SkipIntro", 0, 1)`), read once in `src/game/lv.c` at `LEVELID_TITLE` stage load — in the `else` of the existing `GE_STARTMENU` `#ifdef PORT` block. When set it does what every intro stage's own `!is_first_time_on_main_menu` branch does (`front.c`): `is_first_time_on_main_menu = FALSE; prev_keypresses = TRUE; maybe_is_in_menu = TRUE; menu_update = MENU_FILE_SELECT;` — i.e. it reuses the game's own post-intro route rather than inventing one, so the legal screen + Nintendo/Rare/GoldenEye logo attract loop is skipped. `GE_STARTMENU` still wins when both are set. Default 0 ⇒ untouched. | **LANDED + VERIFIED this session.** Build clean. Headless: default boot frame 90 = the TWYCROSS classification screen (10% non-black), `SkipIntro=1` frame 90 = the **SELECT FILE menu** (folders/photos/cursor all render, 89% non-black), no crash across repeated boots. Screenshots captured + eyeballed. **Owed:** nothing critical — a human boot to confirm the menu is fully interactive (cursor moves, a save slot opens). |
|
||||
| D217 | **1P weapon viewmodel + third-person Bond model textures render garbled — wrong/stale palette on model-DL CI textures (M-84, user QA; surfaced by D215 now that the viewmodel draws).** BUNKER1/Facility: the PP7 viewmodel has yellow+blue scrambled texels and a solid **green** patch by the hammer; Bond's in-game **watch face** shows the same green mismatch; user reports the **third-person Bond world model** does this "sometimes" too. Signature matches the **D161 family** — a CI-format tile drawn without the expected TLUT state, so fast3d's `import_texture` indexes a **stale `rdp.palette`**. D161 fixed the explicit `G_TT_NONE` case; this looks like model DLs not emitting `gDPLoadTLUT` through the port path, or the palette segment not resolving for weapon/character geometry. **Not a D215 regression** — D215 only un-gated the DL. | **OPEN — observed, not investigated (M-84).** Next: `GE_TEXDUMP` / `GE_DTEX` on a weapon texture in BUNKER1 — dump `fmt/siz/palfmt/palidx` + PPM, compare vs a clean world CI tile; check whether `gDPLoadTLUT` reaches `gfx_pc.cpp` for model DLs. |
|
||||
| D218 | **Wide FOV exposes the per-level draw-distance / fog boundary — "blue artifacting" down long peripheral sightlines (M-84, user QA on the D211 FOV slider).** BUNKER→**Dam main tunnel** at `FovScale` ~150: geometry / backdrop past the level's fog-end distance shows at the widened frustum edges (blue = Dam's fog tint). Not a render bug — `fogLoadCurrentEnvironment` (`bgfog.c:305`) does `viSetZRange(Visibility.BlendMultiplier, Visibility.FarFog)`, so **`Visibility.FarFog` is both the far clip plane and the fog-saturation distance**; levels tune it for the stock ~60° FOV. | **Logged (M-84), fix proposed + parked.** Plan: `Video.DrawDistance` (percent, 100–400, default 100 ⇒ no-op) `#ifdef PORT` multiplier on `FarFog` at `bgfog.c:305` (+ the direct `arg0->Visibility.FarFog` reads just below, so fog stays consistent) plus `Video.DrawDistanceAutoFov` (0/1, default 1 — couples draw distance to `FovScale` unless `DrawDistance` is set explicitly). Clamp effective multiplier ≲2.5× (small znear ⇒ far-field z-fighting risk). |
|
||||
| D85 | room primary/secondary DL → `texLoadFromGdl` garbage | OPEN (safety-netted; widen/pool fixes landed, geometry now renders) |
|
||||
| D86 · D87 | modelInitRwData truncated ptr · attract-demo BE `ramromfilestructure` | resolved |
|
||||
| D88.1–D88.3 · D88.5–D88.6 | `Usetup*Z` header + sub-table width/endian conversion (`d88_emit.py`) | resolved |
|
||||
|
||||
@@ -48,6 +48,7 @@ void gfx_set_texture_filter(enum FilteringMode mode);
|
||||
void gfx_set_mipmap_filter(enum MipmapFilteringMode mode);
|
||||
void gfx_set_fix_mip_textures(int on);
|
||||
void gfx_set_wrap_fix(int on);
|
||||
void gfx_set_anisotropy_level(int level); /* 1 = off; clamped to GL max */
|
||||
void gfx_texture_cache_clear(void);
|
||||
void gfx_texture_cache_delete(const uint8_t *orig_addr);
|
||||
void gfx_texture_cache_delete_range(const uint8_t *start, const uint8_t *end);
|
||||
|
||||
@@ -3227,6 +3227,20 @@ extern "C" void gfx_set_mipmap_filter(enum MipmapFilteringMode mode) {
|
||||
}
|
||||
|
||||
extern "C" void gfx_set_fix_mip_textures(int on) { g_fix_mip_textures = !!on; }
|
||||
|
||||
/* D212: expose the (already-implemented) rendering-API anisotropy hook to the
|
||||
* port layer. Clamp to [1, GL max] so a stale ini value can't feed an invalid
|
||||
* GL_TEXTURE_MAX_ANISOTROPY. 1 = isotropic (driver default). */
|
||||
extern "C" void gfx_set_anisotropy_level(int level) {
|
||||
reset_texture_state();
|
||||
int max = gfx_rapi->get_max_anisotropy_level ? gfx_rapi->get_max_anisotropy_level() : 1;
|
||||
if (max < 1) max = 1;
|
||||
if (level < 1) level = 1;
|
||||
if (level > max) level = max;
|
||||
if (gfx_rapi->set_anisotropy_level) {
|
||||
gfx_rapi->set_anisotropy_level(level);
|
||||
}
|
||||
}
|
||||
extern "C" void gfx_set_wrap_fix(int on) {
|
||||
const char* e = getenv("GE_WRAPFIX"); /* RC3 test override */
|
||||
g_wrap_fix = e ? (atoi(e) != 0) : !!on;
|
||||
|
||||
+97
-16
@@ -399,6 +399,8 @@ static unsigned scriptApply(unsigned button)
|
||||
return m;
|
||||
}
|
||||
|
||||
static void inputRebuildBinds(void); /* D214; defined below with keyDown() */
|
||||
|
||||
int inputInit(void)
|
||||
{
|
||||
if (!SDL_WasInit(SDL_INIT_GAMECONTROLLER)) {
|
||||
@@ -413,6 +415,8 @@ int inputInit(void)
|
||||
}
|
||||
inputOpenPads();
|
||||
|
||||
inputRebuildBinds(); /* D214: parse [Bind] now that configLoad() has run */
|
||||
|
||||
/* Relative mouse mode for mouse-look. In click-to-lock mode we start
|
||||
* released and wait for a click in the window (video.c -> inputNotifyClick). */
|
||||
mouseGrabbed = mouseEnabled && !mouseCaptureMode;
|
||||
@@ -486,7 +490,79 @@ static int scaleAxis(int v)
|
||||
|
||||
static int keyDown(const Uint8 *ks, SDL_Scancode sc)
|
||||
{
|
||||
return ks && ks[sc];
|
||||
return ks && sc != SDL_SCANCODE_UNKNOWN && ks[sc];
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* D214 — keyboard rebinding (PD parity: config-string driven, no UI).
|
||||
*
|
||||
* Each gameplay action maps to a comma-separated list of SDL scancode names
|
||||
* (as printed by SDL_GetScancodeName: "W", "Up", "Left Ctrl", "Space", ...).
|
||||
* The defaults reproduce the previously-hardcoded FPS layout exactly, so a
|
||||
* fresh or [Bind]-less ini changes nothing. Parsed once in inputInit(), after
|
||||
* configLoad(). Mouse buttons (fire = LMB, aim = RMB) stay hardwired.
|
||||
* ---------------------------------------------------------------------- */
|
||||
enum {
|
||||
IA_FORWARD, IA_BACK, IA_STRAFE_L, IA_STRAFE_R, IA_TURN_L, IA_TURN_R,
|
||||
IA_FIRE, IA_AIM, IA_ACTION, IA_CANCEL, IA_LEAN_L, IA_START, IA_COUNT
|
||||
};
|
||||
|
||||
static const struct { const char *key; const char *def; } kBindDefs[IA_COUNT] = {
|
||||
[IA_FORWARD] = { "Input.Bind.Forward", "W,Up" },
|
||||
[IA_BACK] = { "Input.Bind.Back", "S,Down" },
|
||||
[IA_STRAFE_L] = { "Input.Bind.StrafeLeft", "A" },
|
||||
[IA_STRAFE_R] = { "Input.Bind.StrafeRight", "D" },
|
||||
[IA_TURN_L] = { "Input.Bind.TurnLeft", "Left" },
|
||||
[IA_TURN_R] = { "Input.Bind.TurnRight", "Right" },
|
||||
[IA_FIRE] = { "Input.Bind.Fire", "Left Ctrl" },
|
||||
[IA_AIM] = { "Input.Bind.Aim", "Left Shift" },
|
||||
[IA_ACTION] = { "Input.Bind.Action", "Space,Z,E" },
|
||||
[IA_CANCEL] = { "Input.Bind.Cancel", "X,R,F,Escape" },
|
||||
[IA_LEAN_L] = { "Input.Bind.LeanLeft", "Q" },
|
||||
[IA_START] = { "Input.Bind.Start", "Return,Tab" },
|
||||
};
|
||||
|
||||
#define BIND_MAX_KEYS 4
|
||||
static char g_bindStr[IA_COUNT][64];
|
||||
static SDL_Scancode g_bind[IA_COUNT][BIND_MAX_KEYS];
|
||||
|
||||
static void inputRebuildBinds(void)
|
||||
{
|
||||
for (int a = 0; a < IA_COUNT; a++) {
|
||||
for (int k = 0; k < BIND_MAX_KEYS; k++) {
|
||||
g_bind[a][k] = SDL_SCANCODE_UNKNOWN;
|
||||
}
|
||||
char buf[64];
|
||||
strncpy(buf, g_bindStr[a][0] ? g_bindStr[a] : kBindDefs[a].def, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = 0;
|
||||
|
||||
int n = 0;
|
||||
for (char *tok = strtok(buf, ","); tok && n < BIND_MAX_KEYS; tok = strtok(NULL, ",")) {
|
||||
while (*tok == ' ' || *tok == '\t') tok++;
|
||||
char *end = tok + strlen(tok);
|
||||
while (end > tok && (end[-1] == ' ' || end[-1] == '\t')) *--end = 0;
|
||||
if (!*tok) continue;
|
||||
SDL_Scancode sc = SDL_GetScancodeFromName(tok);
|
||||
if (sc == SDL_SCANCODE_UNKNOWN) {
|
||||
sysLogPrintf(LOG_WARNING, "input: %s: unknown key name '%s'",
|
||||
kBindDefs[a].key, tok);
|
||||
continue;
|
||||
}
|
||||
g_bind[a][n++] = sc;
|
||||
}
|
||||
if (n == 0) {
|
||||
sysLogPrintf(LOG_WARNING, "input: %s has no valid keys; action unbound",
|
||||
kBindDefs[a].key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int actHeld(const Uint8 *ks, int act)
|
||||
{
|
||||
for (int k = 0; k < BIND_MAX_KEYS; k++) {
|
||||
if (keyDown(ks, g_bind[act][k])) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Fill button mask + stick for controller idx. Returns the 16-bit mask. */
|
||||
@@ -538,32 +614,29 @@ unsigned inputComputePad(int idx, signed char *stick_x, signed char *stick_y)
|
||||
/* GE default control (1.1): stick Y = move fwd/back, stick X = turn,
|
||||
* C-left/right = sidestep, C-up/down = look. FPS layout: W/S move,
|
||||
* A/D strafe (C-buttons), mouse X turns (stick X), mouse Y looks. */
|
||||
if (keyDown(ks, SDL_SCANCODE_W) || keyDown(ks, SDL_SCANCODE_UP)) sy = STICK_MAX;
|
||||
if (keyDown(ks, SDL_SCANCODE_S) || keyDown(ks, SDL_SCANCODE_DOWN)) sy = -STICK_MAX;
|
||||
if (keyDown(ks, SDL_SCANCODE_A)) button |= GE_CONT_C; /* strafe left */
|
||||
if (keyDown(ks, SDL_SCANCODE_D)) button |= GE_CONT_F; /* strafe right */
|
||||
if (keyDown(ks, SDL_SCANCODE_LEFT)) sx = -STICK_MAX; /* keyboard turn */
|
||||
if (keyDown(ks, SDL_SCANCODE_RIGHT)) sx = STICK_MAX;
|
||||
if (actHeld(ks, IA_FORWARD)) sy = STICK_MAX;
|
||||
if (actHeld(ks, IA_BACK)) sy = -STICK_MAX;
|
||||
if (actHeld(ks, IA_STRAFE_L)) button |= GE_CONT_C; /* strafe left */
|
||||
if (actHeld(ks, IA_STRAFE_R)) button |= GE_CONT_F; /* strafe right */
|
||||
if (actHeld(ks, IA_TURN_L)) sx = -STICK_MAX; /* keyboard turn */
|
||||
if (actHeld(ks, IA_TURN_R)) sx = STICK_MAX;
|
||||
|
||||
if ((mb & SDL_BUTTON(SDL_BUTTON_LEFT)) || keyDown(ks, SDL_SCANCODE_LCTRL))
|
||||
if ((mb & SDL_BUTTON(SDL_BUTTON_LEFT)) || actHeld(ks, IA_FIRE))
|
||||
button |= GE_CONT_G;
|
||||
int aimHeld = (mb & SDL_BUTTON(SDL_BUTTON_RIGHT)) ||
|
||||
keyDown(ks, SDL_SCANCODE_LSHIFT);
|
||||
int aimHeld = (mb & SDL_BUTTON(SDL_BUTTON_RIGHT)) || actHeld(ks, IA_AIM);
|
||||
if (aimHeld)
|
||||
button |= GE_CONT_R;
|
||||
if (keyDown(ks, SDL_SCANCODE_SPACE) || keyDown(ks, SDL_SCANCODE_Z) ||
|
||||
keyDown(ks, SDL_SCANCODE_E))
|
||||
if (actHeld(ks, IA_ACTION))
|
||||
button |= GE_CONT_A;
|
||||
if (wheelPulse > 0) { /* mouse-wheel weapon cycle -> A pulse */
|
||||
button |= GE_CONT_A;
|
||||
wheelPulse--;
|
||||
}
|
||||
if (keyDown(ks, SDL_SCANCODE_X) || keyDown(ks, SDL_SCANCODE_R) ||
|
||||
keyDown(ks, SDL_SCANCODE_F) || keyDown(ks, SDL_SCANCODE_ESCAPE)) /* D145: ESC = B (back/cancel) */
|
||||
if (actHeld(ks, IA_CANCEL)) /* D145: Escape is in the default Cancel bind */
|
||||
button |= GE_CONT_B;
|
||||
if (keyDown(ks, SDL_SCANCODE_Q))
|
||||
if (actHeld(ks, IA_LEAN_L))
|
||||
button |= GE_CONT_L;
|
||||
if (keyDown(ks, SDL_SCANCODE_RETURN) || keyDown(ks, SDL_SCANCODE_TAB))
|
||||
if (actHeld(ks, IA_START))
|
||||
button |= GE_CONT_START;
|
||||
|
||||
/* Mouse-look. Mode-dependent (see the tuning-constants comment):
|
||||
@@ -952,4 +1025,12 @@ PD_CONSTRUCTOR static void inputConfigInit(void)
|
||||
configRegisterInt("Input.PadDeadzone", &padDeadzone, 0, 30000);
|
||||
configRegisterInt("Input.PadTriggerPct", &padTriggerPct, 1, 99);
|
||||
configRegisterInt("Input.PadLookInvertY", &padLookInvertY, 0, 1);
|
||||
|
||||
/* D214: keyboard rebinding. Seed each buffer with its default so the knob
|
||||
* is visible/editable in a fresh ge007.ini; configLoad() overwrites any the
|
||||
* user set, then inputInit() calls inputRebuildBinds(). */
|
||||
for (int a = 0; a < IA_COUNT; a++) {
|
||||
strncpy(g_bindStr[a], kBindDefs[a].def, sizeof(g_bindStr[a]) - 1);
|
||||
configRegisterString(kBindDefs[a].key, g_bindStr[a], sizeof(g_bindStr[a]));
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,8 @@ static struct Row rows[] = {
|
||||
{ "Video.FpsCap", "Frame cap", ROW_SLIDER, 10, NULL, 0, 0, 360, 0,0,0,0,0 },
|
||||
{ "Video.MSAA", "MSAA", ROW_MSAA, 0, NULL, 1, 0, 0, 0,0,0,0,0 },
|
||||
{ "Video.TextureFilter", "Texture filter", ROW_ENUM, 1, kTexFilter, 0, 0, 0, 0,0,0,0,0 },
|
||||
{ "Video.Anisotropy", "Anisotropic", ROW_SLIDER, 1, NULL, 0, 0, 0, 0,0,0,0,0 },
|
||||
{ "Video.FovScale", "FOV scale %", ROW_SLIDER, 5, NULL, 0, 0, 0, 0,0,0,0,0 },
|
||||
{ "Input.MouseAimSpeed", "Mouse aim speed", ROW_SLIDER, 1, NULL, 0, 0, 100, 0,0,0,0,0 },
|
||||
{ "Input.MouseTurnSpeed", "Mouse turn speed", ROW_SLIDER, 1, NULL, 0, 0, 100, 0,0,0,0,0 },
|
||||
{ "Input.MouseInvertY", "Mouse invert Y", ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 },
|
||||
@@ -112,6 +114,40 @@ static int s_inited = 0;
|
||||
static volatile int s_open = 0;
|
||||
static int s_sel = 0;
|
||||
|
||||
/* D213: optional on-screen FPS readout (PD parity: Video.DisplayFPS). Drawn
|
||||
* top-right whenever enabled, independent of the F10 panel. Config-only knob
|
||||
* (kept off the 13-row panel, which is at its layout limit) -- matches PD,
|
||||
* whose DisplayFPS is also file-only. Default 0 => emit path unchanged =>
|
||||
* golden dumps byte-identical. */
|
||||
static int s_showFps = 0;
|
||||
static char s_fpsText[16] = "";
|
||||
|
||||
PD_CONSTRUCTOR static void overlayConfigInit(void)
|
||||
{
|
||||
configRegisterInt("Video.DisplayFPS", &s_showFps, 0, 1);
|
||||
}
|
||||
|
||||
/* Sampled once per emitted frame; recomputes the string every ~0.5 s. */
|
||||
static void fpsTick(void)
|
||||
{
|
||||
static uint64_t winStartUs = 0;
|
||||
static int frames = 0;
|
||||
|
||||
uint64_t nowUs = sysGetMicroseconds();
|
||||
if (winStartUs == 0) {
|
||||
winStartUs = nowUs;
|
||||
return;
|
||||
}
|
||||
frames++;
|
||||
uint64_t dtUs = nowUs - winStartUs;
|
||||
if (dtUs >= 500000) {
|
||||
int fps = (int)((double)frames * 1e6 / (double)dtUs + 0.5);
|
||||
snprintf(s_fpsText, sizeof(s_fpsText), "%d FPS", fps);
|
||||
winStartUs = nowUs;
|
||||
frames = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Layout (game 2D pixel space = viGetX() x viGetY(), ~320x240). Shared by the
|
||||
* emit path and the mouse hit-testing in optionsOverlayHandleInput().
|
||||
* BankGothic caps are ~9 units tall here, so rows need ~16 units of pitch and
|
||||
@@ -543,8 +579,26 @@ Gfx *optionsOverlayEmit(void)
|
||||
if (!s_inited) {
|
||||
overlayInit();
|
||||
}
|
||||
|
||||
fpsTick();
|
||||
|
||||
if (!s_open) {
|
||||
return NULL; /* nothing appended -> golden dumps byte-identical */
|
||||
if (!s_showFps || !s_fpsText[0]) {
|
||||
return NULL; /* nothing appended -> golden dumps byte-identical */
|
||||
}
|
||||
/* D213: FPS-only mini DL (top-right), panel closed. */
|
||||
const s32 fw = viGetX();
|
||||
const s32 fh = viGetY();
|
||||
Gfx *fgdl = s_buf;
|
||||
gDPPipeSync(fgdl++);
|
||||
gDPSetCycleType(fgdl++, G_CYC_1CYCLE);
|
||||
gDPSetTexturePersp(fgdl++, G_TP_NONE);
|
||||
gDPSetScissor(fgdl++, G_SC_NON_INTERLACE, 0, 0, fw, fh);
|
||||
fgdl = microcode_constructor(fgdl);
|
||||
fgdl = drawTextR(fgdl, fw - 6, 6, s_fpsText, 0x40ff60ff);
|
||||
gDPPipeSync(fgdl++);
|
||||
gSPEndDisplayList(fgdl++);
|
||||
return s_buf;
|
||||
}
|
||||
|
||||
const s32 W = viGetX();
|
||||
|
||||
+23
-2
@@ -58,6 +58,8 @@ static int cfgMSAA = 1; /* 1/2/4/8 samples; 1 = off
|
||||
static int cfgTexFilter = 1; /* 0 = nearest, 1 = bilinear (default), 2 = N64 3-point + trilinear */
|
||||
static int cfgFixMipTex = 1; /* RC2: clip mip-contaminated texture uploads to base height */
|
||||
static int cfgWrapFix = 0; /* D74 sub-tile UV pre-wrap + RC3/D167 non-PoT mask-period wrap (opt-in; GE_WRAPFIX env overrides) */
|
||||
static int cfgFovScale = 100; /* D211: percent of the original vertical FOV; 100 = unchanged (byte-identical) */
|
||||
static int cfgAniso = 4; /* D212: anisotropic filtering samples; 4 = the value fast3d already applied (no visual delta at default) */
|
||||
static int cfgFullscreen = 0; /* 0 = windowed, 1 = borderless fullscreen */
|
||||
|
||||
/*
|
||||
@@ -84,6 +86,13 @@ f32 portScreenShakeScale = 1.0f;
|
||||
* 0 (default) = the legal screen + logo attract sequence plays as normal. */
|
||||
s32 portSkipIntro = 0;
|
||||
|
||||
/* D211: Video.FovScale as a multiplier on the render FOV. Applied game-side
|
||||
* at the guPerspectiveF chokepoint (src/fr.c) so it lands BEFORE the CPU
|
||||
* pre-multiplies projection x view into the combined world matrix — the
|
||||
* fast3d-side matrix hack only caught the handful of pure-perspective loads
|
||||
* (pause/watch model, sky) and left the world untouched. 1.0f = original. */
|
||||
f32 portFovScale = 1.0f;
|
||||
|
||||
PD_CONSTRUCTOR static void videoConfigInit(void)
|
||||
{
|
||||
configRegisterFloat("Game.ScreenShakeIntensity", &portScreenShakeScale, 0.0f, 10.0f);
|
||||
@@ -94,6 +103,8 @@ PD_CONSTRUCTOR static void videoConfigInit(void)
|
||||
configRegisterInt("Video.TextureFilter", &cfgTexFilter, 0, 2);
|
||||
configRegisterInt("Video.FixMipTextures", &cfgFixMipTex, 0, 1);
|
||||
configRegisterInt("Video.WrapFix", &cfgWrapFix, 0, 1);
|
||||
configRegisterInt("Video.FovScale", &cfgFovScale, 50, 150);
|
||||
configRegisterInt("Video.Anisotropy", &cfgAniso, 1, 16);
|
||||
configRegisterInt("Video.Fullscreen", &cfgFullscreen, 0, 1);
|
||||
configRegisterInt("Window.Width", &cfgWinW, 0, 16384);
|
||||
configRegisterInt("Window.Height", &cfgWinH, 0, 16384);
|
||||
@@ -106,6 +117,14 @@ PD_CONSTRUCTOR static void videoConfigInit(void)
|
||||
* scheduler thread in videoStartFrame() where the GL context is bound. */
|
||||
static volatile int liveCfgDirty = 0;
|
||||
|
||||
/* D211/D212: push the port-only image knobs where they apply. FovScale is a
|
||||
* plain float the game re-reads each frame; anisotropy goes to fast3d. */
|
||||
static void videoApplyImageOptions(void)
|
||||
{
|
||||
portFovScale = (f32)cfgFovScale / 100.0f;
|
||||
gfx_set_anisotropy_level(cfgAniso);
|
||||
}
|
||||
|
||||
static void videoApplyTexFilter(void)
|
||||
{
|
||||
if (cfgTexFilter >= 2) {
|
||||
@@ -276,6 +295,7 @@ int videoInit(void)
|
||||
gfx_set_wrap_fix(cfgWrapFix);
|
||||
|
||||
videoApplyTexFilter();
|
||||
videoApplyImageOptions();
|
||||
|
||||
/* The GL context is currently current on this (host main) thread, but all
|
||||
* rendering happens on the game's scheduler thread. WGL only allows a
|
||||
@@ -316,9 +336,10 @@ void videoStartFrame(void)
|
||||
wmAPI->set_swap_interval(cfgVSync ? 1 : 0);
|
||||
gfx_set_target_fps(cfgFpsCap); /* 0 = uncapped */
|
||||
videoApplyTexFilter();
|
||||
videoApplyImageOptions();
|
||||
sysLogPrintf(LOG_INFO, "video: live config applied "
|
||||
"(vsync=%d fpscap=%d texfilter=%d)",
|
||||
cfgVSync, cfgFpsCap, cfgTexFilter);
|
||||
"(vsync=%d fpscap=%d texfilter=%d fov=%d aniso=%d)",
|
||||
cfgVSync, cfgFpsCap, cfgTexFilter, cfgFovScale, cfgAniso);
|
||||
}
|
||||
|
||||
gfx_start_frame();
|
||||
|
||||
@@ -715,7 +715,33 @@ Gfx *viSetupCurrentPlayerView(Gfx *gdl)
|
||||
|
||||
// Create both a floating-point matrix for the CPU side and a fixed-point matrix for the RSP.
|
||||
g_viProjectionMatrix = dynAllocateMatrix();
|
||||
#ifdef PORT
|
||||
/* D211: Video.FovScale — widen the render FOV here, at the one per-frame
|
||||
* chokepoint that feeds both the world (via the CPU-combined proj x view
|
||||
* matrix) and the sky/full-screen layer. fovy is in degrees, so an
|
||||
* angle-proportional scale (portFovScale, default 1.0f) matches the
|
||||
* documented 60deg->75deg at 1.25. Composes with the per-frame aim-zoom
|
||||
* rewrites of fovy for free. NOTE: currentPlayerSetCameraScale() keeps
|
||||
* the nominal FOV for frustum-cull / screen<->world math, so at large
|
||||
* scales edge geometry can cull a hair early — acceptable for a cosmetic
|
||||
* widen. Default 1.0f => identical matrix, bit for bit. */
|
||||
{
|
||||
extern f32 portFovScale;
|
||||
extern s32 lvlGetCurrentStageToLoad(void);
|
||||
f32 frFovY = g_ViBackData->fovy;
|
||||
/* Gameplay only: the front end (main menu / file / mission select /
|
||||
* briefing) also renders through this path with stage == LEVELID_TITLE
|
||||
* (lv.c lvlRender), and the menu 3D is authored at a fixed FOV. */
|
||||
if (portFovScale > 0.4f && portFovScale < 2.01f && portFovScale != 1.0f
|
||||
&& lvlGetCurrentStageToLoad() != LEVELID_TITLE) {
|
||||
frFovY *= portFovScale;
|
||||
if (frFovY > 160.0f) { frFovY = 160.0f; }
|
||||
}
|
||||
guPerspectiveF(g_viProjectionMatrixF, &g_viPerspNorm, frFovY, g_ViBackData->aspect, g_ViBackData->znear, g_ViBackData->zfar, 1.0f);
|
||||
}
|
||||
#else
|
||||
guPerspectiveF(g_viProjectionMatrixF, &g_viPerspNorm, g_ViBackData->fovy, g_ViBackData->aspect, g_ViBackData->znear, g_ViBackData->zfar, 1.0f);
|
||||
#endif
|
||||
guMtxF2L(g_viProjectionMatrixF, g_viProjectionMatrix);
|
||||
|
||||
/**
|
||||
|
||||
+85
-1
@@ -38,6 +38,8 @@
|
||||
#include "gbi_extension.h"
|
||||
|
||||
#ifdef PORT
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
/* D102: the 1P weapon Model and its RW-data pool were punned onto
|
||||
* hand->field_B68 / hand->modeldatas; on x86-64 struct Model (0xE8) is too
|
||||
* big for that layout and modelInit() aliases objinst->datas onto the pool
|
||||
@@ -607,6 +609,47 @@ void gunUpdateAndFire(GUNHAND handnum)
|
||||
hand->field_87F = 0;
|
||||
}
|
||||
|
||||
#ifdef PORT
|
||||
/* GE_DVM=1 — triage the missing 1P weapon viewmodel (D115 item #5 /
|
||||
* docs/dev/VIEWMODEL-RESEARCH.md). One BUNKER1 run with a pistol in hand
|
||||
* dumps the field_87F show/hide gate terms + the model-setup pointers,
|
||||
* rate-limited ~1/s per hand. Env-gated, zero behaviour change.
|
||||
* 87F==0 with gunHwoi==0 -> suspect #1 (Gun_hand_without_item gate;
|
||||
* inv/hitem/f2A44 show which term diverged)
|
||||
* 87F!=0 but numRec > 192 -> suspect #2 (RW pool overrun)
|
||||
* 87F!=0 but RootNode NULL/wild -> suspect #3 (stale copy_of_body_obj_header)
|
||||
* 87F!=0, RootNode sane -> defect is downstream in subdraw/drawjointlist */
|
||||
{
|
||||
static int dvm = -1;
|
||||
static u32 dvm_last[2] = {0, 0};
|
||||
if (dvm < 0) dvm = getenv("GE_DVM") != NULL;
|
||||
if (dvm && handnum < 2) {
|
||||
u32 now = (u32)(osGetTime() / 1000000);
|
||||
if (now != dvm_last[handnum]) {
|
||||
struct player *pl = g_CurrentPlayer;
|
||||
ModelFileHeader *dh = &pl->copy_of_body_obj_header[handnum];
|
||||
dvm_last[handnum] = now;
|
||||
fprintf(stderr,
|
||||
"[DVM] hand=%d item=%d 87F=%d | hdrLine=%d showFP=%d hideHand=%d "
|
||||
"actState=%d ittype=%d ammo=%d | gunHwoi=%d inv=%d hitem=%d f2A44=%d\n",
|
||||
handnum, item, (s32)hand->field_87F,
|
||||
get_ptr_weapon_model_header_line(item) != 0,
|
||||
bondwalkItemCheckBitflags(item, WEAPONSTATBITFLAG_SHOW_FIRST_PERSON),
|
||||
bondwalkItemCheckBitflags(item, WEAPONSTATBITFLAG_HIDE_FIRST_PERSON_HAND),
|
||||
(s32)hand->weapon_action_state, get_itemtype_in_hand(handnum),
|
||||
(s32)hand->weapon_ammo_in_magazine,
|
||||
Gun_hand_without_item(handnum),
|
||||
pl->hand_invisible[handnum], (s32)pl->hand_item[handnum],
|
||||
(s32)pl->field_2A44[handnum]);
|
||||
fprintf(stderr,
|
||||
"[DVM] hdr=%p RootNode=%p Skeleton=%p Switches=%p numMtx=%d numRec=%d (pool=192 words)\n",
|
||||
(void *)dh, (void *)dh->RootNode, (void *)dh->Skeleton,
|
||||
(void *)dh->Switches, (s32)dh->numMatrices, (s32)dh->numRecords);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (hand->field_87F != 0)
|
||||
{
|
||||
mdlhdr = &g_CurrentPlayer->copy_of_body_obj_header[handnum];
|
||||
@@ -1528,7 +1571,26 @@ void gunRenderFirstPersonGunModels(Gfx **gdlptr)
|
||||
s32 handnum;
|
||||
Model *model;
|
||||
|
||||
#ifdef PORT
|
||||
/* D215: the N64 read `renderdata = *(ModelRenderData *)&D_80035CC0;` is a
|
||||
* reinterpret across three separately-declared adjacent .data globals —
|
||||
* `u32 D_80035CC0 = 0;` immediately followed by
|
||||
* `u32 D_80035CC4[] = {1, 3, 0, 0, ...};` (see gun.c) — so on N64 it yields
|
||||
* exactly { basemtx=0, zbufferenabled=1, flags=3, everything-else=0 }.
|
||||
* On x86-64 it breaks twice over: `ModelRenderData`'s pointer members
|
||||
* (`basemtx`, `gdl`, `mtxlist`) widen 4->8 B so `flags` is no longer at
|
||||
* struct offset 8, and the C standard does not guarantee those two globals
|
||||
* are laid out adjacently or in declaration order. Measured result:
|
||||
* `renderdata.flags == 0`, which gates every 1P weapon-model DL node off in
|
||||
* `modelRenderNodeGundl` (`flags & 1`) — the whole "no gun visible while
|
||||
* playing" bug. Reproduce the N64 field values directly. ABI/layout only
|
||||
* (D3x class), no logic change. */
|
||||
renderdata = (ModelRenderData){0};
|
||||
renderdata.zbufferenabled = TRUE;
|
||||
renderdata.flags = 3;
|
||||
#else
|
||||
renderdata = *(ModelRenderData *)&D_80035CC0;
|
||||
#endif
|
||||
|
||||
for (handnum = 0; handnum != 2; handnum++)
|
||||
{
|
||||
@@ -1612,9 +1674,31 @@ void gunRenderFirstPersonGunModels(Gfx **gdlptr)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef PORT
|
||||
/* GE_DVM=1: D215 verification probe — how many gfx commands the 1P
|
||||
* weapon model's subdraw() actually emits. Pre-fix this was 1 (just the
|
||||
* gSPSegment); a healthy weapon DL is ~150. Rate-limited ~1/s/hand. */
|
||||
Gfx *dvm_before = renderdata.gdl;
|
||||
#endif
|
||||
subdraw(&renderdata, HAND_WEAPON_MODEL(handptr));
|
||||
gdl = renderdata.gdl;
|
||||
|
||||
#ifdef PORT
|
||||
{
|
||||
static int dvmr = -1;
|
||||
static u32 dvmr_last[2] = {0, 0};
|
||||
if (dvmr < 0) dvmr = getenv("GE_DVM") != NULL;
|
||||
if (dvmr && handnum < 2) {
|
||||
u32 now = (u32)(osGetTime() / 1000000);
|
||||
if (now != dvmr_last[handnum]) {
|
||||
dvmr_last[handnum] = now;
|
||||
fprintf(stderr, "[DVMr] hand=%d item=%d 87F=%d cmds_emitted=%ld cull=%d\n",
|
||||
handnum, item, (s32)handptr->field_87F,
|
||||
(long)(renderdata.gdl - dvm_before), renderdata.cullmode);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (bondwalkItemCheckBitflags(item, WEAPONSTATBITFLAG_MIRROR_DUAL) != 0)
|
||||
{
|
||||
gSPClearGeometryMode(gdl++, G_CULL_BOTH);
|
||||
|
||||
Reference in new issue
Block a user