Second report on #244: main.cpp references C-defined globals that MSVC
cannot resolve. MSVC mangles namespace-scope variables; the Itanium ABI
does not, so a C++ reference to a C global links on GCC/Clang and fails
under MSVC with an unresolved external.
main.cpp already has the fix pattern -- the file-scope extern "C" block
added by the earlier MSVC pass, with a comment saying exactly this. Ten
globals added since then never made it into the block:
memory.c i_stat, i_mask, g_guest_store_count,
g_vblank_ack_count
interrupts.c g_vblank_raise_count, g_vblank_deliver_count
dirty_ram_interp.c g_dirty_ram_blocks_run, g_dirty_pump_count
overlay_loader.c g_call_unit_depth
psx_bios_backend.c g_psx_dispatch_depth
Each is referenced only through a block-scope `extern` inside a function.
With no namespace-scope declaration in view, that block-scope declaration
gives the entity C++ linkage, which is the failure. Declaring them in the
file-scope extern "C" block fixes it without touching the use sites: the
block-scope redeclarations now inherit C linkage, as the block's comment
already describes for the symbols that were there.
Which ten was measured, not eyeballed. Every block-scope extern in
main.cpp (31 distinct symbols) was probed by appending
`extern "C++" { extern T sym; }` to a copy of the file compiled with the
target's real command line: a conflicting-linkage diagnostic means the
symbol already has C linkage from an extern "C" header, silence means it
does not. 21 came back already-C (declared in psx_cycles.h, cpu_state.h,
debug_server.h and friends, or already in this block); these 10 came back
C++. After the change all 31 probe as C linkage.
Audited beyond main.cpp: it is the only C++ TU in the runtime with this
pattern. beetle_libretro.cpp's `extern PS_GPU GPU` / `extern PS_CDC *PSX_CDC`
are genuine C++ symbols from the Beetle core, correctly left with C++
linkage, and psx-beetle is not built under MSVC in any case.
No behaviour change on the existing toolchains: main.cpp.o is byte-identical
to its origin/master build. Full runtime suite on a clean build dir is 47/48,
the single failure being gte_register_access_test, which fails identically at
origin/master (43/44 there) -- it links gte.cpp + pgxp.cpp but references
gpu_ws_precise_nclip_enabled, defined in gpu.c, which is not in that target.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014YD7rrVK63Kncki6prB65S
The README and docs/BUILDING.md both advertise MSVC as a supported
Windows toolchain, and the build system means it: runtime.cmake carries
/GS- /guard:cf-, /STACK, /SUBSYSTEM:WINDOWS with /ENTRY:mainCRTStartup,
an /experimental:c11atomics scoped to audio_trace.c, an SDL2 MSVC
dev-package lookup, and psx-oracle / psx-beetle are skipped under MSVC
because they are MinGW-only. psx_cycles.c already picks _BitScanReverse
over __builtin_clz, and the recompiler already emits a .CRT$XCU static
initializer instead of __attribute__((constructor)).
Two translation units regressed past that work and stop cl.exe cold.
Both files are in the runtime source list (runtime.cmake:261,267), so
this hit the framework runtime and every generated game project alike.
runtime/src/sio.c -- five GNU [0 ... PSX_MAX_PLAYERS - 1] range
designators, which MSVC has no equivalent for. Replaced with a
PSX_PAD_INIT() element-repeat macro in sio.h, next to the
PSX_MAX_PLAYERS 1..8 bound that makes the ladder finite. Each
PSX_PAD_REP_n is flat rather than recursive so no nested macro call
is involved and the expansion is the same under the conforming and
the traditional MSVC preprocessor; variadic so a braced element
survives argument splitting. Element count cannot drift from the
array extent because both are spelled PSX_MAX_PLAYERS.
runtime/src/mdec.c -- __attribute__((aligned(16))) on the two SSE2
scratch locals. The alignment is load-bearing: idct_sse2_dot8 stores
with _mm_store_si128, which faults on an under-aligned destination.
The irony is that mdec.c:9-10 gates the SSE2 path on _M_X64 || _M_AMD64
specifically so MSVC x64 takes it, and then that path used a
GCC-only attribute. Replaced with PSX_ALIGN(n) from the new
runtime/include/psx_align.h, which maps to __declspec(align(n)) on
MSVC -- prefix position being the one placement both spellings share.
Written in the style of the neighbouring psx_bss.h.
No behaviour change on the existing toolchains, and that is checked
rather than asserted: sio.o and mdec.o are byte-identical to their
pre-patch builds under both gcc and clang, and for sio.o at every
supported PSX_MAX_PLAYERS from 1 through 8. A -Wpedantic sweep over
runtime/src, which reported the five range designators before, now
reports none. psx-runtime and psx-oracle link, and the runtime ctest
suite is 43/43.
Unfixed and pre-existing, unrelated to this change: gte_register_access_test
fails to link (gte.cpp references gpu_ws_precise_nclip_enabled, defined in
gpu.c, which is not in that target's source list).
Not attempted here: nothing in CI compiles runtime/src with cl.exe, which
is why these two landed unnoticed. A windows-latest MSVC configure+build
job is the durable fix and should be its own change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014YD7rrVK63Kncki6prB65S
A plugin that draws its own overlay primitives needs to know where the
right-hand edge of the screen is, and it cannot work that out for itself.
GPUSTAT carries the horizontal-resolution bits, so a plugin can recover
the coarse mode width, but the visible width also depends on the GP1(06h)
X1/X2 range -- and that register is write-only and mirrored nowhere a
plugin can read.
Ape Escape is the worked example. It scans out 384 (368-mode, dot clock 7,
horizontal range 539..3227) while its mode width is 368, and the usual
assumption of 320 is wrong by 64. A HUD row right-aligned to 316 lands 68
pixels short of the edge, which is exactly what the Quick Gadget Select
mod does today.
psx_mod_display_width()/psx_mod_display_height() return what the presenter
uses, straight from gpu_get_display_info(). Zero means the geometry is not
established yet, and the header tells callers to skip drawing rather than
substitute a guess -- an overlay in the wrong place is worse than an
overlay that waits a frame.
mod_runtime_test covers the passthrough with a stubbed GPU, including the
384-vs-368 case that motivates it and the zero case.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The memory-card ACK path cleared I_STAT bit 9 (SPU) before raising the
SIO0 interrupt. That is cross-device interrupt ownership: an SIO event
consumed a pending SPU source, so SPU-IRQ-driven audio (Konami intro,
MGS dialogue streaming) lost its interrupt during card scans. The clear
originated as a netplay digest-parity experiment (probe ACKs landing on
i_stat_before 0x200 vs 0x000) and was never a hardware behavior.
Remove the clear. A card ACK now only raises bit 7; a pending SPU
interrupt stays pending until the guest performs the SPU/INTC
acknowledgement sequence.
Regression: sio_spu_istat_ownership_test drives the card READ preamble
with SPU pending and asserts bit 9 survives every ACK delivery (fails
before this change, passes after). The pre-existing DISABLED
sio_card_protocol_test baseline is unchanged at 147/309 failures.
(cherry picked from commit f14bd43dcb7f97acfe60e22870d9f0c6c201afb5)
Cancel the previous command's outstanding second-response interrupt when
a new CD command is accepted (psx-spx command flow; DuckStation
BeginCommand semantics). Without this, a Pause(0x09) INT2 survives into
the guest's next Setmode/Setloc/ReadN sequence and fires mid-read as a
stale COMPLETE — LIBDS drivers treat it as a read failure, corrupting
the streamed resource chain.
Cross-validated: C-12 Final Resistance mission 1 PC=0 null-callback
termination disappeared after absorbing this fix (DEVLOG 2026-08-05).
MOHU Mission 1 freeze in a DsRead FATAL/retry loop on single-sector
marker reads.
Also guard beetle SIGSTOP trap behind #ifndef _WIN32 (Windows has no
SIGSTOP).
(cherry picked from commit 2c43fc69c66242a86183156a6f1e60b2f81b1e47)
(cherry picked from commit c03fe5ba3d49241dfaedb344db6ad9c977070722)
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.
Split from parked PR #193. Adds the standalone keyboard_pad_chord_test target and verifies that default keyboard bindings emit Select, Start, and Select+Start without one key masking the other.
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.
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.
`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>
The cache was accepted forever once any cmake ran: default min version was
empty, so a Deck (or any host) that cached a pre-sysroot pack (<=1.0.13)
never re-downloaded and kept compiling against host headers — absent on
stock SteamOS — with objects that mis-link after a pack upgrade
(__isoc23_strtoul).
Both resolvers now default the floor to 1.0.14, the first pack with the
Linux build sysroot. This is a framework floor, not a per-title pin:
RETCOMM_TOOLCHAIN_MIN_VERSION still overrides in either direction, and
"0" / "off" / "none" disables it (pre-floor behavior).
- tools/toolchain_pack.py: DEFAULT_MIN_VERSION in default_min_version();
the ensure_toolchain stale-probe passes min_version="0" so its log line
still sees below-floor packs.
- host: toolchain_min_version() mirrors the same default and disable
tokens; below-floor caches now fail host_portable_cmake_ready and the
wizard downloads /releases/latest.
Verified: Python — floor/override/disable semantics, real v1.0.10 pack
rejected and v1.0.14 accepted, an isolated cache holding only v1.0.10 no
longer resolves (visible again with floor disabled), full cache resolves
to v1.0.14. C — same matrix via a unit harness incl. manifest-less dirs;
both platform branches syntax-checked.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vc7zPEnfUFMhMtJzUEruyb
(cherry picked from commit 00f0a9a9bb)
Stock SteamOS (Steam Deck) has no /usr/include and no GL dev symlinks, so
the wizard rebuild died at recomp-ui's find_package(OpenGL REQUIRED) with
"Could NOT find OpenGL (missing: OPENGL_INCLUDE_DIR)" — reported to the
user only as "psxrecomp rebuild failed (exit 1)" (recomp-ui issue #34).
The cmake-clang-v1 >=1.0.14 pack already bundles a jammy sysroot with GL
headers and a legacy libGL.so stub, but only clang.cfg used it; CMake's
find_* still searched the host.
- psxrecomp_cli: when the active pack carries sysroot/usr/include (Linux),
pass -DCMAKE_SYSROOT=<pack>/sysroot -DOpenGL_GL_PREFERENCE=LEGACY to both
the game and the emitters configure. LEGACY is required: the sysroot has
legacy libGL.so + GL/gl.h but no GLVND dev files (glx.h/libOpenGL.so/
libGLX.so), so the GLVND path cannot complete and OpenGL::GL would never
be created. Skipped when the caller already sets CMAKE_SYSROOT.
- codegen host: run_cli_posix/run_cli_win now route the child's stderr
into the progress pipe and keep a last-lines capture (CliTail); on
failure err_msg carries the most recent error-looking line — JSON
progress rows contribute their message, raw rows (compiler output,
Python tracebacks) contribute as-is. Exit-3 disc-verify text unchanged.
Verified against a stock-SteamOS simulation (bwrap: tmpfs over
/usr/include, GL dev symlinks removed) using the shipped v1.0.14 pack:
configure previously failed exactly as on the Deck; with the sysroot args
FindOpenGL resolves from the pack, the build links, and the binary runs.
CliTail paths exercised with real compact-JSON rows and raw stderr.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vc7zPEnfUFMhMtJzUEruyb
(cherry picked from commit 0bb45442f8)
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.