Commit Graph
54 Commits
Author SHA1 Message Date
James Dansereau 40ef53e395 v0.4.0 M3: GEPD key-layout preset + crouch/reload bindings (D371)
Input.Layout (0=FPS default, 1=GEPD) + Input.CrouchMode (hold/toggle) rows
in F10 INPUT (Turok-standard functional section; the planned MODERN bucket
is dropped per D356). The GEPD preset acts as the per-key effective
default under [Input.Bind] overrides; new IA_RELOAD/IA_CROUCH actions emit
GE_CONT_B (unbound by default, N64-original). Toggle crouch latches B with
a menu-boundary latch drop. optionsoverlay rowSetCommit hook re-derives
the binds via inputLayoutApply(). inputRebuildBinds no longer warns on
intentionally-empty defaults. Boot + ini round-trip + F10 page verified;
in-game feel-check owed (D371).
2026-09-27 16:29:18 -04:00
James Dansereau c2c6fbdd68 checkpoint: F10 GE-style refresh + distance defaults retune, D360-D370 series (local WIP; D370 pending user acceptance) — pre-wave baseline for the v0.4.0 modern-options wave 2026-09-27 16:18:20 -04:00
James Dansereau fe57d73924 optionsoverlay/watchsettings/frontoptions: D356 settings regroup — functional sections, save-file scoping, per-section reset-to-defaults (Gate E)
- 5 sections (INPUT/GAMEPLAY/GRAPHICS/AUDIO/VIDEO, all below MAX_PROWS):
  DISPLAY+GRAPHICS merged into GRAPHICS; AUDIO carries the watch music/FX
  sliders out of the Bond-file page; VIDEO keeps the pure video rows;
  __HdrMouse/__HdrPad merged into INPUT; __HdrBond/__BondFile retired for
  the front-only top save-file row (frontoptions.c)
- save-file scoping: per-file sections (GAMEPLAY, AUDIO) annotated
  '(File N)'/'(no file)' via the new Row.saveScoped flag +
  watchSettingsActiveFolder(); per-file rows in mixed sections carry a dim
  '(save)' tag; the D353 optionsRowIsBondChooser special-case is
  generalized (both UIs)
- N64-watch rows Bond.Look / Bond.AimControl commented out of the menu
  surface (D181/D216 style: keys + watch paths stay live)
- per-section 'Reset to defaults' replaces the Turok rows (plan Gate E):
  edge-triggered two-step arm->confirm within 3 s, one commit, disarm on
  timeout / navigate-away / close (optionsResetMaintain/Clear);
  rowResetSection walks the section's declared range: watch rows ->
  BLANKSAVEDATA through the D352 commit path (front direct, stage queue),
  ini rows -> kResetDefaults[] (the port's C initializers); __Resolution
  is a documented exclusion (action-backed)
- GE_WSPROBE_RESET probe: front tick-120 prepare/dispatch/verify, stage
  tick-120 dispatch / tick-121 verify; a direct -level_XX boot starts its
  timer from phase 0 (front prepare is front-only)
- docs: findings.md D356 + index + CSV, WATCH-SETTINGS-PLAN status,
  OPTIONS-MENU-PLAN pointer, plan doc status -> LANDED

Verified headless: GE_WSPROBE_RESET front (all 5 sections failures=0,
__Resolution untouched, file-2 scope isolation OK) and -level_33 (6
commands queued, all sections verified post-drain, saved music/sfx = 0xFF);
GE_WSPROBE / GE_WSPROBE_FRONT regressions clean; 248/248 build + link.
2026-09-27 14:49:08 -04:00
James Dansereau 462675cf47 optionsoverlay.c: Turok-style regroup - DISPLAY/GRAPHICS split, BOND FILE absorbs music/FX, designated rows, overflow log, file-scope title annotation (D353) 2026-09-27 13:32:39 -04:00
James Dansereau 8e9bee6ef0 watchsettings.c: D350-bridge robustness - hold-not-drop at stage transitions, chooser spinlock, queue coalescing, warning logs (D352) 2026-09-27 13:32:18 -04:00
James Dansereau 1303c0ebe1 D350: expose watch-backed Bond settings in PC options and F10
Add a game-thread bridge and field-scoped per-file persistence for PC UI edits, with an explicit file chooser; keep watch-only edits session-only. Fix D351 row initializer offsets for units and auto-hide. Document verification and review follow-ups.
2026-09-27 13:20:21 -04:00
James Dansereau 7ff9ddbe14 D345/D346: options-screen pointer + keyboard nav fixes; settings wording pass
D345 (a-g): re-assert window on menu change; cursorToItem skips snap while
the 1:1 pointer is live; W/S emit stick in menus under NaturalPitch;
F10-style discrete row stepping + arrow left/right value adjust replace raw
drift (first pass shipped broken, fixed); screen-placed cursor parks in the
left gutter off the row text.

D346 (+b): labels/values aligned to Nightdive/Turok + PD-port conventions
(title-case On/Off, Frame rate cap, Anti-aliasing/None, Anisotropic
filtering, units in values not labels, deadzone as %, self-explanatory
auto-FOV toggles); VIEW section merged into VIDEO (MAX_PROWS 12->15,
ROW_DY 20->18). Display-only: config keys and stored values untouched.

Port-only; no src/game edits. User playtest verified.
2026-09-27 12:12:32 -04:00
James Dansereau 13473d5ec6 D343: PC options as a GE front-end screen (MENU_PC_OPTIONS)
Replaces the M2 panel. Built like the Cheat Options screen: open dossier
(tabs + blank CLASSIFIED page), black Zurich ink via frontPrintText, GE's
highlight box, PREVIOUS tab as back, the game's crosshair. Page 0 is a
numbered section list; each section lists the F10 overlay's rows with a
value column (ON in the cheat red) and slider bars.

Game code (Rule 2, user sign-off 2026-09-27): MENU_PC_OPTIONS enum value
and five one-line dispatch cases, all #ifdef PORT. The file-select label
now enters the screen via frontChangeMenu. The M2 button hand-off in
input.c is gone; F10/Select don't open the overlay on this screen.
2026-09-27 10:58:27 -04:00
James Dansereau fd82dda717 D343 M2: GE-styled options panel on file select; D344 docs
The file-select Options label opens a panel drawn in the same hook:
sections as tabs, rows from the F10 overlay's table via an exported row
API, the game's crosshair as pointer (input.c passes the stick, hands the
buttons to the panel). Quit to desktop now goes through videoRequestQuit.
Also repairs two escaped-newline typos in the D343 docs.
2026-09-27 10:38:16 -04:00
James Dansereau 461ea871c2 D344: orderly quit so exit never lands inside the GL driver; GE_QUITFRAME
Quit (quit event, window close, Alt+F4, from either SDL pump) used to
exit(0) immediately while the render thread could be mid-frame in the
driver: the likely trigger of the host's 0x119 bugchecks. Now quits are
requested; the render thread glFinish()es, unbinds the context and parks
at a frame boundary, and the host exits after (max 2 s). GE_QUITFRAME=<n>
gives the harness a clean self-quit; golden_gate.sh uses it.
2026-09-27 10:38:16 -04:00
James Dansereau 413014b8f6 D343: file-select Options label opens the settings overlay; F10 idle-timer fix
One Rule-2 #ifdef PORT draw hook in constructor_menu05_fileselect (user
sign-off 2026-09-27). The label's hit test and open request live in
optionsoverlay.c; the open is applied on the input thread. While the
overlay is open on file select, g_MenuTimer is held at 0 so the screen no
longer drops to the legal screen after 30 s.
2026-09-27 10:01:27 -04:00
James Dansereau f242992360 D245: water tiled at the wrong size + s16 sky/water vertices; D242 closed
The IsWater 32x32 CI8 image (drawn through an RGBA16 tile, D229) was
imported with its mip chain as 32x43 and normalised as 16 wide, so the
pattern ran at 2x and wrapped every 43 rows instead of the N64's 32:
fold steps made it jump. Import the base level only and use CI8
geometry in the tri path. Sky/water fans now go through a port-only
G_FLOATVTX_EXT (float clip positions + S/T) instead of s16 Vtx, removing
the tc shift and wScale quantisation jitter. User-verified vs 1964;
golden 3/3; Win + Linux build. A/B: GE_D245_OLDVTX / GE_D245_OLDTEX.
2026-09-26 18:56:52 -04:00
James Dansereau d07ed0f2d0 D287: apply SDL mouse-mode/cursor changes on the window thread only
input.c called SDL_SetRelativeMouseMode / SDL_ShowCursor from the scheduler
thread (every controller poll) and from the host thread's focus/click
handling at the same time. SDL's mouse API is not thread-safe; on Windows
relative mode starts/stops the raw-input thread, and clicking back into the
window after being unfocused is exactly when both threads flip it - the D287
freeze trigger. Callers now record the wanted state; inputApplyMouseRequests()
applies it on the host thread from videoPumpEvents(). Queue-deadlock leads
ruled out statically. Win+Linux build clean, golden 3/3; freeze re-test owed.
2026-09-25 22:37:46 -04:00
James Dansereau 3072035417 D226: HUD scale option (Game.HudScale 75-200%) — fast3d G_HUDSCALE_EXT rect scaling about per-element anchors
Ammo (per-hand corner), bottom messages/title card (bottom-left) and top
dialogue (text origin) scale with crisp glyphs; dialogue and bottom
messages are width-capped because their line breaks are authored. 100%
emits nothing (golden 3/3). Captures verified at 150/200%; Win + Linux.
2026-09-24 11:28:29 -04:00
James Dansereau f62bceaf88 D335: native widescreen, HUD + front end (Option B Phase 3)
HUD elements now keep 4:3 proportions via fast3d's EXT aspect modes (the PD
pattern): watch, gauge bars, messages/title card, upper text and countdown
CENTER; ammo RIGHT (right hand) / LEFT (left hand). The front end is
pillarboxed (whole menu_jump_constructor_handler CENTER) and the menu
pointer is mapped into the pillarbox. port/include/hudaspect.h emits only
when native widescreen is on and the window is wider than 4:3, so 4:3 and
native-off stay byte-identical. Rule-2 approved.

Verified with 16:9/21:9 captures (HUD, watch, title card, legal, file
select, gun barrel); golden 3/3; Win + Linux builds. Gun-barrel circle
leaks into the bars while sliding in -> GRAPHICS-BACKLOG D335a.
2026-09-23 14:56:29 -04:00
James Dansereau d9d7cd3586 perf: cache getenv in hot-path probes (post-D302 sweep)
Swept all 178 getenv sites; 97 were uncached. The load-time/one-shot ones
stay as-is. The 25 on per-frame/per-node/per-string/audio-thread paths
(langGet, modelGetNodeRwData, modelRenderNodeGundl, skyRender and its poly
path, chrpropRender, frozen-camera tick, amDmaCallback, alRaw16Pull,
_decodeChunk, __CSPHandleMIDIMsg, sndHandleEvent, audioSetNextBuffer,
configGetInputLog/configGetFrameDump) now go through GE_ENVFLAG/GE_ENVSTR
(port/include/envflag.h): one getenv per site per process, exact since the
env is fixed. All sites are inside PORT probe blocks; no game logic touched.

Windows + Linux builds clean (only pre-existing, D324-audited warnings);
bunker1 golden 3/3; GE_INPUTLOG still gates on (12 lines with scripted input).
2026-09-23 13:18:32 -04:00
James Dansereau 7af146a1d0 D329: fix 60 fps guard anim pin (D318/D320 root cause) - tick-granularity divergence
The D318/D320 guard freeze is not a platform-invariant lock. modelTickAnim
folds numticks into one modelSetAnimFrame2WithChrStuff call, which only
advances framea/frameb when floor(frame) crosses an integer. At 1 tick/frame
(port at 60 fps) and attack speed 0.5-0.8, a pair left equal by the aim-hold
endframe clamp is re-emitted stale after the endframe is raised, so the
model pins forever. At >=2 ticks/frame (N64's normal regime) it cannot.

- model.c (#ifdef PORT, Rule-2 sign-off): when the loop did not run and the
  current endframe no longer blocks the next frame, re-derive frameb as
  modelSetAnimFrame does. GE_D329=0 disables, GE_D329LOG=1 logs triggers.
- Verified: lockstep clk=1 no longer pins (Facility ai_19, Control ai_9,
  WSL + native Windows, watchdog off); 0 triggers at clk=2; user playtest
  confirms N64-matching Ourumov timing. Bunker1 golden failure predates it
  (identical with the fix off).
- d318bprobe (TEMP, env-gated): attack-state probe + GE_D318B_CLK lockstep.
- d320repro: fix jump=<chr>,... parser (documented form never parsed).
- findings D329 + D318/D320/D321 corrections, porting-notes D13.
2026-09-23 09:53:10 -04:00
James Dansereau 97c7e38845 D320: resolve sweep via forced repro — ai_19/ai_9 pins confirmed, ai_12 clean; watchdog generalized (table-driven + uncapped repeat rescue); D318 N64-manifestation corrected (unverified; 1964 deliberate-timing A/B armed)
Also commits the prior session's carried-uncommitted set: D294 room-pool
fix (sqrt + 2x cap, user-verified state), D324 stdlib-shim prototypes,
D323 explicit-LOD census + tools_pc tooling, GRAPHICS-BACKLOG row.
texdump*/ probe artifacts gitignored.
2026-09-22 22:30:32 -04:00
James Dansereau aebcf178e6 D75 (M-195): GE_D75V per-batch vertex probe + findings/probes doc updates
- gfx_sp_vertex probe (env GE_D75V=lo-hi): logs per G_VTX batch the
  transformed x/y/z/w min/max, raw first-vertex ob, dest_index, and the
  MP translation row. Proves the logo transform is correct and that every
  batch encodes v0=0 (slot-overwrite hypothesis).
- videoGetFrameCount() accessor for frame-gated probes.
- findings.md: M-195 entry in the D75 section (emission + transform
  eliminated; G_VTX v0=0 overwrite is the leading suspect).
- GE-ENV-PROBES.md: register GE_D75V, bump reconciled line.
2026-09-21 05:14:57 -04:00
James Dansereau b2d5551ece Drop-in ROM: first-run sidecar conversion, no Python (issue #6) (#84)
* Drop-in ROM: first-run sidecar conversion, no Python (issue #6, Part A + interim)

The release bundle now turns a dropped-in NTSC-U ROM into a playable game
with zero tooling: when data/pcmodels-* / data/pccg-* are missing at boot,
the engine spawns the frozen prepare-assets converter shipped in the bundle
(prepare-assets/ge007-convert[.exe], PyInstaller --onefile), waits for it,
and re-checks before continuing (port/src/romconvert.c, called from
romdataInit after ROM validation). Failure aborts boot with a message
instead of the later D179-class crash.

- prepare-assets.py: runs in-process via runpy when frozen (no user Python
  to exec); NTSC-U-only gate with a clear message for PAL/JP ROMs; d88's
  sibling import needs an explicit sys.path entry (run_path does not set
  one). Verified: frozen exe output byte-identical to the unfrozen path and
  to the shipped sidecars.
- CI (linux + windows): PyInstaller freeze step before bundling
  (ensurepip / --break-system-packages for MSYS2's pip-less python).
- bundle scripts: copy ge007-convert into prepare-assets/ (hard-fail if not
  frozen), size guard 60 -> 120 MB.
- Release notes: US-only drop-in instructions, no Python required.
- D258: PAL/JP filelist naming defects documented; multi-region deferred to
  the Part B follow-up.

* CI: install pip via pacman on Windows (MSYS2 ensurepip is disabled); regen findings index for D258
2026-09-13 23:17:29 -04:00
James Dansereau 8c3ce1c688 Build provenance: origin URL + codename in --version and startup log; fork policy in CONTRIBUTING (#82)
- versioninfo gains GE007_VERSION_CODENAME (larkspur) and GE007_ORIGIN_URL,
  printed by --version (new 'origin' line) and the startup log. A build keeps
  pointing at this repository no matter who compiles or renames it.
- CONTRIBUTING: new 'Forks and derivative works' section — rebranding,
  localizing and extending are welcome without permission; attribution (with
  a link) and preserving/linking the docs/dev engineering record are the ask;
  stripping the attribution is what disqualifies a fork as a descendant.
2026-09-13 22:14:41 -04:00
James Dansereau da31edaa70 Steam Deck: first-run preset (native 1280x800 fullscreen, MSAA 4, 150% draw/LOD distance) (#77)
STEAMOS detection in main() seeds video config defaults before configLoad;
first-launch-only by construction (an existing ge007.ini always wins). The
150% DrawDistance/LodDistance addresses authored N64 fade distances reading
short on the close-up Deck panel (e.g. the Dam lock fading in within sight)
while desktops keep the faithful 100% default.
2026-09-13 21:43:29 -04:00
James Dansereau 72af96cf0d Options overlay: native gamepad support (Select / D-pad / A / B / Start).
Controller-only machines (Steam Deck) had no way to open or navigate the
F10 options overlay -- it was keyboard+mouse only. Now:

- Select (BACK) rising edge toggles the overlay, tracked across both the
  open and closed poll paths (padSelectPrev) so a held button cannot
  re-toggle on the transition. BACK is read nowhere else in the port.
- While open, D-pad or left stick up/down moves the selection, A/X step
  forward (the Enter equivalent), B/Y step back, Start closes. The gamepad
  bits are OR-ed into the existing keyboard edge logic, so repeat/clamp/
  scroll behaviour is identical; input.c already swallows pad 0 while the
  overlay is open, so nothing reaches the game.
- inputPadButton/inputPadAxis accessors expose raw SDL pad state to the
  overlay (unaffected by our logic-layer swallow).

Smoke-tested headless with GE_OPTIONSOVERLAY=1 and no pad attached.
2026-09-13 18:40:52 -04:00
James Dansereau cc9068bfa1 Remove the legacy always-grab mouse mode (D192 + D239)
Per the M-82/M-106 user decision: click-to-lock is now the only cursor
capture mode. Port-only deletion, no game-code touch:

  port/src/input.c       drop mouseCaptureMode + Input.MouseCaptureMode key;
                         simplify reconcileGrab/notifyClick/releaseCapture/
                         aimGepdCompute guards; delete the !haveAbs relative
                         P-controller menu path and its dead state (menuEst/Tgt,
                         menuCursorStep, MENU_P_GAIN/EST_DELTA/MOUSE_TO_PX);
                         drop the unused inputMouseCaptureActive() export
  port/include/input.h   update WI-1 comment, remove the dead declaration
  port/src/optionsoverlay.c  remove the 'Mouse capture' toggle row + labels

Existing ini files that set Input.MouseCaptureMode are ignored (config layer
skips unknown keys). Closes D192 (outer grid cells unreachable under mode 0)
and D239 (always-grab parity) by removing the bugged path rather than
patching it.
2026-09-13 15:33:50 -04:00
James Dansereau c20a205bac D194: absolute (cursor-anchored) aim -- GEPD-style position-based aiming
Holding RMB (or the Aim bind, default Left Shift) in a stage now releases
the click-to-lock grab and drives the aim stick from a closed position
loop on the live camera (vv_theta/vv_verta read via player.h): the
crosshair chases the point under the free OS cursor and HOLDS it --
stationary cursor = stationary aim, even while walking. Release re-grabs
with no snap.

- Target derived from cursor NDC against the real render viewport rect
  (viGetView*), portScaleFovY so scope zooms compose for free; re-anchors
  on every cursor move or FOV change.
- Coast prediction (game's 0.05*mult/tick speed decay) prevents overshoot;
  first-order dithering maps continuous demand onto the game's 10 discrete
  stick levels for precision work (shoot-the-lock etc.).
- LMB fires while the cursor is out; ESC still releases capture; menus and
  MouseCaptureMode=0 unchanged. New knob: Input.AimAbsolute (default 1).

Also: -I include shadows <math.h> with the game's constants-only header,
which left fabs/pow/tan in input.c on implicit declarations -- new
port/include/port_math.h pulls the real system math.h via #include_next.
2026-09-13 11:21:13 -04:00
James Dansereau c0047f76f7 docs(dev): D202 M-71 by-ear PASS + close-ready; new D205 wrong/extra SFX indices (bank exonerated); [VOL]/[DISTVOL] probes 2026-09-07 14:18:39 -04:00
James Dansereau 1087820bc3 port(audio): D204 AI-queue starvation fix + GE_D204 health monitor
GE's amMain feedback loop (src/audi.c:531) has a ~3 ms setpoint that PC
scheduling jitter clears, so the SDL queue starves and the device pads
playback with silence -> audio runs permanently ~2 % below real time.

- F5 (the fix): audioGetAiLengthBytes() subtracts a 1024-frame (~46 ms)
  cushion before reporting, so the loop tops up while slack remains;
  audi.c's control law untouched (rule #2 clean).
- F1: osAiGetLength reports AI_LEN_REG single-buffer semantics (bytes left
  in the buffer currently playing), not full SDL queue depth.
- F3: Audio.QueueLimit 8192->2880 + non-silent drops on overflow.
- F4: oversize-block invariant guard.
- GE_D204=1 cheap audio-health monitor (rt/q/drop/max), safe to leave on;
  GE_D204_OLD restores pre-fix behaviour for A/B measurement.

Measured A/B in one binary: rt 0.980 -> 1.000, queue min 0 -> 368,
drop 0. Findings: docs/dev/findings.md D204.
2026-09-05 19:07:32 -04:00
James Dansereau a9cc629de6 port(audio): Phase-3 software mixer + K0_TO_PHYS/bnkf pointer fixes (D198-201)
Software RSP-audio-ucode mixer (macro-swap aXxx -> Impl functions, PD-port
pattern) makes libaudio output audible on PC. Alongside it, two pointer-width
bugs surfaced by exercising the audio path for the first time:

- D198: alAdpcmPull/_decodeChunk used K0_TO_PHYS (N64 kseg0 mask) on 64-bit
  heap pointers; switched to osVirtualToPhysical like every sibling call site.
- D201: bnkf.c bank/inst/sound/wavetable relocation offsets were s32,
  truncating the real 64-bit base pointer for ~50% of allocations; widened
  to uintptr_t.
- D200 (already committed-pending from a prior session): reverb's ALDelay
  u32 negative indices zero-extended into +4GB offsets on 64-bit; (s32)/(s64)
  casts match PD ground truth.
- D199: documents the mixer's aSetBuffer persistent-DMEM-context design,
  reconstructed from call-site pairs (no RSP disassembly to check against)
  since there's no N64 audio ucode source to reference.
- D195/D197 (playtest reports referenced by an earlier HANDOFF note but never
  written into findings.md): backfilled as OPEN with next steps.

Verified: ./build-pc.sh ntsc-final links clean; -level_09 runs 600+ frames
crash-free with music + SFX audible.
2026-09-05 07:22:21 -04:00
James Dansereau 76fb7b7a25 port: D184 F10 overlay v2 — mouse control + resolution/fullscreen rows
- Fix invisible text: drawText passed the measured text w/h into textRender's
  width/height args, which are the on-screen CLIP rect (not the text size), so
  every glyph failed the clip test. Pass the full 2D viewport like bondview2.c's
  debug-text path.
- Mouse UI: hover-to-highlight; left-click in the value column toggles/cycles;
  drag sliders; red X close box (clicking a row's label only focuses it, so
  selecting can't flip a toggle). inputSuspendForOverlay() forces the OS cursor
  free + visible while the overlay owns the mouse.
- New rows: Fullscreen (live) and Resolution (windowed presets filtered to the
  desktop, live resize + recenter). SDL window ops are posted from the
  scheduler thread and drained host-side in videoPumpEvents.
- Layout recomputed for the wide BankGothic font: right-aligned values, wider
  row pitch, all 13 rows fit the ~240-unit 2D space; nothing clips.

Port-layer only. Links clean; runtime-verified on a real display (front end +
-level_09): layout, hover/click/drag, live VSync/filter/fullscreen/resolution.
2026-09-03 09:54:21 -04:00
James Dansereau 2d7add1ef9 Merge branch 'main' into feat/f10-options-overlay 2026-09-03 01:15:26 -04:00
James Dansereau 7b9e078991 linux-runtime: declare getenv()/realloc() for the strict-host build
The N64 <stdlib.h> stub declares neither. MinGW's other CRT headers leak
them; a strict host GCC (Linux/macOS, -std=gnu11) does not, so getenv()'s
64-bit char* return is assumed int and truncated -> configGetFrameDump
(GE_PCDUMP path) dereferenced a low-32 pointer and segfaulted. Add K&R
decls: pc_protos.h (D38 mechanism -> game TUs via <ultra64.h>) and the
port/shim/stdlib.h non-Windows branch (-> port TUs that #include
<stdlib.h>). K&R form stays compatible with the real prototypes; malloc/
calloc/free are already covered elsewhere so not repeated.
2026-09-02 23:03:46 -04:00
James Dansereau fe7a34c56e linux-build: port/shim/sched.h routes C++ TUs to the host <sched.h>
The decomp's src/sched.h (N64 OSSc* scheduler) is on the include path and
shadows the system header. libstdc++'s <thread>/<mutex> gthr headers in the
fast3d C++ TUs #include <sched.h> and need the real one (cpu_set_t,
sched_yield) -- with -std=gnu++20 libstdc++ defines _GNU_SOURCE so
<pthread.h> also references cpu_set_t. New shim: C++ -> generated
hostsched.h (absolute path, hoststring pattern); C -> #include_next so
src/sched.h still wins as before. Windows build verified green (240/241).
2026-09-02 22:31:33 -04:00
James Dansereau 59751d608c linux-build: POSIX branches for time()/bcopy/bzero and Win32-only probes
- pc_protos.h: the vestigial 'long long time()' prototype conflicts with
  glibc's time_t time(time_t*); no game code calls time(), guard it to
  _WIN32 (MinGW shadows <time.h> with the N64 stub; POSIX gets the real one).
- n64stubs.c: don't redefine bcopy/bzero on POSIX -- glibc exports them
  (via <strings.h>, size_t sizes); the local int-signature definitions
  conflict. _WIN32-only now.
- libultra.c: #define _GNU_SOURCE on POSIX for clock_gettime(CLOCK_*) and
  pthread_getattr_np; guard the D60 VirtualQuery committed-memory probe and
  GetCurrentThreadStackLimits behind PLATFORM_WINDOWS with a
  pthread_getattr_np-based stack-bounds fallback for the diagnostic path.

Windows build verified green.
2026-09-02 22:07:36 -04:00
James Dansereau ccef9497ac port: Linux libc-header compat for <stdarg.h> and snprintf
Next GCC-on-Linux breaks after the stddef fix (linux-build CI):

- port/shim/stdarg.h routed to the decomp include/stdarg.h, which defines
  va_list from __builtin_va_list but NOT the __gnuc_va_list alias that
  glibc's <stdio.h> references — 'unknown type name __gnuc_va_list' across
  every TU that pulls <stdio.h>. Add a Linux/macOS branch that provides
  va_list / __gnuc_va_list / va_* straight from compiler builtins and does
  not drag <ultra64.h> into system-header parsing. Windows branch unchanged.

- pc_protos.h declared snprintf's size argument as 'unsigned long long' —
  correct on MinGW (LLP64) but not glibc, where size_t is 'unsigned long':
  'conflicting types for snprintf'. Spell it size_t (now visible via the
  stddef fix); identical to ull on MinGW.

Windows: both changes are no-ops on MinGW (stdarg Windows branch byte-identical;
size_t == unsigned long long).
2026-09-02 21:31:21 -04:00
James Dansereau 2f5d7f9063 port: D184 — F10 port-layer options overlay (approach C)
New port/src/optionsoverlay.c + port/include/optionsoverlay.h: an
immediate-mode options overlay drawn as its own fast3d 2D display list,
appended after the game DL. Implements docs/dev/OPTIONS-MENU-PLAN.md §2/§3.

- config.c/h: configForEachOption() + configSetOptionMeta() label/step/enum
  side table; CONFIG_OPT_* enum. config.c stays free of specific-key knowledge.
- gfx_pc.cpp gfx_run(): one-line hook runs optionsOverlayEmit() after the game
  DL, before gfx_flush. Returns NULL when closed -> zero bytes appended, golden
  dumps byte-identical.
- video.c: F10 toggles (videoPumpEvents, next to F12); ESC closes; wheel
  scrolls; videoRequestLiveConfig()/liveCfgDirty re-applies VSync/FpsCap/
  TextureFilter at frame start.
- input.c: while open, controller 0 returns a neutral pad and nav
  (arrows/enter/wheel/mouse) routes to optionsOverlayHandleInput() — mirrors
  the D180 WI-1 swallow pattern.
- v1 rows: VSync, FpsCap, MSAA(restart), TextureFilter, MouseAimSpeed,
  MouseTurnSpeed, MouseInvertY, MouseCaptureMode, ScreenShakeIntensity.
  Live-apply where possible; configSave() on close.
- GE_OPTIONSOVERLAY=1 auto-opens at boot (env-gated diagnostic, left in).

Port-layer only, no src/ edits, no #ifdef PORT in game code. Build links
241/241. Runtime framediff/soak/eyeball owed on an interactive machine
(this headless env kills the GUI process early and writes 0-byte PPMs).
2026-09-02 19:03:37 -04:00
James Dansereau cd1ed57449 port: D178 — byte-swap the raw briefing segment (blank objectives / briefing text)
Family B of the M-36 triage. The Ubrief*Z segment (assets/obseg/brief/) is
a raw 48-byte ROM image of struct BriefStruct { u16 brief[4]; struct { u16
textid; u16 enabled_difficulty; } objective[10]; }, loaded by front.c
load_briefing_text_for_stage() via _fileNameLoadToAddr() with no converter
and no BE->LE fixup. On the LE host all 24 u16 read byte-swapped:

- textid 0x2C04 (getStringID(LDAM,4)) -> 0x042C -> langGet() resolves an
  unloaded bank -> NULL -> blank objective/briefing text. This is the whole
  of the D143 "briefing text blank" residual (the bank loaded fine; the ids
  into it were swapped).
- enabled_difficulty 0x0001 -> 0x0100 = 256, so `selected_difficulty >=
  enabled_difficulty` is always false -> difficulty-gated objectives vanish
  (on Agent only the one AGENT-difficulty line survived -> lone "a." bullet).

Fix: romdataFixupBriefing() in port/src/romdata.c, called from a #ifdef PORT
block in load_briefing_text_for_stage() right after the load — same shape as
langFixupLoadedBank(). BE-serialized-struct reconciliation (AGENTS.md #2),
semantics-preserving, N64 build untouched. GE_D178=1 dumps raw + fixed words.

Verified: Dam briefing renders all four objectives at 00 Agent with correct
difficulty gating and "007: James Bond" header; -level_09 framediff 3/3;
GE_STARTMENU=7/=13 crash-free; build links clean 240/240.

Investigation + patch: subagent (worktree). Suspect 2 (Gfx-width temp_s0
arithmetic) was ruled out — benign 512-byte gap, langLoadToBank records the
bank base itself. Follow-ups noted in findings.md: annotate D143 as
superseded; latent 64->32 pointer truncation at front.c:6569 / :2202.

Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
2026-09-02 10:56:03 -04:00
James Dansereau ad74865404 port: click-to-lock mouse capture + absolute menu cursor + aim retune (D180)
WI-1: Input.MouseCaptureMode (0 = legacy always-grab, default; 1 =
Quake/Nightdive-style click-to-lock). In mode 1 the OS cursor is free
until a click lands in the game window; ESC, focus loss, or entering
any front-end menu frees it again, and re-entering a stage while armed
re-locks so unpausing needs no click. Mouse buttons are withheld from
the game while the cursor is free in-stage (no phantom fire). Controller
input paths are untouched. reconcileGrab() runs once per controller-0
poll off the game's current_menu global (UI context only).

WI-2: 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*), for true 1:1 tracking. The relative-delta path
(legacy / grabbed) is unchanged.

B3: Input.MouseAimSpeed default 25 -> 16 (aim mode still overshot at 25).

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).

Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
2026-09-02 01:26:33 -04:00
James Dansereau 91b7c5aa4e port: config system — add Float/UInt option types with clamps
Modelled on the PD port's configRegister{Float,UInt}. Needed for the
in-game video/options menu work (WI-3): sliders that store fractional
or unsigned values (sensitivity, screen-shake, fov). configSave() now
flattens all four option kinds into one section-grouped pass so mixed
int/float keys in the same [Section] stay together, and the
newly-registered-key migration check covers them.

Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
2026-09-02 01:26:24 -04:00
James Dansereau 52539d10de Prepare for public release
- README, LICENSE (MIT for the port layer), NOTICE, CONTRIBUTING.
- Restructure docs: internals.md, porting-notes.md, dev-process.md +
  dev/agentic-development.md (the research write-up), dev/ (raw record).
- Replace decomp status-badge CI with a PC-port validate workflow.
- Remove session/agent scaffolding and stale planning docs from the tree.
- Add .gitattributes (normalize line endings to LF); add screenshots.

The pre-2026-08 history is the GoldenEye 007 decompilation by Larry Ficken
(KholdFuzion) and contributors, preserved unchanged. The PC-port work from
2026-08-17 on is mine.
2026-09-01 19:28:59 -04:00
James Dansereau 326564736d M-26: [D] [Debug] ini flags mirroring GE_PCDUMP / GE_INPUTLOG
- config.c: Debug.FrameDump (string) + Debug.InputLog (int).
  configGetFrameDump()/configGetInputLog() check the env var first
  (tools_pc/*.sh unaffected), fall back to the ini.
- video.c / input.c consumers switched to the accessors.
- GE_DETERM is design-only (D117), not mirrored.
- Verified: ini-only FrameDump=40-120:20 -> exactly 5 frames; GE_PCDUMP
  still overrides; -level_09 crash-free.
2026-08-30 13:00:52 -04:00
James Dansereau db1834ca9b M-26: [A/E] atexit config+window persistence, --help/--version
- main.c: atexit(portAtExit) = videoSaveWindowState() + configSave(),
  fires on clean exit(0) (SDL_QUIT/ESC/close); abort() paths skip it.
- video.c: new [Window] ini section (Width/Height/X/Y/Maximized);
  sentinels (0 / -1) reproduce native-size + centered. videoSaveWindowState()
  snapshots live geometry via the SDL2 wapi backend. Video.Fullscreen
  now round-trips.
- main.c: --version / --help (-h) before crashInit — build id, usage,
  21-level -level_XX table.
- Verified: -level_09/-level_20 crash-free 6/6 frames, ge007.ini gains
  populated [Window] block on exit.
2026-08-30 12:55:06 -04:00
James Dansereau 9d136bc657 M-24: more playtest QoL — gamepad hotplug, wheel weapon-cycle, title FPS
port/src/input.c:
- inputRescanPads(): close + re-open all pads on hotplug. Game latches the
  connected mask at osContInit so a late pad merges into controller 0.
- inputPostWheel(): a wheel notch queues a 2-poll A-button press. GE's
  default scheme cycles weapon forward on a bare A edge (invButtons=A,
  bondview2.c:5162/5326). Both directions cycle forward.

port/src/video.c: pump SDL_MOUSEWHEEL -> inputPostWheel,
SDL_CONTROLLERDEVICEADDED/REMOVED -> inputRescanPads, and refresh the
window title with vidAvgFPS ~1 Hz via wmAPI->set_window_title.

-level_09 2/2 crash-free to frame 900, unregressed (91.6%). Build green.
2026-08-30 12:18:55 -04:00
James Dansereau 46fa579e54 M-24: playtest QoL — F12 screenshot + mouse frees on focus loss
port/src/video.c: F12 sets a request flag consumed in videoEndFrame (render
thread, GL context current) -> ppm/shot_NNN.ppm via the existing
gfx_opengl_dump_bound_fbo. SDL_WINDOWEVENT_FOCUS_LOST/GAINED now call
inputSetMouseGrab(0/1) so alt-tabbing away releases the trapped cursor.

port/src/input.c: add inputSetMouseGrab() — toggles SDL relative-mouse
mode (respecting MouseEnabled), zeroes the aim delta, and gates inputUpdate
mouse reads on a mouseGrabbed flag.

-level_09 3/3 runs crash-free to frame 900, unregressed (91.6%).
2026-08-30 12:08:22 -04:00
James Dansereau 8337a87fb2 D118: implement SDL input layer (kbd/mouse/gamepad -> N64 pad)
port/src/input.c was a stub; input only worked via libultra.c's minimal
contSnapshotFromKeyboard (no C-buttons, mouse, or gamepad).

- input.c: keyboard+mouse + SDL_GameController -> OSContPad. Analog stick
  = move, C-buttons = aim. Mouse-look bridged to digital C-buttons via a
  clamped per-axis accumulator (proportional press dwell). RMB/LT = aim
  mode, LMB/RT = fire. Config: ge007.ini [Input] MouseEnabled /
  MouseAimSpeed / MouseInvertY.
- input.h: add inputConnectedMask() + inputComputePad().
- libultra.c SI section: contSnapshotFromKeyboard() now delegates to the
  input module (single source of controller state); osContGetReadData()
  fills all MAXCONTROLLERS channels, not just controller 0.
- GE_INPUTLOG=1 gates a per-poll OSContPad trace (zero-cost when unset).

Build green (ntsc-final); boots -level_09 crash-free 35s. Live input
untested from headless agent - human playtest owed.
2026-08-29 11:11:08 -04:00
James Dansereau 03dcda7bc3 port: feed host argv into the game token string
osPiReadIo was stubbed to return 0, so token.c's cartridge-token read
always produced an empty string and N64 debug switches (-level_09,
-hardN, -m*) were silently ignored -- the game could only ever reach a
level via attract-mode demo playback. Synthesise the token string from
argv[1..] (sysGetTokenString) and serve its bytes from the 0xFFB000
range in osPiReadIo, so -level_09 now boots straight into BUNKER1.

Empty when no args are passed, so no-arg (attract) runs are unchanged.
2026-08-28 09:58:02 -04:00
James DansereauandClaude Sonnet 5 81d5614fbd D69.3: bg/stan sidecar port-layer wiring (port/src/pccg.c)
Serves the offline-converted sidecars from tools_pc/d69_emit.py, cloned
from port/src/pcmodels.c (D50 Plan-B pattern): pccgReserveSize/
pccgLoadSidecars/pccgPatchTable/pccgTotalSize/pccgSidecarBase.

Wiring: romdata.c extends the CART_BASE VirtualAlloc reservation to
include pccgReserveSize() right after the pcmodels extension and loads the
sidecar image there; romdataCartAddrValid's bounds check and libultra.c's
D60 DMA-source diagnostic both extended to cover the pccg byte range.
One-shot pccgPatchTable() call added next to pcmodelsPatchTable() in
load_object_fill_header (objecthandler_2.c) -- reused purely as a
"definitely after obInit()" hook, unrelated to model loading.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3xib6jjMV8o2iE4JfixAo
2026-08-27 21:02:12 -04:00
James Dansereau 8f631462f3 PC port: phase 2 — D59-D68 milestone: full intro renders; blocker is D69 (BG-file BE headers)
D59/D64: the 'external GL DLL' crash was an unbounded blood-RLE write
(sentinel die_blood_image_end lost its N64 section adjacency on PC)
clobbering the gun-barrel sub-DL at ptr_logo_and_walletbond_DL+0x200.
D60: osPiStartDma DMA-target validation (VirtualQuery-based) in libultra.c.
D61/D63: TEMP env-gated ROM-read / VTX-pool / menu probes (strip list in
HANDOFF Task 3). D62: OSMesgQueue/OSScMsg PC layout verification.
D65/D65b: enum HEADS/BODIES signed sentinels under PORT (PC GCC picks
unsigned underlying types; N64 semantics were signed).
D66: romCopyAligned + ramrom replay 64-bit pointer width (s32 truncation
of .bss targets > 4 GiB). D67: struct image_entry N64 layout
reconstruction (dataoffset:24 first, all-u32 bitfields, sizeof==8).
D68: Globalimagetable BE->LE fixup (port/src/gimgfixup.c): bswap the
IMAGESEG-marked Gfx w1 words in the 17 ROM-copied DLs + sImageTableEntry.
index in the 32 tables after texReset's romCopy; marker scan moved to
bytes 6..7 under PORT; compiled globalDL shadows synced post-texLoad for
explosion.c's g_ExplosionDisplayLists.

Result: game boots, plays intro music, and renders the entire ~3.5-minute
intro (logo -> gun barrel with Brosnan -> cast screen) at ~59 fps. New
blocker D69: load_bg_file faults on first stage load - BG-file headers are
N64 big-endian segment-0x0F offsets read LE on PC. Full analysis + strategy
in PCPortResearch.md §F/D69; HANDOFF rewritten for the D69 session.
2026-08-24 03:27:29 -04:00
James Dansereau 330b2bae46 PC port: phase 2 — Plan B executed (D50): offline model sidecars + port plumbing; texCopyGdls w1 fix
- tools_pc/d43_emit.py: full emit pass, 512/512 round-trip validated;
  data/pcmodels-<region>/pcmodels.bin + manifest.csv (gitignored, regenerate)
- port/src/pcmodels.c(+h): cart-extension reservation, sidecar copy, one-shot
  file_resource_table patch; romdata.c/h extended
- objecthandler_2.c: #ifdef PORT hook — pcmodelsPatchTable() + poolRemaining=0
  reset (closes D48.3/R6 reload hazard)
- language.c/textrelated.c: runtime C fixups for lang-bank BE offset tables
  (romdataFixupLangBank) and font re-layout (romdataFontPcSize/FixupFont)
- front.c: legal-screen uninitialized-pointer UB seed (N64 survived by luck)
- tex.c (D50.6): texCopyGdls copied only w0 of each 16B Gfx slot on PC —
  compaction scratch kept stale mempool w1s -> garbage G_SETTIMG -> OOB in
  import_texture_rgba16. Full-slot copy under #ifdef PORT; byte-proven vs
  PlegalpageZ sidecar (w0s match, w1s stale). Model GDLs now execute.
2026-08-23 10:22:03 -04:00
James Dansereau 98412c281c PC port: Phase 1.5 done, first frames render (D38-D42)
- D38: implicit function declarations truncated pointer returns on x86-64;
  added port/include/pc_protos.h prototype shim (398 decls, C-only, anchored
  in port/shim/PR/ucode.h) + pc_netorder.c for ntohl/ntohs; build warning-clean.
- D39: Globalimagetable rebasing - per-symbol offsets + GIMG_OFF() in
  image_bank.c (== N64 (u32)&sym incl. the 0x02000000 base); segment end
  markers extended to the true 0x13F8 size (CSV entry truncated by 0x930).
- D40: g_ModelHitEntries pool sized for the 40-byte PC ModelHitEntry stride
  (was a 12KB .bss overflow clobbering is_ramrom_flag -> demo-replay SIGSEGV).
- D41: release the GL context from the host main thread after videoInit so
  the game scheduler thread can bind it (WGL single-thread currency; this
  SDL2 build's MakeCurrent return value is an inverted ABI artifact).
- D42: rsp.c g_gfxTaskSettingsList XOR toggle -> explicit toggle on x86-64.

Boot now renders real frames ([NOTE] frame N rendered in ... us). Next
blocker D43 (model-file loading ABI mismatch) documented in PCPortResearch.md
section F/H; HANDOFF.md rewritten for it.
2026-08-22 04:07:07 -04:00
James Dansereau dbeee7353f PC port: Phase 1.5 — fix langInit decompression (D31), start D32 ABI layout
- D31 (resolved): replace GE's gzip-1.2.4 inflate with real-zlib
  decompressdata() in port/src/rzdecomp.c; exclude src/game/decompress.c +
  zlib.c from the PC build. Root cause: struct huft is 8B on MIPS but 16B on
  x86-64, overflowing load_resource's fixed 0x2100 buffer and clobbering its
  frame/retaddr on the first file load. mainThread now boots past all language
  files into bossInitMainthreadData().

- D32 (in progress): ROM-serialized structs with 32-bit pointer fields
  misalign when read as 64-bit pointers. Part A done: ModelAnimation's
  bitDescriptors/bitStream are now u32 (N64 layout preserved) with casts at the
  two use sites in model.c. Part B (load-time rebase in expand_ani_table_entries)
  is open — see docs §F/D32 + §H.

- Docs: D31 resolved, D32 recorded (A done / B open), Phase 1.5 added, new §H
  handoff section. AGENTS.md: refined non-negotiable #2 to "game logic is
  unmodified" with a narrow ABI/layout exception; refreshed phase status.
2026-08-21 16:01:49 -04:00