Commit Graph

918 Commits

Author SHA1 Message Date
Matthew Stanley 432a44f64a input: add plugin controller presentation policies
Supersedes the global HYBRID controller-mode shape from PR #233 with a trusted plugin callback that can choose analog or digital presentation per input sample.

Keeps analog mode hardware-faithful by no longer folding physical D-pad input onto the left stick, while leaving debug injection able to steer stick-only paths.

Includes the pad/cursor probe tooling from #233 for diagnosing input doubling.
2026-08-25 20:37:49 -07:00
Matt Stanley d5b5315ea4 Merge pull request #231 from mstan/split/pr193-sdl3-audio-stream-spec
audio: report SDL3 stream input spec to runtime
2026-08-25 14:23:49 -07:00
Matt Stanley 707f839570 Merge pull request #230 from mstan/split/pr193-savestate-no-pc0
savestate: defer saves until a safe resume PC exists
2026-08-25 14:23:46 -07:00
Matthew Stanley 151588d62e audio: report SDL3 stream input spec to runtime
Split from parked PR #193 commit 42109c58. SDL3 audio streams convert from the requested stream format to the device format internally, so psx_sdl_audio_open must report the requested stream input spec back to the runtime. Reporting the device format made the runtime generate at the wrong rate before SDL resampled again.
2026-08-25 13:07:40 -07:00
tetrisgm b0424b0b4e savestate: defer saves until a safe resume PC exists
Split from parked PR #193 commit 1469550c. The mod-state preservation half is already covered on master by the framework mod staging cleanup, so this extraction keeps only the independent save-state fix: when the resume hint is 0, wait for a published block-leader PC instead of writing a state that would later resume at pc=0.
2026-08-25 13:05:23 -07:00
tetrisgm 1d32acf74f boot_state: skip unknown sections instead of failing the whole load
`default: return 0` made every savestate written by a build with one extra
section a poison pill for every build without it. Worse than unloadable: by the
time the apply loop hit the unknown tag it had already restored RAM, VRAM and
CPU state, so the refusal left a HALF-RESTORED machine that wedged or died at
PC=0 -- which presented as "loading a save crashed the whole thing", with the
restored-but-untracked VRAM showing low-res in the wreckage.

A sectioned state format exists precisely so readers can step over what they do
not know. Known-but-malformed sections still fail as before; only genuinely
unknown tags are skipped.

This also makes the states saved during the short-lived BS_SEC_TEXPACK window
load cleanly again (the extra section is now ignored), and it is the
forward-compatibility prerequisite for ever adding that section back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 13:03:17 -07:00
Matt Stanley 8943695aab Merge pull request #217 from mstan/codex/hd-texture-pack-core
runtime: add external HD texture pack core
2026-08-25 12:46:36 -07:00
Matt Stanley f294f7397a Merge pull request #226 from mstan/hotfix/linux-selfcompile
Self-compile hotfixes: offline dependency vendoring, GLVND GL, Windows/MinGW C++17, and a green ctest
2026-08-25 11:59:06 -07:00
Matt Stanley c3d7fc5f71 Merge pull request #216 from mstan/codex/game-window-width
config: honor title window width
2026-08-25 11:51:40 -07:00
Matt Stanley 33be1ac338 Merge pull request #225 from mstan/fix/overlay-region-high-loading-exe
Treat RAM below a high-loading boot EXE as overlay region
2026-08-25 11:46:16 -07:00
Matt Stanley 6c65801caa Merge pull request #222 from mstan/codex/text-xlate-fix-only-tables
text: load fix-only translation tables
2026-08-25 11:31:40 -07:00
Matt Stanley 3612b91066 Merge pull request #215 from mstan/codex/keymap-event-scancode
input: match host hotkeys by event scancode
2026-08-25 11:31:11 -07:00
Matt Stanley 45ceb81174 Merge pull request #214 from mstan/codex/screenshot-hires-byte-stride
runtime: fix high-resolution screenshot byte stride
2026-08-25 10:19:17 -07:00
Alex Vanderveen 7efc712dd3 Windows/MinGW self-build: pin cxx_std_17 and stop capturing a structured binding
Two defects that only bite a MinGW C++17 build, both still live on master.

runtime.cmake declared only `c_std_11` on the runtime target, leaving the C++
dialect to whatever the parent project happened to set. A game CMakeLists that
omits CMAKE_CXX_STANDARD therefore compiled mod_packages.cpp as a pre-17
dialect. State the runtime's real requirement instead of inheriting it.

mod_packages.cpp captured a structured binding (`for (const auto& [id, ...])`
with `id` used inside a lambda). That is a C++20 extension; clang accepts it
silently, MinGW/GCC at C++17 rejects it. Bind the map entry to ordinary locals
before the lambda.

Cherry-picked from ae9432a3 on feat/rbengine-bak, minus that commit's unrelated
lib/recomp-net submodule bump. Verified by building psx-runtime on Linux.
2026-08-25 12:24:05 -04:00
Alex Vanderveen 2bea610f87 Treat RAM below a high-loading boot EXE as overlay region
The overlay-region model assumed the boot EXE sits at the bottom of RAM:
[KERNEL_END, text_end) is main-EXE text, [text_end, RAM) is runtime-loaded
overlay code. That holds for a load_address of 0x80010000, where the text
base equals the kernel-window end and the region below the text is empty.

It is false for a boot EXE that loads HIGH and streams its gameplay code
into the RAM beneath itself. Klonoa loads a 46 KB stub at 0x80180000 and
runs its overlays from 0x10000-0x130000, so the floor landed at 0x18B000
and the entire overlay region fell below it:

  - dirty_ram_clear_image_baseline() wiped the dirty bits across
    [0x10000, 0x18B000) believing it was the clean compiled text image.
  - the dirty_ram_dispatch_inner admit heuristic requires phys >= floor,
    so a JALR into an overlay page the CD had DMA'd there (never touched
    by the CPU write hooks, so never marked dirty) was refused by the
    interpreter, fell through to psx_unknown_dispatch and fail-fast
    exit(1) — Klonoa, frame 687, target 0x80123D00.

Pin the text BASE (g_text_image_lo) alongside the floor and route the
gates through one predicate: the overlay region is both sides of the text
image, not just above it. Street Fighter Alpha 3 (0x80113B00) and
Bomberman Fantasy Race (0x8003004C) load high too.

No-op for bottom-loading games: g_text_image_lo stays at its 0x10000
default, the below-text clause is empty, and the baseline clear starts at
the same page as before.

Klonoa now runs 6874+ frames at a locked 60 fps with fail-fast on and
zero unknown dispatches; it previously died at frame 687.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit cf742f3190)
(cherry picked from commit 30e8a71f93539c4e10ff6767c44c24137c9dd714)
(cherry picked from commit 94f184f744)
2026-08-25 11:17:51 -04:00
Codex 7972b536fb text: load fix-only translation tables 2026-08-25 02:11:25 -07:00
Codex 4591c49958 timing: add rational VBlank period generator 2026-08-25 01:54:42 -07:00
Codex 4af774e913 style: normalize HD texture source endings 2026-08-25 01:50:55 -07:00
Codex 98aaea953b runtime: add external HD texture pack core 2026-08-25 01:50:23 -07:00
Codex f64ca0ce3c config: honor title window width 2026-08-25 01:45:55 -07:00
Codex 52861da621 input: match host hotkeys by event scancode 2026-08-25 01:43:25 -07:00
Codex 1d4a659ba7 runtime: fix high-resolution screenshot byte stride 2026-08-25 01:39:33 -07:00
Matt Stanley 1ea6a45066 Merge pull request #187 from mstan/split/pr169-load-accel-audio-sink
runtime: sink audio during accelerated loads
2026-08-24 22:43:41 -07:00
Matt Stanley 979e5e330c Merge pull request #204 from mstan/split/pr169-fmv-depth24-mirror-sync
gl: sync CPU mirror on 24-bit FMV entry
2026-08-24 22:27:23 -07:00
Matt Stanley 15e5eb3ead Merge pull request #205 from mstan/split/pr169-fmv-filter-user-setting-clean
presentation: add opt-in FMV filter setting
2026-08-24 20:29:11 -07:00
Matthew Stanley 15099cc5b6 presentation: add opt-in FMV filter setting 2026-08-24 20:28:43 -07:00
Alex Vanderveen ffa11fa2dc cdrom: a Read that continues the current stream must not restart the drive
Legend of Mana's land-creation effect rendered as hard-edged coloured
wedges instead of a soft bloom. The cause was in the CD-ROM controller,
not the renderer.

The game streams a file by issuing Setloc(next)+ReadN roughly 10,800
guest cycles after each sector arrives. Every ReadN called
start_read_stream(), which clears the sector buffer -- destroying the
sector that had already been delivered and announced but not yet drained
by the guest. Measured at the palette load:

  buffer      psx-runtime   DuckStation
  0x0E1F18      125111        125111
  0x0E2718      125113        125112   <- the effect's colour palette
  0x0DCF18      125113        125113

Sector 125112 carries the effect's 5-colour palette (0x0888F8 =
(248,136,8), 0xB0F8F8 = (248,248,176), verified against the disc image).
Destroyed before the guest could take it, its buffer received 125113's
raw file bytes instead, and the colour routine faithfully scaled those
into 216 distinct vertex colours -- the wedges. The table now holds 5.

DuckStation only calls BeginReading -- the function that clears the
buffers and re-arms the read-start latency -- when the request actually
moves the drive:

  if ((!setloc_pending || setloc_position.ToLBA() == GetNextSectorToBeRead())
      && (drive_state == Reading || ...))
    "Ignoring read command with pending/same setloc, already reading"
  else
    BeginReading();

A Read issued while the drive already streams the sector the pending
Setloc names is the game saying "keep going", not "start over". The
command is still ACKed with the same INT3; only the restart is skipped.

Two things fall out of it. The un-drained sector survives, so the game
receives the consecutive sectors it expects (109,110,111,112,113 rather
than 109,111,111,113,113). And each request stops paying
initial_read_delay_cycles() again: inter-sector gaps drop from ~456,000
cycles to 225,792 -- exactly the 2x cadence -- so this load takes 2 guest
frames instead of 13, matching DuckStation. int1_pended goes 10 -> 0.

Also included, developed alongside and verified in the same runs:

  - An 8-slot sector buffer ring, as the hardware has (nocash;
    DuckStation NUM_SECTOR_BUFFERS = 8). A delivered sector fills the
    NEXT slot and the INT1 that announces it latches the read slot, so a
    sector arriving mid-drain no longer clobbers the FIFO. The read
    pointer follows the slot each INT1 announces, captured when the INT1
    is RAISED: this drive free-runs, so latching "newest" at
    presentation time handed the guest a sector seven ahead of the one
    it asked for.
  - The seamless no-IRQ refill advances the read pointer, because it is
    a continuation of the drain already in progress. Without it a
    multi-sector DMA starves mid-transfer (measured: 1,128,960 starved
    drains).
  - A pended data-ready is released on a short schedule rather than
    inside the guest's ack store, matching DuckStation's
    QueueDeliverAsyncInterrupt.
  - Flow control for accelerated (non-1x) reads: never overwrite a
    sector the guest has not consumed while running faster than
    hardware, capped at the authentic sector period. Inert at authentic
    timing.

New cdrom_state counters ring_starved and ring_dropped, plus
cdrom_timing_dump for per-sector deadline/delivery records, are the
regression tripwires: ring_starved and int1_lost must stay 0.

Verified on Legend of Mana (SLUS-01013): the effect renders correctly,
the palette table holds 5 colour words, sectors arrive at the 2x
cadence, and no notification is lost.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 20:05:08 -07:00
Matt Stanley d1df20514f Merge pull request #206 from mstan/fix/pr199-headless-no-recomp-ui-setup-host
runtime: fix headless generated-game builds without recomp-ui
2026-08-24 17:26:39 -07:00
Matthew Stanley 1f5f70579e gl: sync CPU mirror on 24-bit FMV entry 2026-08-24 17:12:27 -07:00
Matthew Stanley 84a901289d mods: make bezel artwork user-selected 2026-08-24 17:03:52 -07:00
Matthew Stanley 9ba703e5ed Merge remote-tracking branch 'origin/master' into split/pr169-presentation-bezel
# Conflicts:
#	runtime/runtime.cmake
2026-08-24 15:16:40 -07:00
Matthew Stanley c8e3dac678 presentation: expose bezel artwork as a mod 2026-08-24 15:15:15 -07:00
Matt Stanley e5c721a042 Merge pull request #182 from mstan/split/pr169-overlay-autocompile-flavor
autocompile: pass overlay flavor to spawned compiler
2026-08-24 10:43:48 -07:00
Matt Stanley 6abb113c59 Merge pull request #184 from mstan/split/pr169-window-dpi-maximize
sdl: make window startup DPI-aware and maximize fit-to-display
2026-08-24 10:20:44 -07:00
Tomasz Finc 2bd2208c8b Fix psxrecomp_add_game_runtime for --no-recomp-ui headless builds
psxrecomp_add_game_runtime() (added in #110) is unusable with
PSX_RECOMP_UI=OFF and real generated game C linked — a documented,
supported config ("PSX_RECOMP_UI OFF for headless/generated builds",
BUILDING.md) that nothing currently exercises. Found while bringing up
a new title with recomp-ui intentionally left out initially.

Two independent breaks in that combination:

1. main.cpp calls psx_game_codegen_forward_if_built() from main()
   whenever PSX_HAS_GAME_CODEGEN is set (i.e. any time real game C is
   linked), but only *declares* it under the narrower RECOMP_LAUNCHER
   guard. Any no-launcher build with generated game C fails with
   "use of undeclared identifier". Introduced a precise
   PSX_HAS_CODEGEN_SETUP_HOST macro (set only when a codegen-setup
   host is actually linked) and re-guarded both the declaration and
   the call site on it instead of the mismatched pair of guards.

2. psxrecomp_codegen_host.c was unconditionally added to every game
   target's extras, but it unconditionally #includes recomp_launcher.h
   from the recomp-ui submodule. Any --no-recomp-ui build fails with
   "recomp_launcher.h file not found". Gated its inclusion on
   PSX_RECOMP_UI.

Verification:
- Built and ran a new title (headless: PSX_RECOMP_UI=OFF, real
  generated game C, no launcher) end to end: OpenBIOS -> game entry ->
  continuous gameplay, clean atexit shutdown, no crash.
- Regression-checked the one existing title that actually calls
  psxrecomp_add_game_runtime() (TombaRecomp; every other shipped title
  calls the lower-level psxrecomp_add_runtime_target directly and
  never reaches this code): built TombaRecomp's real production config
  (PSX_RECOMP_UI=ON, ENABLE_SETUP_WIZARD, recomp-ui launcher, mod
  catalog, setup-host/no-disc mode) against this branch via
  -DPSXRECOMP_ROOT override, no submodule surgery. Clean build, ran to
  the launcher with no regressions. Both changes are conditionally
  gated on flags Tomba always sets the opposite way, so this is
  expected and was confirmed empirically, not just by inspection.

Not covered by this PR: CMAKE_CXX_STANDARD 17 is also never inherited
by a game repo that include()s runtime.cmake directly (as opposed to
add_subdirectory()-ing runtime/CMakeLists.txt), which silently
compiles text_xlate.cpp's std::filesystem usage against whatever the
host compiler's default standard is. Six of seven other title repos
work around this by setting it themselves; happy to send a follow-up
if that's wanted here instead of per-repo.
2026-08-23 23:12:24 -07:00
tetrisgm c78a8511b7 present: bezel artwork behind the frame ([video] bezel)
A still image drawn behind the game, filling whatever the letterbox or
pillarbox leaves over -- the trick vertical shmups use on horizontal displays,
where the dead space carries artwork so it reads as designed rather than as a
limitation. MAME calls the same idea bezel artwork.

Drawn across the whole drawable before the game quad, which is simpler and
safer than filling two margin strips: no margin arithmetic, it covers pillarbox
and letterbox alike, and the frame paints over the centre.

The reason this is the right shape and present_edge_fill was not: edge fill
stretches one edge COLUMN of the live frame across the margin. That suits a flat
menu backdrop and turns track geometry into horizontal smears, so it needs to
know whether the current frame is a menu -- a question it answers with
force_4_3, which is only ever set by the widescreen path. A still image has no
such question. It looks the same in a race and in a menu, which is the point.

Loaded after the GL context exists (the first attempt sat next to tex_pack_init,
which runs before the window, so g_gl_active was false and it silently never
loaded). Decoded with stb_image, already linked for the HD texture pack. A
relative path resolves against the disc directory, matching the pack convention,
so artwork can sit beside the disc.

Verified on a 7680-wide maximised window with 4:3 content: margins that were
pure black now carry the image on both sides, with the frame untouched in the
middle.

Known limits: the image is stretched to the drawable, so an aspect that does not
match the window will distort (cover-crop is a small follow-up), and this has
only been exercised in 4:3 pillarbox, not with the widescreen mod active.

For WipEout 3 the team wallpapers by MotorcycleEmptiness
(awesome-wipeout.github.io) suit this exactly; the shipped default is a
placeholder gradient used to verify the path, not final art.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 13:33:58 -07:00
Matthew Stanley 1391dedb0c runtime: sink audio during accelerated loads 2026-08-23 13:22:55 -07:00
tetrisgm 16b439876c sdl: maximise the window instead of computing the frame size
clamp_window_aspect fits the CLIENT area to the usable bounds, but a window is
client plus title bar and borders. On a display whose usable height it exactly
matched, the result was a window taller than the screen: client 5504x4128,
window 5550x4263, positioned at y = -112 and hanging off the top.

Deriving the decoration size first does not work either. SDL_GetWindowBordersSize
reports nothing useful before the window has been shown, so a post-creation
correction based on it silently did not apply -- the window stayed exactly as
wrong, which is worse than not trying.

The window manager already solves this exactly, so ask it: SDL_MaximizeWindow
fits the work area with decorations and taskbar included. Measured on a
7680x4320 panel, client area is now 7680x4039 against 5504x4128 before.

Only when the width was not explicitly chosen -- an explicit window_width is a
deliberate decision and is left alone.

Suggested by the user after two failed attempts at doing the arithmetic here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit d6d0946703)
2026-08-23 13:17:48 -07:00
tetrisgm 9ad208c34a sdl: declare per-monitor DPI awareness before SDL_Init
Without it Windows virtualises everything the process sees. On a 7680x4320
panel at 400% scaling SDL_GetDisplayUsableBounds reports 1920x1032, so
clamp_window_aspect fits the window to roughly 1376 LOGICAL pixels and it opens
as a small box that the desktop compositor then upscales.

The internal render resolution is unaffected, which is what makes this
expensive rather than cosmetic: the game renders at supersampling 16 and the
result is thrown away scaling a 1376-wide window up to an 8K display. Choosing a
high internal resolution and getting a small blurry window is the exact opposite
of the intent.

permonitorv2 makes SDL report physical pixels, so the window sizes against the
real panel and the drawable matches it 1:1. Set before both SDL_Init call sites,
guarded on the hints existing so it builds against older SDL.

Also changes g_video_win_w's default from a hardcoded 1280 to 0, meaning "fit
the display", clamped to the usable bounds and the configured aspect. An
explicit width still wins. 1280 was a reasonable default when a window was a
window; it is not one on a 4K or 8K panel.

Diagnosed after reporting the display as 1920x1080 from a non-DPI-aware query
and telling the user their panel could not be larger. It is 7680x4320; the
measurement was virtualised, not the display.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit 236778ed7b)
2026-08-23 13:17:09 -07:00
tetrisgm 063b5210d9 autocompile: pin the overlay flavor through the spawn env (PSX_OVERLAY_FLAVOR)
The runtime pins the cache dir and captures path into the spawned
compile's environment so the cache can never drift from where the loader
reads - but not the FLAVOR. compile_overlays.py's --flavor defaults to 0
(play), so an instrumented (flavor 2) runtime spawned compiles whose
shards its own loader then rejected: the overlay_loader_status message
said it plainly - "this build reads ..._f2 but shards exist under ..._f0
-> ALL overlays run INTERPRETED". On mod-heavy titles (WipEout 3's
framerate packages patch most of the game text) that is the difference
between full speed and ~0.2x, misread as general slowness for weeks.

The runtime now exports PSX_OVERLAY_FLAVOR alongside the other pins, and
the tool honors it over --flavor, so each build population self-heals.

Measured after the fix (instrumented, WipEout 3 ntsc120full8): shard
loads 0 -> 8 (45 funcs), interpreted insns 6.6M/s -> 1.0M/s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 45f9d0f866)
2026-08-23 13:15:48 -07:00
tetrisgm e301a57279 debug: expose savestate completion receipt
(cherry picked from commit c7a3d59e1a)
2026-08-23 13:12:47 -07:00
Matthew Stanley 2cbbbfc4da observability: window traps that read as results, and a per-game watch list
Three related defects in the interp observability rings. All three share a
failure mode that is worse than an error: the tool reports an empty result
that is indistinguishable from a real finding. "total: 0" reads as proof the
events never happened.

1. callret_watch armed on `lo != 0`, making address 0 an off switch rather
   than a legal window floor. `lo=0 hi=0x200000` — the obvious spelling of
   "watch the whole address space" — replied ok and recorded nothing.
   Measured on one build, identical but for the floor:

       lo=0x00000000 hi=0x00200000  ->  total 0
       lo=0x00000004 hi=0x00200000  ->  total 468732

   Four bytes of window between an empty ring and half a million call
   resolutions. This cost real time during beads-eio.3.59: an
   interp->compiled call route was briefly believed absent when the ring was
   simply never on.

   Arm on a NON-EMPTY window (hi > lo) instead. `lo=0` is now an ordinary
   floor. Note that `{"lo":"0"}` was the DOCUMENTED disarm spelling
   (TCP_COMMANDS.md), so that contract is kept explicitly rather than broken:
   lo=0 with no hi still disarms, and now says so in the reply. Added
   `disarm=true` as the unambiguous spelling. A `lo` with no `hi` is refused
   outright instead of silently inheriting the previous ceiling (usually 0,
   i.e. an empty window) — that was the second way to arm nothing by accident.

2. s3_smear_watch had the same `lo`-nonzero gate, so lo=0 with a real hi
   recorded nothing while answering ok. Same fix. It already cleared `hi`
   whenever `hi` was omitted, so `{"lo":"0"}` naturally lands on an empty
   window and still disarms — no special case needed there.

   Both commands now report `armed`, and callret_watch's dump carries `armed`
   plus the active window, so an empty read can always be told apart from an
   unarmed one.

3. The xprobe JAL/JALR watched-target list was six guest addresses from two
   specific titles COMPILED INTO the shared runtime (an MMX6 card-load flow
   and a Tomba target). Every other game therefore watched nothing, silently:
   the `watched` dump came back empty and gave no hint that the build was
   incapable of watching anything. Same defect class as beads-eio.3.21, and
   the direct reason the JAL call site was unusable while measuring
   beads-eio.3.59 — only the JALR callret ring could be used there.

   The list is now configured, never compiled in: `xprobe_watch` TCP command
   to get/set, or PSX_XPROBE_WATCH in the environment so targets are watched
   from instruction zero without a rebuild. Default EMPTY — no title is
   privileged — and a read with nothing watched says so rather than looking
   like a negative result. Matched on the normalised address so KSEG and
   physical forms both work.

Verified against Ape Escape, headless, RelWithDebInfo:
  - PSX_XPROBE_WATCH=0x8002C0E4 -> watching that target from boot
  - xprobe_watch targets=<two addrs> -> count 2; the `watched` dump then
    carries 41 JALR records for 0x8002C0E4 and 21 for 0x8002C14C, with full
    frame/cycle/src/sp/ra context. This title could previously watch NOTHING.
  - xprobe_watch targets= (empty) -> count 0 plus an explanatory note
  - callret_watch lo=0 hi=0x200000 -> armed true, total 77484
  - callret_watch lo=0            -> disarmed, legacy spelling named
  - callret_watch lo=0x40000      -> refused, window left unchanged
  - callret_watch disarm=1        -> disarmed
  - s3_smear_watch lo=0 hi=0x200000 -> armed true (previously silent)
  - pre-fix build, callret lo=0 hi=0x200000 -> plain ok, total 0 (the trap)

TCP_COMMANDS.md regenerated for the new command. Note the regen also picks
up rows that had drifted on master beforehand: `--check` already failed on
pristine master (index said 292, the servers register 304), which is why the
count line jumps 292 -> 305 rather than by one.
2026-08-23 11:57:33 -07:00
Matt Stanley 13cf125554 Merge pull request #170 from ClaudioVarandas/fix/spu-pitch-zero-holds
fix (spu): a pitch of 0 holds the voice instead of playing at full speed
2026-08-22 00:59:22 -07:00
Claudio Varandas 69da235063 spu: a pitch of 0 holds the voice instead of playing at full speed
VxPITCH is added to a 12-bit fractional phase accumulator, so a pitch of zero
means the sample counter does not advance and the voice holds its current
sample. spu.c coerced zero to 0x1000 instead -- full-rate 44.1 kHz playback --
so a voice the game had parked streamed forward through SPU RAM, decoding
whatever followed it as ADPCM. DuckStation and Beetle both just add the pitch
to the counter, so zero holds there.

Alien Resurrection (SLUS-00633) parks its ambience when the pause or save menu
opens: pitch 0, envelope left frozen mid-sustain, no key-off. Measured in the
menu, both voices of the hard-panned ambience pair read pitch 0x0000, env
0x5FFF in sustain, zero key-ons over five seconds -- and cur_addr at 0x49160
and 0x49210 against a repeat address of 0x489E0, about 0x790 bytes past their
own loop point and still advancing. A voice with pitch 0 that advances at all
is the defect. The menus turned into a continuous mid-band buzz, 79-82% of the
output energy between 500 Hz and 2 kHz, which is what garbage ADPCM sounds
like. Removing the coercion silences them; verified in play.

The audio pipeline was healthy throughout -- zero underruns, zero overflow
drops, buffer at target -- because nothing was wrong with it. The SPU rendered
faithfully what it had been told to render.

Safe by construction: with phase not advancing the block-step loop simply does
not run, adsr_run still steps the envelope every output sample, and a later
key-off still releases and deactivates the voice.

The line dates to 9b332ba, the original SPU implementation. Nothing documents
it, accuracy/axis5_spu.md lists pitch as implemented without mentioning it, and
no test covers a pitch of zero.
2026-08-21 23:21:42 +01:00
Matt Stanley 1dc58357a9 Merge pull request #167 from Alexbeav/fix/full-range-straightline-handoff
fix(runtime): validate full AOT entry before straight-line handoff
2026-08-20 20:44:17 -07:00
Matt Stanley 60465617a2 Merge pull request #166 from rickguedes/fix/spu-voice-address-alignment
spu: voice start/repeat addresses ignore bit0 (16-byte-aligned fetch)
2026-08-20 15:18:41 -07:00
Matt Stanley 43fb799c3f Merge pull request #163 from rickguedes/fix/load-delay-pgxp-scope
autocompile: PGXP load-delay shard fix, host-time capture pacing, wedge visibility
2026-08-20 14:29:11 -07:00
Matt Stanley d56bd0247c Merge pull request #165 from rickguedes/fix/cdrom-cdda-report-irq
cdrom: CD-DA position report interrupts (Setmode bit2) during Play
2026-08-20 11:48:01 -07:00
Alexandros Mandravillis 73eb746479 fix(runtime): validate full AOT entry before straight-line handoff
The dirty-RAM interpreter reused a suffix-aware validity check when resuming generated code after a patched call site. If an entry prologue had been overwritten but its resume suffix still matched, execution could enter stale AOT code and fall through to another generated piece without redispatch.

Emit a whole-entry predicate for straight-line handoffs, retain suffix checks for explicit guest transfers, and fail closed for older generated dispatchers until regeneration.

Validated with the MGS PAL 6,875-frame route, manual codec-to-heliport gameplay, focused continuation-guard tests, and a current-upstream recompiler build.
2026-08-20 14:51:26 +03:00
Matt Stanley 95366a6504 Merge pull request #164 from rickguedes/fix/overlay-close-input-bleed
runtime: swallow the overlay-closing press so it doesn't bleed into the game
2026-08-19 22:13:38 -07:00