Commit Graph
661 Commits
Author SHA1 Message Date
James Dansereau 37f86bae2d Media: drop the mp4 montage, use the GIF in README + Pages; remove orphan bug shots
- README and docs/index.md (Pages) now embed goldeneye-gh-preview.gif
  where the mp4 <video> tag was; release notes already used the GIF
- git rm the 27 MB mp4 (GIF stays — it is the release-notes asset, pinned
  to the v0.2.0 tag)
- remove three unreferenced docs/img/bugs screenshots left over from the
  culling/D176 A-B captures
2026-09-13 22:18:52 -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 69582caeb0 Release notes: finalize for v0.2.0 final (#81)
* Release notes: finalize for v0.2.0 final (CI re-stamps the body on tag cut)

- Drop '(pre-release)' from the title; <version> stamps to 0.2.0, so the
  Downloads/Verify tables already name the final assets correctly.
- Blockquote rewritten: no more 'cut is for playtesting'. Claims what is true
  (21 missions load and run crash-free) without claiming an end-to-end
  completable campaign; adds the first-public-release note, hedged with
  'to our knowledge'.
- New What's-new bullets: Everything-unlocked-by-default (D257, ON by default,
  F10 opt-out, no active cheats) and the Steam Deck first-run preset + the two
  new F10 rows (No hit flash / Skip intro).
- Known issues unchanged — all eight walked with the user and confirmed still
  open; wording already matches the latest reports.
- Preview montage GIF at the top of the body (release pages can't play mp4);
  raw URL pinned to the v0.2.0 tag, so it resolves once PR #80 has merged and
  the tag is cut.

* Release notes: campaign-completed claim + D255 register-capture diagnosis

- Blockquote: full campaign playtested end to end (all 21 missions, Agent
  difficulty) per the v0.2.0-pre PC playtest
- D255 bullet: updated from 'narrowed to a stale model reference' to the
  pinned diagnosis (NULL ObjectRecord.model on an active record during
  mid-firefight destruction; repros on Bunker + Frigate)
2026-09-13 22:12:43 -04:00
James Dansereau 608808a212 Docs: v0.2.0 finalization — README/Pages + media pass (#80)
* README: 32 s gameplay preview montage (mp4) + preview-video runbook

* Prune: drop superseded goldeneye-demo.gif (replaced by gh-preview montage)

* Docs: v0.2.0 finalization pass — final wording, aligned known issues, Deck-verified line, social preview card

- README + pages site: 'v0.2.0 (pre-release)' -> v0.2.0 throughout; the
  stale 'not yet verified on real Deck hardware' line is replaced with the
  playtest fact (D253 fixed, D255 still open).
- Known-issues lists in README + site now match the release notes one-for-one
  (adds the F10 bottom-row duplicate D251 and the intermittent Deck SIGSEGV
  D255; D243/D230/D252/D245/D246/D75 carry their finding labels).
- Working list mentions AllUnlocked default-ON (D257) with the F10 opt-out.
- First-public-release note added to the README hero and site status line,
  hedged with 'to our knowledge'.
- social-preview.png (1280x640 GitHub link card) composed from a gameplay
  frame of the preview montage; scratchpad/make-social-preview.py is the
  local recipe (gitignored, like make-preview.py).
- Commit docs/img/shots/manifest.csv (gallery -> source recording map);
  gitignore the regenerable CONTACT-SHEET.jpg; drop scratch-gdb-cmds.txt.

* Social preview: regenerate card from updated briefing HTML (v0.2.0 final wording)

The PNG committed earlier was a screenshot of the rendered site (stale
wording, video element unrendered). The canonical source is
.github/social-preview.html per .github/social-preview.md; update its
Status/Statistics lines to the v0.2.0 final state and re-render:

- Status: RELEASED v0.2.0 — WINDOWS · LINUX · STEAM DECK
- All 21 solo missions — full campaign completed in playtesting
- ~22k lines C/C++ · ~10k Python (port layer + tools)
- ~250 bugs root-caused · two coding agents, one human, part-time

Verified by pixel-diff against the previous render: only the four text
bands changed; title/fonts/layout identical. Drop the unreferenced
root-level social-preview.png.

* docs: refresh social preview + status for v0.2.0 playtest results

- Social card: 'RELEASED v0.2.0', 21/21 missions completed in playtesting,
  ~250 bugs fixed, two agents + one human; re-rendered the PNG from the
  canonical .github/social-preview.html source (pixel-diff vs the old render
  shows only the four edited text bands changed)
- README Status: campaign now claimed as completable end to end (user
  playtest: full campaign, Agent difficulty, PC); Working list matches
- findings: D255 updated with the Deck register capture (Rdi=nil -> NULL
  ObjectRecord.model, destroy-path ordering case confirmed; truncation ruled
  out) and the rule-#2 sign-off question for a guard; D254 field-verified

* docs: regenerate findings index (D254/D255 status updates)
2026-09-13 22:12:13 -04:00
James Dansereau c62803f890 D257: Game.AllUnlocked option — all levels, 007 mode, full cheat menu (#79)
Port-layer only (no game-code edits):
- main.c seeds debug_enable_all_levels_flag + debug_007_unlock_flag
  after configLoad() when Game.AllUnlocked is set (same class as the
  existing GE_UNLOCK_ALL getenv hook).
- libultra.c eep shim patches the save block at read time (the exact
  fileValidateSaves pattern: block 4, 5x96 bytes): sets all 20
  progression-gated cheat-unlock bits per slot and fills every empty
  stage/difficulty completion time with the max (0x3FF), preserving
  existing player times, then recomputes each slot's CRC with the
  game's own fileGenerateCRC so validation passes. The completion-time
  half is required: with any SP cheat active, g_AppendCheatSinglePlayer
  makes mission select accept only STAGESTATUS_COMPLETED levels, so an
  unlock-only save locks every level once a cheat is turned on.
- optionsoverlay.c: 'All unlocked' toggle row (default ON for this cut).

Verified: fresh-eep GE_SAVELOG shows all 5 slots stored==calc; user
playtest confirms all levels at each difficulty, 007 mode, populated
cheat menu, cheats working, mission select intact with all cheats on.
2026-09-13 21:48:09 -04:00
James Dansereau c51f01ad14 F10 overlay: expose Game.NoHitFlash and Game.SkipIntro toggles (#78)
The two user-facing config keys were registered but only reachable by
hand-editing ge007.ini. Add ROW_TOGGLE rows (below Screen shake):

- No hit flash  (D232, community "no damage flash"; suppresses the
  non-black fade paths in bondview2)
- Skip intro    (D216; read at title-stage load, so it takes effect the
  next time the title screen is reached)

Video.FixMipTextures / Video.WrapFix deliberately left out: opt-in
fidelity bug-fixes, not preferences.

Verified live (isolated CWD): both rows render in the F10 panel and
toggle; clean exit persists Game.NoHitFlash=1 + Game.SkipIntro=1 to
ini; relaunch with SkipIntro=1 logs "Game.SkipIntro: booting to
file-select" and skips straight to mission select. NoHitFlash's
in-game suppression rests on its prior D232 verification (needs
combat, not automatable).
2026-09-13 21:44:56 -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 f8526f66d0 CI: re-stamp release notes on every tag cut, not just at creation
The release job only set --notes-file when creating the draft; existing
drafts got 'upload --clobber' (assets only), so the body stayed frozen at
the first cut's notes. Compute the stamped notes unconditionally and add
'gh release edit --notes-file' to the update path.
2026-09-13 20:33:00 -04:00
James Dansereau 1f71bccbdb Release notes: D256 save-persistence fix, D255 status update
D256 (Linux saves/config now persist from any launch dir) is in this cut
and Deck-verified; the D255 line reflects the narrowed diagnosis and the
register-capturing crash handler. Drop the 'no known crashes' claim from
the pre-release blurb since D255 is open.
2026-09-13 20:29:38 -04:00
James Dansereau 4ca075061e D256: Linux $S (saves/config) now falls back to the exe dir, mirroring Windows
romdataInit finds the ROM in CWD data/, exe-dir data/, or ./, but the
POSIX $S branch only ever produced a CWD-relative path, so any launch
from another directory ran fine while every EEPROM and ini write failed
silently (no ./data/ to fopen into) — saves never persisted and F10
settings reset on every launch. Mirror the Windows branch via
readlink(/proc/self/exe) and mkdir the target dir best-effort. Verified
on WSL with exe+data in /tmp and CWD=/: config, eeprom, and ROM all
land in <exedir>/data/.
2026-09-13 20:17:34 -04:00
James Dansereau c7898d1620 D254/D255: stop the Linux crash handler eating its own logs; document the Facility firefight crash
The POSIX handler re-entered on SIGABRT (sysFatalError -> abort) and
truncated ge007.crash.log, so field crashes came back as bare abort
records with no faulting PC or registers. Guard against re-entry,
terminate directly with _exit(), and dump si_addr + the general register
set from the ucontext — a Deck crash now names the bad pointer on its
face.

Also documents D255 (new SIGSEGV in vtxstore's active-object walk during
a Facility firefight on the Deck; disassembly shows the fault is a
32-bit-zero-extended read of ObjectRecord.model, misnamed ChrRecord.
chrflags by the decompiler) and updates the release notes' Deck status.
2026-09-13 19:28:25 -04:00
James Dansereau f091619553 Release notes: known-issues pass from v0.2.0-pre playtest
- D197 (Silo face wrap): closed per user playtest, dropped
- D75: reworded — Nintendo logo still broken, Rareware logo filtering off;
  mission-complete/mode-select screens have no 3D models (old framing wrong)
- F10 ghost row: now deterministic bottom-row duplicate of the selected item
  (D251 addendum); earlier intermittent 4K framing superseded
2026-09-13 19:02:04 -04:00
James Dansereau 49f843a156 Regenerate findings index (D253) 2026-09-13 18:58:20 -04:00
James Dansereau 20368d1d17 Docs: drop false 'no widescreen' limitation; Deck install via SFTP or releases download 2026-09-13 18:56:40 -04:00
James Dansereau 1db6992e5e Fix D203/D253: Facility SIGSEGV on Linux — callback prototype ABI mismatch
stanCheckLinkedSpecialTile was decompiled with an all-s32 middle-arg
prototype while its callers pass it through the standTileLocusCallback_B_t
typedef (f32 f32 f32). On x86-64 SysV the int/float class split puts the
6th arg (outFlags/record) in %rdx per the typedef but %r9 per the
definition, so the FORCECROUCH/LADDER store hit a stale scratch register.
MIPS o32 passes both through GPRs a0-a5 identically, which is why the
mismatch was invisible on N64 and in the decompilation.

ABI-class edit (AGENTS.md #2 narrow exception): body never uses the three
middle params; N64 codegen unaffected. Verified under gdb on WSL:
pre-fix SIGSEGV at stan.c:2441 with r9=0x3 and a valid record in the
caller frame; post-fix the same walk route completes clean.
2026-09-13 18:55:26 -04:00
James Dansereau 060d5ac86d Docs: README + Pages landing overhaul for v0.2.0; Deck settings docs.
- README: manual Contents TOC dropped (GitHub auto-generates); Download
  table moved to the top with Quick start under it; Status compressed to
  a Working paragraph + tight known-issue list (D-numbers live in the
  release notes / finding log now); How-it-works tightened.
- Pages (docs/index.md): demo gif is the hero; download table up top;
  new How-it-works and Honest-status sections; legal footer; the 19-shot
  playtest gallery unchanged.
- Options overlay is now gamepad-driven: README Steam Deck section, the
  bundle README (new @DECK@ block on Linux, controller column added to
  the controls table), and the release-notes template all document
  Select / D-pad / A / B / Start instead of a Steam key-mapping workaround.
2026-09-13 18:40:57 -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 97ba1e9b07 Pages: caption remaining gallery shots (7 Silo, 9 Frigate) — all 19 now labeled. 2026-09-13 18:26:42 -04:00
James Dansereau 5768bd7f46 Pages: level captions on the playtest gallery (owner-provided).
Shots 7 and 9 remain uncaptioned pending owner.
2026-09-13 18:26:00 -04:00
James Dansereau f6ffa41c58 Pages: trim playtest gallery to 19 captures (drop 8,12,14,15,17,18,22 per owner review).
Numbering kept as-is (gaps remain) so the contact-sheet numbers stay
valid for the pending caption pass.
2026-09-13 18:24:23 -04:00
James Dansereau 998bbd5364 Pages: v0.2.0-pre playtest gallery (26 in-engine captures).
Resized to 1600px width / q85, clean names (shot-NN.jpg), in a
collapsible section so the landing page stays light. Raw captures and
the curation work-products (contact sheet, manifest) stay local under
docs/img/screenshots/ (gitignored). Per-image captions/level names and
trimming pending owner review of the contact sheet.
2026-09-13 18:22:25 -04:00
James Dansereau c5e5abea52 review pass: shoulder edge-state in menus; release-notes asset names
Two defects found in the local review of the unpushed v0.2.0-pre work:

1. input.c: padShoulderPrev was only updated outside menu mode, so a
   shoulder held across the menu->game transition fired a spurious
   weapon cycle on the first gameplay poll. Edge state is now tracked
   every poll; emission stays suppressed in menus where A means select.

2. ci.yml release job: the notes template substitutes <version> with the
   raw tag (v0.2.0-pre), but bundle-{linux,win}.sh strip the leading v
   when building asset names -- the published notes would have linked
   goldeneye-pc-port-v0.2.0-pre-* assets that don't exist. Substitute
   the stripped form (NOTES_VERSION) instead.
2026-09-13 17:16:07 -04:00
James Dansereau 3da19f7539 docs: README + Pages landing overhaul for the v0.2.0-pre push
README:
- compress the Background section (two-agent detail now one tight
  paragraph; full write-up stays in agentic-development.md)
- new 'Beyond playing' section: tweak it (ini/F10/-fresh), read it
  (internals + porting notes as the entry points, src/ vs port/ split),
  mod it (MIT port layer, CONTRIBUTING ground rules, docs/dev record)
- Quick start no longer implies NTSC-U only

Pages (docs/index.md, served from main:/docs):
- status was stale ('alpha, v0.1.0') -- now v0.2.0 pre-release with the
  honest rough-edges summary linking to README #status
- new Downloads section up top: Windows + Linux/Steam Deck bundles with
  the no-ROM note and the four-step path
- 'Play it - or take it apart' CTA mirroring the README sentiment
  (try it / read the code / mod it / research angle)
- in-engine gallery (attract views + demo clip); more captures land
  here as playtesting continues
2026-09-13 16:05:49 -04:00
James Dansereau 517754546b README: document -fresh clean-slate launch flag 2026-09-13 16:03:01 -04:00
James Dansereau 798aebcb64 main: -fresh flag wipes save + config before startup
Launch with -fresh (or --fresh) for a clean-slate run: removes
$S/ge007.eep (the file-backed EEPROM save, all five slots) and
$S/ge007.ini before configLoad / the eeprom's lazy load touch them.
The ini is re-written with defaults on exit as usual; the .eep only
reappears if a save is actually written. Verified in a scratch data
dir: both files removed at launch, ini regenerated as defaults.
2026-09-13 16:02:50 -04:00
James Dansereau 135df58e0a input: modern dual-stick controller layout (Xbox re-release scheme)
Steam Deck target. LS move/strafe + RS look + RT fire + LT aim were
already in place (D194/D238 naturalPitchMode); this completes the
scheme with shoulder weapon cycling per the console re-releases:
RB rising edge = next weapon, LB rising edge = previous weapon.

Cycling rides the game's own A-line formulas (bondview2.c
weaponForwardOffset/weaponBackOffset): a fresh A edge cycles forward;
A+Z presented on the same tick cycles backward -- the identical trick
the mouse-wheel path uses (D223), which also guarantees no stray shot
(triggerOn is gated off while invButtons is held). Edges are emitted
for exactly one poll so holding a shoulder cannot latch A and block
firing; both are suppressed in menu mode where A means select.

A/X = action/use, B/Y = crouch/cancel (unchanged); LB no longer maps
to lean. README controls table + release notes updated.
2026-09-13 15:58:03 -04:00
James Dansereau 3ed75e72f5 bundle-linux.sh: match ldd's absolute paths in the SDL2 check (CI red fix)
The $ORIGIN verification compared ldd's absolute resolved path against the
relative $OUT, so it always failed even though the bundled libSDL2 was the
copy being resolved (CI 34778363397 showed exactly that). Match both forms;
the remaining-deps listing now filters by bundle dir name for the same
reason.
2026-09-13 15:49:37 -04:00
James Dansereau 2f002a31aa D252: document the rainbow particle-palette bug (no fix for v0.2.0-pre)
User playtest report: bullet-impact sparks and lingering smoke/explosion
residue drift through the full hue wheel over time instead of holding their
intended grey/orange palette. Logged in findings §F (D252) with this
session's mechanism work: particle colour comes entirely from the 15
distinct IMAGE_SMOKE_N x IMAGE_FIRE_N (RGBA16) pairs the particles age
through (assets/oddtextures.c records, white default vertex); the M-82/D172
tile-offset fix and the 5551 decode are both verified correct for this path;
prime suspect is import_texture_rgba16 deriving width/height from the tile
line size (32x7 instead of 16x14 for the 16px-wide fire tiles), which would
scramble each aging stage into a different wrong hue -- exactly the observed
slow rainbow sweep. Per user instruction: documented only, ships as a known
issue (README + release notes + GRAPHICS-BACKLOG); index csv regenerated.
2026-09-13 15:49:34 -04:00
James Dansereau 52f79b2cb6 Credits: GEPD Edition mouse-aim reference (Carnivorous; 1964 team)
The in-game GEPD-style aim mode (D194/M-122) is modelled on the Mouse
Injector plugin for 1964 -- credit its author Carnivorous and the 1964
developers Rice & schibo in the README Credits section. Note the port's
implementation is an independent reimplementation of the behaviour, not
derived code.
2026-09-13 15:49:28 -04:00
James Dansereau 4065a0007d Bundle SDL2 in the Linux tarball (turnkey Steam Deck sideload)
bundle-linux.sh now copies the exact libSDL2 the exe was linked against next
to it and sets the rpath to $ORIGIN via patchelf, with an ldd assertion that
the bundled copy is the one resolved. zlib/libGL remain system deps
(preinstalled on desktop distros and SteamOS). CI installs patchelf; the
generated in-bundle README gains a Steam Deck sideload note and the SDL2
zlib-license copyright file is carried when the distro ships one.

Also refreshes the release-facing docs that ship with / describe the cut:
  - .github/release-notes.md: v0.1.0-alpha body -> <version> pre-release body
    (what's new + known issues incl. D243/D230/D245/D246/D197/D75, the D251
    4K ghost row, and 'not yet verified on real Deck hardware')
  - tools_pc/dist/README.md.in: alpha claims (no audio / black sky / US-only)
    -> pre-release status; ROM table now lists all three regions (the asset
    step auto-detects by SHA-1)
2026-09-13 15:39:22 -04:00
James Dansereau 6d29f0accf README for v0.2.0 pre-release: status rewrite, download emphasis, Steam Deck section
- Status: 'alpha' -> 'v0.2.0 (pre-release)'; working list now reflects the
  60 fps fix (D248), click-to-lock mouse + GEPD aim mode (D194 closed), sky
  (D227) and water green-pulse (D229) fixes, F10 overlay, mute/screenshot QoL;
  known issues updated (cutscenes D243/D160/D173, music D230, front-end models
  D75, water seam D245, edge strips D246).
- Download: leads with the two v0.2.0-pre bundles (names match bundle-*.sh
  output, which strips the leading v), notes SDL2 is bundled in the Linux
  tarball so it runs as-is on any distro or a sideloaded Steam Deck.
- New 'Steam Deck' subsection: sideload steps, CPU-bound performance note,
  D203 status (suspect fixed, unverified on real hardware).
- Quick start + comparison table + Linux build section updated to match.
2026-09-13 15:36:09 -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 d3d968bc0e D251: F10 options overlay QoL pass -- scrollable panel, MSAA wrap, hidden auto-% rows, SensLink toggle, hover-select removed
Known issue (logged in D251): intermittent ghost repeat of a top row at the
panel bottom in true 4K fullscreen; local repro clean, MSAA-off bisection
pending with the user.
2026-09-13 15:29:23 -04:00
James Dansereau 774d398263 D194: record full closure (M-123) -- sens co-tuned, aim-mode FPS resolved by user evidence 2026-09-13 15:29:19 -04:00
James Dansereau 4b322638a1 Merge branch 'fix/d194-mouse-dt-decouple' -- D194 GEPD-mirror aim mode
RMB aim mode rewritten as a direct mirror of GEPD's mouse aiming:
crosshair-position writes with the game's own damping left running,
gun pose derived from crosshair position (no stick synthesis), camera
edge-scroll at screen edges. vv_theta [0,360) wrap dropped for GEPD
parity. User playtest: spazz accepted (95%), sensitivity calibrated
(hipfire matched to aim mode; MouseSensitivity master now scales both).
Defaults ~25% above the user-approved match point. Input.GepdSens
renamed Input.AimModeSens (old key kept as deprecated alias).
2026-09-13 14:29:41 -04:00
James Dansereau 46f83aa116 D194: rename user-facing Input.GepdSens -> Input.AimModeSens (community name); old key kept as deprecated alias 2026-09-13 14:29:35 -04:00
James Dansereau 2d5674bd8c D194/D238: defaults ~25% faster per user -- GepdSens 30->38, MouseTurnSpeed 40->50 (match point kept as the 100% master baseline) 2026-09-13 14:27:49 -04:00
James Dansereau 7410893273 D194/D238: align hipfire with GEPD aim -- MouseTurnSpeed default 100->40 (mid-speed match at GepdSens=30); master MouseSensitivity now scales aim mode too 2026-09-13 14:22:25 -04:00
James Dansereau 6d083c2c7c D194: user playtest verdict -- spazz accepted (95%), remaining work is sensitivity co-tune + FPS control 2026-09-13 14:17:17 -04:00
James Dansereau 68cb3872d0 D194: GepdSens default 20->25 (user: a bit slow); log ct=g_ClockTimer for multi-tick spazz check 2026-09-13 14:14:17 -04:00
James Dansereau e7c2ffe52b D194: drop vv_theta [0,360) wrap in GEPD aim (GEPD parity); extend gepdaim log with residue/autoaim terms for spazz diagnosis 2026-09-13 13:59:58 -04:00
James Dansereau 790fb622c5 D194: GEPD-mirror aim -- direct crosshair/gun/camera writes, game damping left running
Mirrors MouseInjectorPlugin (games/goldeneye.c) in model: mouse motion
drives a crosshair POSITION accumulator (clamped +/-5.16), written
straight into crosshair_x/y_pos + gun_azimuth_angle/turning via GEPD's
formulas; the view only scrolls past 72% toward the screen edge
((ratio-0.72)*475*dt*fov/basefov); no look stick is emitted while
aiming; on release the game's own damping eases everything to centre.

Key correction vs the earlier attempt: GEPD patches NOTHING in
bondview2's damped crosshair update (caclulate_gun_crosshair_position_
rotation) -- the per-frame overwrite simply wins over the damping, and
that function is what derives crosshair_angle (HUD crosshair + aim ray)
and field_FFC (arm/gun offset in gunUpdateAndFire). The temporary
portAimOverrideActive() guard is removed; bondview2.c carries only an
explanatory comment. No game-code behaviour change.

Knobs: Input.AimAbsolute (1=GEPD mirror, 0=legacy velocity stick),
Input.GepdSens (default 20). Field mapping vs GEPD's N64 RAM offsets
verified with offsetof (constant 0xBE0 delta = 32->64-bit pointer
expansion).

Open: crosshair/arm 'spazz' glitch, aim-mode FPS drop (perf.ps1),
sensitivity tuning -- see docs/HANDOFF.md M-122.
2026-09-13 13:53:05 -04:00
James Dansereau d956dffefc D194: GEPD-style relative aim -- crosshair locked, mouse movement rotates view
The cursor-anchored chase model swung the aim out to where the cursor
sat, which is not how GEPD feels. Replace it with the model the PD port
itself uses (mouseDX/DY + sensitivity): while RMB is held and the cursor
is free, per-poll cursor DELTA rotates the view by the same screen angle
the cursor swept (viewport width <-> full hFOV, height <-> fovy). The
crosshair stays locked at centre; stop moving and the game's own speed
decay coasts the view to rest where it is. Scope zooms compose for free
(angle-per-pixel scales with the live fovy). Dithered quantization keeps
fine sub-level movements smooth; no target, no chase, no snap-back.
2026-09-13 11:54:49 -04:00
James Dansereau d4b283899f D194: fix inverted angle conventions in absolute aim
Both axis signs were wrong (verified against bondview2.c plant code +
live GE_INPUTLOG trace): a right turn INCREASES vv_theta and looking
down DECREASES vv_verta. The anchor placed the frozen target at the
mirror image of the cursor point, so the controller steered through
the real point and ran away to the screen corner.

- anchor: tgtTheta = curT + offYawR, tgtVerta = curV - offPitchD
- stick map: errT>0 -> +stick (turn right), errV<0 -> +stick (look down)
- per-poll absaim log now includes cursor/ndc/fov for tuning

Injection test (RMB hold + 15 mouse steps): target tracks cursor,
error converges to deadband (0.7deg), stick->0, view holds.
2026-09-13 11:38:39 -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 16966df2e3 Merge branch 'main' into fix/d194-mouse-dt-decouple 2026-09-13 10:13:34 -04:00
James Dansereau 7c48f664ca D222/D218/D249: fix high-FovScale culling, draw distance, LOD scaling (#76)
* D222/D218: fix high-FovScale culling + add Video.DrawDistance

D222 (high FovScale breaks screen-edge culling): currentPlayerSetCameraScale()
derives both the frustum-cull plane normals and the fog/LOD
distance-visibility-fade scale (c_scalelod/c_lodscalez) from c_perspfovy,
which every viSetFovY/viSetAspect/viSetFov/viSetZRange call in fr.c was
setting from the nominal fovy -- never the portFovScale-widened value D211
already computes for the actual render projection two lines away. Factored
the widen-FOV math into a shared port/src/video.c helper (portScaleFovY,
same logic D211 already had inline) and fed it into all 4 currentPlayerSetPerspective
call sites. Identity at FovScale=100 by construction. Audited: the affected
fields are consumed only by render-side fog-visibility-fade and screen-space
transform code (propobj.c/bondview.c), not NPC AI visibility/behavior.

D218 (wide FOV exposes the fog/far-clip boundary): implements the
previously-parked plan. Video.DrawDistance (percent, 100-400, explicit
override) and Video.DrawDistanceAutoFov (couples the multiplier to FovScale
by default) applied in fogLoadCurrentEnvironment (bgfog.c) to both the
far-clip plane and the fog-blend distance read, so they stay consistent.
Clamped to [1.0, 2.5] per the plan's own far-field z-fighting caution.
Both new knobs added to the F10 overlay alongside FovScale.

Rebuilt clean, crash-free at FovScale=150/DrawDistanceAutoFov=1 on Dam.
bunker1 golden framediff was inconclusive in this dev box -- confirmed via
an A/B stash/rebuild that the unmodified baseline also fails it by a
similar/larger margin (pre-existing frame nondeterminism, not a regression
from this change).

D249 (default-FOV culling, sometimes) is unrelated/still open -- different
mechanism, not touched here.

Needs a human's eyes: does high-FOV screen-edge culling and the wide-FOV
fog/draw-distance artifacting actually look fixed now.

Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog

* D249 follow-up: add Video.LodDistance geometry-LOD scaling

Found a real, distinct geometry-LOD system in src/game/model.c:
MODELNODE_OPCODE_LOD nodes whose children are shown/hidden per-frame by
modelUpdateDistanceRelations() testing a view-space distance (scaled by
getPlayer_c_lodscalez(), now FOV-consistent per this branch's D222 fix)
against each node's MinDistance/MaxDistance -- separate from the
fog-visibility-fade D218 already covers. It has an existing ground-truth
multiplier hook, g_ModelDistanceScale, already used transiently by the
game itself (chr.c's 0.3125f close-up override, front.c/title.c's menu
modelSetDistanceDisabled calls).

Rather than write to that shared global (would fight those existing
overrides), compose a second multiplier at the same call site:
portLodDistanceMultiplier() (port/src/video.c), driven by new
Video.LodDistance (percent, 25-400, default 100) and
Video.LodDistanceAutoFov (default OFF, unlike DrawDistanceAutoFov --
this is meant as a standalone perf lever to pull down, not something
that silently costs more as FovScale widens). Inverse-scaled: 200%
keeps full detail twice as far (more cost), 50% drops to the lower LOD
twice as soon (less cost). Both new keys in the F10 overlay.

Identity at LodDistance=100. Rebuilt clean, crash-free at LodDistance=40
(aggressive) on Dam and at defaults on bunker1.

Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog

* D222/D218 follow-up: fix guard fade-early regression, retune draw-distance coupling

Live playtest on Dam found two issues in the D222/D218/D249 work:

1. Regression: D222's c_lodscalez widening (correct for the screen-edge
   cull planes) is also read directly by propobj.c's
   chrobjFogVisRangeRelated()/sub_GAME_7F054C58() -- the character/prop
   fog-visibility-fade cutoff. Uncompensated, this made guards fade
   sooner at wide FOV than before D222. Fixed by dividing that same
   c_lodscalez reading by portDrawDistanceMultiplier() at both call
   sites, so the "how far can you see" question stays answered
   consistently across both consumers.

2. Tuning: a straight 1:1 FovScale auto-coupling on
   portDrawDistanceMultiplier() wasn't aggressive enough -- live-tested
   up twice (1x -> 2x -> 4x FovScale, clamp ceiling raised 2.5x -> 4.0x
   -> 6.0x each time) until guards held up and the Dam tunnel's blue fog
   artifact receded. Re-verified the fog ramp itself (not just the
   far-clip cutoff) scales with the multiplier end-to-end.

User confirms this is good for now.
2026-09-13 10:13:04 -04:00
James Dansereau 70bc9d65b5 D250: getenv() caching fix -- root cause of the Dam fps drop (plus sysSleep unit fix, GE_D250 probe, perf.ps1) (#75)
* D250: fix sysSleep unit-conversion bug in frame-pacing; add GE_D250 lvlRender probe

port/fast3d/gfx_sdl2.cpp sync_framerate_with_timer() computed the
remaining frame budget in 100ns units (qpc_to_100ns) and passed it
straight into sysSleep(), which takes microseconds -- a real 10x
oversleep whenever that branch fires. Genuine bug, but only active
when target_fps is nonzero (tied to Input/Video FpsCap); confirmed via
live testing that it is NOT the active cause of the user's reported
~45-50fps with FpsCap=0/VSync=0, where this pacing path never runs.

Added GE_D250 (src/boss.c, #ifdef PORT) to isolate lvlRender()'s
DL-building cost (culling/animation/scene traversal) from gfx_run's
already-measured software-RSP interpretation cost. Live test on Dam
shows lvlRender averaging ~0.5-1.5ms -- also not the bottleneck.

Documented in docs/dev/GE-ENV-PROBES.md (GE_D250 row). findings.md
D250 update to follow after rebase.

Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog

* docs(D250): live A/B session -- MSAA/aniso/FOV/resolution/vsync all ruled out

Live session with the user driving the game directly, confirmed the
sim-healthy/render-sagging pattern from earlier idle testing reproduces
under real play on Dam. One-at-a-time A/B via the F10 overlay ruled out
MSAA, anisotropy, FOV, resolution (1280x960 fullscreen vs 640x480
windowed), and VSync (render never exceeds ~60 even uncapped) as the
cause -- the cost is resolution/GPU-setting-independent.

gfx_run (~6-10ms) + lvlRender (~0.5-1.5ms, via the new GE_D250 probe)
together don't account for the real ~20-22ms/frame. Leading remaining
suspect: the emulated N64 thread-handoff/scheduler round-trip
(gesched.c/sched.c) -- real OS context-switch/wake latency standing in
for what was free hardware handoff on N64. Needs a real profiler
(Visual Studio / WPR) attached during a live bad-fps moment to confirm;
ad-hoc per-call timers have now ruled out the two most obvious CPU call
sites without finding the cost.

Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog

* D250: fix uncached getenv() in fast3d hot paths -- root cause of the fps drop

A live WPR/xperf CPU trace (tools_pc/perf.ps1, new sister script to
debug.ps1) with Microsoft public symbols resolved found the dominant
render thread spending only 5.2% of its CPU time in the game's own
code -- 43% was in msvcrt.dll, and getenv()/_getenv_helper_nolock
alone accounted for ~50% of the thread's total CPU time.

Root cause: port/fast3d/gfx_pc.cpp's import_texture() (runs on every
texture bind) and several per-triangle/per-G_SETCOMBINE debug-probe
checks (GE_DTEX, GE_TEXDUMP, GE_TEXRAW, GE_D229 x2, GE_D172 x2, plus
GE_TEXDUMP/GE_PCDUMP in gfx_opengl.cpp) called getenv() uncached on
every call, unlike the safe static-cached pattern already used
elsewhere in this codebase. On a texture-heavy scene that's tens of
thousands of locked, locale-aware Windows CRT environment lookups per
second -- invisible to both of the timers added earlier this session
(gfx_run's own cost measurement and the new GE_D250 lvlRender probe),
since neither isolates CRT/library time from the game's own code.

Fix: cache every getenv() site in gfx_pc.cpp/gfx_opengl.cpp with the
established static-int pattern. Narrow, mechanical, no logic change.

Verified: the exact idle-Dam test that was pinned at 45-50fps before
the fix now holds a rock-steady 60-61fps, zero dips. Also explains why
the earlier MSAA/aniso/FOV/resolution/vsync A/B pass all showed "no
change" -- the getenv() cost was a large per-call constant that
swamped any real, smaller resolution-dependent GPU cost at every
setting tried.

Residual, separate issue found once this was fixed: still runs badly
at 1440p+ (640x480 now holds a clean 60fps) -- likely the real GPU
fill-rate cost the getenv() bug was masking, now exposed on its own.
Not yet investigated.

docs/dev/findings.md D250 updated with the full root-cause writeup.
docs/porting-notes.md gets a new lesson under section E (cache
getenv() in any hot-path probe). tools_pc/perf.ps1 automates the
trace-and-analyze workflow for next time.

Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog

* docs(D250): correct the 1440p+ follow-up -- it was a stale-build false alarm

User re-tested 1440p/4K on the actual getenv-fix build and confirmed
it holds up fine too. The "still runs badly at 1440p+" report was
from before the fix was live, same stale-build class as the DLL-load
false alarm earlier in the session. D250 is fully resolved across all
resolutions tested, not just 640x480.

Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog

* docs: add missing GE_D219RAW row to GE-ENV-PROBES.md (CI validate fix)

Pre-existing gap unrelated to this branch's own changes -- gen_env_probes.py
--check caught it now since it runs on every PR. GE_D219RAW has had live
getenv() sites in src/game/image.c since the D219 investigation but was
never added to the probe doc.
2026-09-13 09:33:36 -04:00
James Dansereau ef0c0900b4 docs(D250): rule out build flags + idle-load as the fps bottleneck (#74)
Build flags confirmed as -O2 RelWithDebInfo by default, not the cause.
GE_D193 idle-load probe on bunker1 and streets both hold a healthy
55-60fps in this dev box, well under the 16.7ms/60fps budget -- does
not reproduce the user's reported ~45fps at 720p on a high-end PC, so
the real cost is likely gameplay-load-dependent (AI/combat/particles)
or resolution/GPU-fill-rate-bound in a way idle testing doesn't
exercise. This session had no interactive input tooling to drive real
gameplay headlessly.

Ruled out in passing: the D51 vi-post debug log is throttled to
~once/sec, not a per-frame cost.

Next step needs a human: capture GE_D193=1 output during a live
session at the moment fps visibly tanks, to tell sim-side cost from
render/GL-side cost.

Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog
2026-09-13 08:54:08 -04:00
James Dansereau 1e9d8eb852 docs(D194): PR #62 playtest feedback -- hipfire good, aim-mode still too sensitive
User A/B playtest of PR #62 (fix/d194-mouse-dt-decouple, rebased onto
main this session) confirms hipfire/primary mouse-look feels right now
(dt-decouple + SOLITAIRE natural-pitch), but RMB/Shift aim-mode is
still too sensitive and not usable for precision aiming.

Static re-read of the aimHeld branch in port/src/input.c finds a likely
cause the M-107 curve fix didn't reach: AIM_STICK_MIN=61 is a hard
floor the stick snaps to on any motion past a 0.3px/poll threshold --
the aimCurveGamma response curve only shapes the range above that
floor, so tiny mouse nudges still jump straight to 10% of max aim-turn
speed instead of ramping from zero, unlike the (working) hipfire path.
Not build/runtime-verified -- a hypothesis, not a confirmed fix.

PR #62 stays held, unmerged, pending another aim-mode iteration.

Claude-Session: https://claude.ai/code/session_016DQUKGLZxFtPy52MkKXXog
2026-09-13 08:28:52 -04:00