Files
goldeneye-pc-port/docs/BACKLOG.md
T
James Dansereau 57e24a374b M-28: watch-menu text (D150/D151) + audio-lock deadlock mitigation (D152)
D150 (src/str.c) — NULL-tolerant strcpy/strncpy/strcat under #ifdef PORT.
The watch BRIEF/OBJECTIVES pages build text with strcat(buf, langGet(id));
langGet() returns NULL on PC for a string bank the in-level flow never
loaded, and str.c's primitives are __nonnull__ builtins so a plain param
NULL check is optimised away — the guard launders the ptr through an empty
asm (GE_IS_NULL). NULL src -> empty string instead of an AV crash.

D151 (src/bondtypes.h) — watch data (objectives / mission background / M /
Q / Moneypenny) all rendered blank. propDef types 35 (WatchMenuObjectiveText)
and 23 (ObjectiveStart) store a plain s32 lang slot id in their 3rd word;
the decomp structs decode it as [u16 reserved][u16 text] reading text at
offset 0xA, which only works because the id is in the low 16 bits of a
big-endian word. d88_propdefs.py bswap32's the whole word -> text @0xA
reads 0 -> langGet(0) -> NULL. Fix: text is a full u32 at the word offset
under #ifdef PORT (N64 layout kept under #else). No converter change, no
sidecar regen. This is the D143/D150 "briefing text renders blank" root cause.

D152 (port/src/libultra.c) — mission-failed / objective-failed permanent
black screen. The D147 recursive-mutex behind osSetIntMask can be leaked
(libaudio has unbalanced / early-return mask calls; a transient thread can
acquire OS_IM_NONE and exit) -> mainThread + amMain both block forever in
alEvtq* -> gfx pipeline stalls. Live gdb.txt from the user's session
confirmed the deadlock (mainThread in the mission-fail audio fade-out
sndSetScalerApplyVolumeAllSfxSlot -> alEvtqPostEvent). Mitigation: model
the mask as a self-healing logical lock (owner/depth under s_imMx + condvar)
— normal microsecond critical sections behave as before, but a waiter
blocked > 2s concludes the holder leaked it, logs the stale owner + the
stealing caller's return address, and steals the section. Worst case is a
~2s audio hiccup + a log line naming the leak, instead of a hung game.
Proper narrow fix (dedicated ALEventQueue lock) still owed.

D153 investigated and dismissed — the "-level_09 crashes at frame ~900"
seen while regression-testing was D117/D134 host-scheduling flakiness under
heavy concurrent build+run load; on a quiet machine -level_09 runs past
frame 2400 clean with all three changes applied.

Build: green (ntsc-final). Golden framediff not run clean (same machine-load
flakiness); one quiet -level_09 run = 2400+ frames, 0 heartbeats.
2026-08-30 20:25:02 -04:00

19 KiB
Raw Blame History

All of these items should be completed once the main original game is playable from start to finish on PC with no crashing or major issues.


Playtest defects — logged M-28 (2026-08-30), not yet worked

User-reported during free playtest. Suggested priority order below (cheap input fixes first, then the texture correctness bug, then the filtering feature).

  • B1 — texture filtering / "interlaced" shimmer (feature). Whole game has an interlaced/shimmering look. Want N64 3-point-filter emulation + bilinear/trilinear handling like the Nightdive / Quake-remaster ports. MVP: one good default filtering mode in port/fast3d/ (3-point emu shader or clean bilinear). Long-term: full toggle set (see "PC graphics settings" → Quality + stretch goals below). Biggest of the four.
  • B2 — glitchy textures / wrong colors (correctness bug). Some textures render with wrong colors / garbage — very noticeable on Depot (-level_30), present in many other spots too. Likely a texture-decode/format bug (CI/TLUT, RZ decode port/src/rzdecomp.c, or the import_texture_* path in gfx_pc.cpp). Distinct from B1. Needs investigation — could be a real fidelity blocker, not cosmetic-parked.
  • B3 — RMB aim far too mouse-sensitive. Manual-aim (RMB/LShift) mouse sensitivity is way too high. Input.MouseAimSpeed (default 50) still overshoots badly. Quick fix: lower default + recheck the aim-band curve in port/src/input.c (the 61..80 stick push added M-24). Port-only.
  • B4 — menu crosshair-cursor screens: PC input is wrong. Menu screens that use the crosshair cursor don't behave like a PC pointer. The original front-end was PC-style "select a file with the mouse" — menu input should be a separate mode from in-game input, not the same analog-stick emulation. Needs a dedicated menu-pointer path in port/src/input.c (absolute cursor → menu hit-test) rather than feeding synthetic stick deltas. Port-only.
  • B5 — no game save on level completion. On the OG cartridge, finishing a level writes progress to EEPROM (unlocks next mission, records time/difficulty). On PC nothing is saved — progress is lost on quit. This is Phase 4 (file-backed EEPROM): osEepromWrite/Read are currently no-op stubs; build on PD's fs.c (file-backed save dir). The save logic already runs in the decomp — it just needs real backing storage. Blocks any multi-session campaign playthrough.

PC graphics settings

Optional video/rendering options, modelled on the PD port (baseline) with Quake 1 remaster / Nightdive-style extras as stretch goals. Every option defaults to the current behavior — zero visual delta at defaults.

Starting point (verified):

  • port/src/config.c exists but is minimal (58 lines: Int + String only; PD's is 300 lines with Float/UInt, clamping, INI sections). Copy-and-adapt from pd_port/port/src/config.c.
  • port/src/video.c is a trimmed port of PD's (250 vs 588 lines). Render path is architecturally identical to PD: native-res FBO (GE = 640×480 NTSC LAN1) → scaled blit to window, so all PD display options port over cleanly.
  • No #ifdef PC blocks exist in src/ yet; only the documented ABI/layout exception edits (D3x) are present. Anything below that changes behavior (FOV, HUD placement, menu contents) is a policy decision — see route notes.

Baseline (PD parity) — each item cites its PD source:

  1. Config system: full configRegister{Int,UInt,Float,String} + clamps (pd_port/port/src/config.c).
  2. Window/display (video.c + SDL): fullscreen checkbox, fullscreen mode (borderless/exclusive), maximize, center window, resolution dropdown (display-mode list + "Custom"), HiDpi toggle.
  3. Frame pacing: vsync (Adaptive/Off/On/N-frames via SDL_GL_SetSwapInterval), framerate cap slider (PD caps FPS when vsync is off — keep that guard).
  4. Quality: MSAA Off/2x/4x/8x/16x (FBO samples — verify our FBO path accepts samples>0 first; PD reads it back via videoGetMSAA), texture filter Nearest/Bilinear, anisotropic 1–16 slider, mipmap filter, on-screen FPS display toggle.
  5. FOV slider (baseline per owner decision). PD does this game-side (Game.PlayerN.FovY, g_PlayerExtCfg in their main.c + decomp edits). Two routes — decide in a spike:
    • (a) Port-layer: override the camera projection in our fast3d where the view matrix/projection is consumed. No src changes; must find the exact injection point (camera math flows through game GBI setup).
    • (b) PD-style port-only src/ edit reading a port-provided value. This would be the first behavioral src edit — exceeds the current ABI/layout exception in AGENTS.md #2; if chosen, expand the documented exception ("PC-port feature hooks, opt-in via config, default = original behavior") and record a Dxx finding.
  6. Widescreen handling: non-4:3 window aspect → stretch vs pillarbox choice, plus HUD centering (None/4:3/Wide à la PD Game.CenterHUD). PD implements the HUD part with G_ASPECT_*_EXT extended GBI mods set game-side; for us that's either a fast3d viewport intercept of the HUD quads (route a) or a port-only src edit (route b). Same policy note as FOV.

In-game video options menu: v1 = config file (+ CLI flags if trivial). PD's in-game menu (pd_port/port/src/optionsmenu.c, 2042 lines) hooks into their decomp's menu tables with port-side handlers; GE's menu code differs, so it's an adaptation, not a copy. Feasible under route (b) policy; revisit after the config-file v1 works.

Stretch goals (Quake 1 remaster / Nightdive):

  • Internal resolution scale: render at 2x/4x native then downscale (free supersampling AA, big fidelity win on modern displays).
  • FXAA post-process toggle (cheap AA alternative to MSAA).
  • SSAO + bloom / light-shaft post-processes — Quake Enhanced ships each enhancement as an individually-toggleable option; keep that pattern (one toggle each, all default off, shared post-FBO stage with FXAA/colorblind).
  • Integer-scaling option.
  • Texture upscaling filter (ESRGAN-style or pre-generated upscales) — the biggest item; do last. Note GE textures are RZ-compressed at load (port/src/rzdecomp.c) — an upscale pass would hook there or post-decode.

Acceptance: every option persists via config file; toggles apply live where PD's do; tools_pc/golden/ dumps byte-identical at all-defaults; no src/ changes unless route (b) is explicitly chosen and documented.

LAN deathmatch / multiplayer — the netplay foundation (do first)

The idea: play GE's existing competitive multiplayer (deathmatch / mission-based, 2–4P) across up to 4 instances of the PC port on a local network. This is the nettech proving ground: the game logic already exists in the decomp (original N64 feature — no new mode to invent), so this item isolates and de-risks the entire networking layer. Co-op (below) bolts onto whatever this produces.

Milestone 0 (prerequisite, real port work): verify GE's existing split-screen multiplayer works locally on PC — two cameras, split viewport, two independent input streams, full match lifecycle. Every code path netplay will drive must first work on one box.

Net model (research item — owner leans client-server):

  • Leading candidate: host = server + player (Source-engine style): host runs the authoritative sim, clients send inputs and receive state. Friends connect by manual IP entry on a LAN. Self-healing desync, one machine owns truth.
  • Cost to size up: a full per-tick state snapshot format (players, entities/props, AI, doors/props) must be defined from scratch — no existing save/replay infra covers frame state (EEPROM saves are mission-level only).
  • Alternative for the research phase: lockstep/deterministic (exchange ~10 bytes of input per 60Hz tick, both machines run the full sim). Far less code if the sim is bit-deterministic across hosts — PRNG is ported verbatim (port/src/random.c, good), but host-scheduling nondeterminism is a known risk (see D24-implications note below). Desync = hard stop. A per-tick state-hash desync detector is cheap insurance either way.
  • PD port has zero networking code — nothing to copy; the net layer is new (port/src/net.c-style, SDL_net or plain sockets).

Shape of the work under host-authoritative: generalize to host + N clients (N≤3), per-client input channels, state snapshots fanned out to all clients. The host already runs the full sim, so scaling past 2P is mostly protocol fan-out — which is why 2P is a natural intermediate milestone on the way to the 4-instance bar.

v1 bar: LAN only, manual IP entry (host listens / client connects), deathmatch playable; 2P first, then scale to 4 instances. No matchmaking, discovery, reconnect, or internet play. Stretch: ping display, graceful reconnect-on-drop, mission-based (non-deathmatch) multiplayer variants, remote-player model interpolation between snapshots (Quake Enhanced ships it; hides LAN jitter).

Co-op (new mode — built on the deathmatch net layer)

The idea: two players progress the single-player campaign levels together — locally on one PC, and over LAN with each player on their own PC. Sequenced after LAN deathmatch: reuse its connection handling, snapshot format, and input channels; this item adds the mode itself.

Key fact (defines the scope): GE has no co-op mode. Unlike PD, GoldenEye only ships split-screen competitive multiplayer. Co-op on solo levels exists in PD only — so this is a net-new game mode: mapping/level and GE game- code feature work, on top of the (already-existing) networking plumbing. Consequences:

  • Non-negotiable #2 ("game logic unmodified") is about not altering existing behavior; adding a new mode is a separate, explicit owner decision. Record the policy call when work starts.
  • pd_port/ is the reference implementation: PD's decomp shows how two-player co-op was wired into this same Indy engine (shared objective progression, both players' AI/enemy interaction, win/lose conditions, split-screen presentation). Expect an adaptation study, not a copy — GE's mission/ objective code differs file-by-file.
  • Cheaper fallback worth considering during design: "freeplay" co-op (both players active on a solo level, but only P1's objective progress counts / or objectives are duplicated per player). Much less new logic; decide after the PD study.

v1 bar: LAN + local 2P co-op on solo levels over the deathmatch net layer. No matchmaking, discovery, reconnect, or internet play.

Fun features (remaster research: Quake 1 Remaster / Nightdive)

Feature mining from the Quake 1 Remaster (id, 2016), Nightdive's remaster line (Duke Nukem 3D: Forever, DOOM, System Shock), and — round 2, 2026-08-29 — Quake Enhanced, Quake II Enhanced, Turok Resurrection, Doom 64 Definitive Edition, SiN Reloaded. Pattern across all of them: classic fidelity as default + a shelf of opt-in QoL/accessibility/asset options. Tiered by effort; all stay in port/ unless noted.

Cheap wins (config toggles over existing infra):

  • Screenshot hotkey. Productize the existing debug dump: gfx_opengl_dump_bound_fbo() (port/src/video.c:199) is already there — wire a keybind + configurable output dir, timestamped filenames. Every Nightdive title has this; ours is ~an afternoon.
  • Screen-shake intensity slider (0–10x). All shake funnels through viShake() (src/game/bondview2.c:8300 et al.) which lands in our port shim — scale amplitude there, zero src changes. PD precedent: Game.ScreenShakeIntensity.
  • No hit-flash / damage-indicator toggle. The red full-screen flash on taking damage is a top community-mod option; Nightdive ships it. Impl: find how the flash overlay is drawn (likely a full-screen HUD quad) and intercept in the port layer, or route-(b) one-liner.
  • Auto-pause / input-hold on window focus loss. SDL focus events; trivial, prevents drive-by deaths when alt-tabbing.
  • Controller polish (copy from PD input.c): per-player gamepad assignment (Input.ControllerIndex), per-stick deadzone/sensitivity sliders, rumble scale + rumble mapping to GE's feedback moments. Note osMotor* (Rumble Pak) is currently stubbed — accessory detection only (PCPortResearch §4) — so wiring it to SDL_GameControllerRumble is part of this item; Doom 64 DE shipped haptic feedback (a KEX first). We have basic pad support (D118); this is the remaster-grade layer on top.
  • Tick-rate / framerate options. PD ships Game.TickRateDivisor + framerate cap. Flag: any tick-rate change must be reconciled with the future netplay determinism requirement before shipping as a user option (lock it to 60Hz default, document).
  • Fog / draw-distance option. Turok's KEX port removed the N64's heavy distance fog; Quake II Enhanced added height-based fog with custom colors. fast3d already decodes fog generically (PCPortResearch §5) — a config slider scaling/overriding fog density, default = original values. Zero src changes.

Medium:

  • High-res / user texture packs (the big goodwill item). Quake 1 Remaster's core feature is swappable asset sets (classic vs remastered toggle). GE textures are RZ-compressed and decoded at load (port/src/rzdecomp.c) — a per-texture override lookup against a user directory (fall back to ROM) hooks there. Enables the community to make packs; also our escape hatch for the blurry-asset complaints without touching fidelity defaults.
  • Accessibility: colorblind post-process filters (protanopia/ deuteranopia/tritanopia full-screen shader pass) + the screen-shake slider above. Nightdive includes these; cheap once we have a post-process stage (shared infra with FXAA from the graphics item — sequence together). Quake II Enhanced also shipped high-contrast / readability UI options — add where the port layer can reach (bitmap HUD text limits scope; document what's not doable under route b).
  • Crosshair customization (size/opacity at minimum; GE's crosshair is game-drawn — check whether it's a textured quad we can scale in the port layer before assuming route b).
  • Widescreen FOV compensation — already baseline in the graphics item; listed here because it's the feature every remaster ships and users will expect.

Big / stretch:

  • Demo / replay record + playback. Record the input stream (+ initial state), play back headlessly. Quake tradition; Nightdive titles have replay-ish tooling. Strong synergy with netplay work: a working replay system is a determinism test harness — if a recorded demo replays bit-identically, lockstep netplay's hardest assumption is proven. Consider building it as the netplay pre-flight tool even before it's user-facing.
  • Photo mode. Pause + free orbit camera + capture (System Shock precedent). Free camera = port-layer camera override; pairs with the screenshot hotkey.
  • In-game console. Type commands at runtime. Less "remaster feature" than dev tool, but for a WIP port it's arguably the highest-value item on this list: toggle debug flags, dump state, force conditions without restarting. PD has no equivalent; we'd own the design.
  • Benchmark mode. Scripted auto-run sequence + FPS report (Quake tradition); trivial once tick-rate options exist.
  • AI opponents in deathmatch. Quake Enhanced ships bots in DM. GE's N64 MP is human-only, so bots = net-new game logic — same policy class as co-op (record the owner decision when work starts). Stretch: lets a solo player exercise the netplay layer and makes MP approachable with 1–2 humans.
  • Animation smoothing ("wiggle elimination"). Quake II Enhanced restored high-res skeletal animations specifically to kill low-fps "wiggle". GE's model keyframes are N64-rate; interpolating (slerp) between keyframes in the port-layer model-transform path would visibly smooth guard/Bond motion on modern displays. Stretch — sequence with the graphics backlog, do late.
  • Dynamic shadows. Quake Enhanced's headline graphical feature. For fast3d this means a shadow-map pass + per-light casting — deep stretch; park next to texture upscaling (both "do last" items).
  • Remastered audio option (Phase 3 note). System Shock's remake added new sounds/music. Keep N64 ADPCM fidelity as the default; optionally offer a lossless re-encoded soundtrack toggle — owner decision + content work, park until Phase 3 lands.
  • Optional modern UI skin. SiN Reloaded shipped upgraded 2D screens / menu art; System Shock an overhauled interface. For GE this is route (b) game-side territory (menu tables in front.c / initmenus.c) — park as an explicit owner decision; lowest priority on this list.
  • Stereo 3D — both reference families shipped it; explicitly deprioritized as anachronistic. Park unless someone asks.

Out of scope by the v1 bars above: online matchmaking, leaderboards, anti-cheat, cross-play (Quake 1 Remaster's biggest features; revisit only after LAN netplay is solid).

Also out of scope by policy (community port): online-platform integration — Steam/GOG/Epic achievements, cloud saves, storefront add-on downloads. No platform tie-in of any kind.

--Old is above here


Technical cleanup (backlogged; not gated on playability)

  • Remove TEMP D63 debug scaffolding. ~16 references in port/src/ (libultra.c): the master-DL scanner and env-gated GE_D63 logging inside osSpTaskStartGo(). D63 is closed; this is dead weight in the hottest function in the port.
  • Add a D24-implications bug class to docs/PORT-LEARNINGS.md. "Host-scheduling nondeterminism / fake priority semantics": the pthread kernel does not enforce N64's 0–31 priorities (osYieldThread = Sleep(0)), so interleavings impossible on console can occur, and frame timing has host jitter. Consequence for future sessions: when a flaky timing bug appears (especially Phase 3 audio underruns), reach first for host thread priorities (SetThreadPriority: scheduler thread time-critical, tick normal) and the deferred GE_DETERM mode (D117) — not for kernel changes.
  • Watch item: osYieldThread = Sleep(0). The one place we fake cooperative behavior. If a level-sweep hot loop misbehaves under host load, this shim is the first thing to inspect. Keep as-is until then.

Architecture decision (recorded; no action)

  • Keep the pthread + real src/sched.c model. Compared against the PD port's actual design (verified in pd_port/: zero threads — thread API is pure no-op, mainProc() runs as the host main thread, pdsched.c is a gutted frame-callback wrapper, audio driven inline via amgrFrame(), M_AUDTASK dropped). PD could do that only because their original N64 scheduler already used direct-call submission ("nothing writes to the cmdQ in PD") and they adapted game-side lib code (audiomgr.c). For GE — classic osSendMesg(cmdQ) protocol, real __scMain loop, amMain blocking on retrace/reply messages — that route means editing src/audi.c/submission points (non-negotiable #2 violation) and regressing Phase 3, which currently runs the full authentic audio scheduling protocol. No rewrite. Related: audio stays inline in the M_AUDTASK branch (docs/AUDIO-PLAN.md, Option A + sub-decision A3); no dedicated audio worker thread unless host thread priorities provably fail.