beetle_main.cpp has defaulted to port 4382 since 4380 began colliding
with other oracles, but its header and the psx-beetle block in
runtime/CMakeLists.txt still said 4380. beetle_debug_server.c now says
4380 is only its fallback for a port <= 0. Comments only.
A correction to 9ef516a7 ("psx-beetle: build on macOS and send large
replies whole"): its message says none of its changes affect MinGW or
Linux. The send_raw change does. On EWOULDBLOCK every platform now waits
for the socket (up to 15 s) instead of dropping the client mid-reply.
That only turns a truncated reply into a complete one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 2026-06-27 MMIO entry in FAITHFUL_TIMING_PLAN.md says the 13 prior
loops were unchanged when mmio_timer and mmio_spu were added. icache_miss
was not: the two new loops sit before it and move its loop top from
0x80010144 to 0x80010170, so each of its two misses costs one cycle more
and it reads +16 on both backends (re-measured against live psx-beetle; the
cycle_testrom README already explains the shift). The entry now carries a
dated correction, and the beetle-macos.md intro no longer points readers
at +14 as the current value.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Boot anchors (reset, the kernel copy, the first A0/B0/C0 calls, the shell)
pass within the first frames, before a TCP cyc_watch arm can land, so a
native-vs-Beetle boot comparison could not be run from the tree.
psx-runtime (debug_server.c) and psx-beetle (beetle_main.cpp) now read
PSX_CYC_WATCH ("<pc>" or "<pc>-<end>" for region mode) and PSX_CYC_WATCH_N
(default 16) at start-up and arm the watch before the first guest
instruction. The native arm shares the TCP handler's code path
(cyc_watch_arm); psx-beetle calls beetle_cyc_watch_arm. With neither
variable set nothing changes. tools/cycle_compare.py --no-arm then reads
both rings.
PSX_BEETLE_UNPACED=1 skips psx-beetle's 59.94 Hz wall-clock pacing for
headless oracle runs. Guest timing is unaffected.
docs/beetle-macos.md step 7 documents the variables and the anchor-key
difference for BIOS copy windows declared dispatch_key = "rom": the shell
is keyed natively at its ROM address (OpenBIOS 0xBFC0A500, SCPH-1001
0xBFC18000) while Beetle sees 0x80030000. Checked on macOS: the documented
example records the OpenBIOS shell entry at 5,015,670 in Beetle, the value
of the #429 land gate, and the native watch arms from boot.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
beetle-linux.md told builders to re-land several Beetle hooks by hand
because they were never exported to docs/: the rtrace and IRQ callbacks,
the guest-cycle clock, PS_CDC::PSXRecomp_GetDecodeVolume and the CDC/DMA
IRQ peeks that beetle_libretro.cpp links against, plus the SPU event
calls and the per-instruction hook the real cyc_watch now needs. Ship
them, one patch per hook, all against the 5759277b base:
- beetle_rtrace_irq_hook.patch
- beetle_guest_cycles_hook.patch
- beetle_cdc_dma_peek_hook.patch
- beetle_spu_event_hook.patch
beetle_cdcmd_trace_hook.patch was generated from a tree that already had
the wtrace block, so applying it after the wtrace patch redefined the
wtrace globals and typedefs and needed a manual fix. It is now a diff on
top of the wtrace and SIO patches; the resulting tree is unchanged.
beetle-macos.md is the macOS recipe, verified from a fresh official
clone on Apple Silicon: `git apply` of all seven patches, the osx static
build, the psx-beetle configure on current master, OpenBIOS under the
retail file names (Beetle otherwise boots its own, different OpenBIOS),
headless runs through SDL's dummy driver, and ruler #2 as the check
before trusting a build. beetle-linux.md now uses the full patch set,
notes the current configure requirements and the headless option, and
gives psx-beetle's actual default port (4382).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruler #2 no longer built from master:
- game.toml's text_size 0x800 is now rejected by the EXE parser ("a
truncated text_size must be 4 KiB page-aligned"). Use 0x2000, the
page-rounded size of the 0x1800-byte text. The icache_miss victim block
at +0x1000 is now inside the analysed range and compiled too.
- psxrecomp-game writes the text as shards (<exe>_full_NN.c), but
CMakeLists.txt still named a single <exe>_full.c. Glob the shards and
fail with the regeneration hint when there are none.
Add mkdisc.py, a pure-Python stand-in for mkpsxiso (Windows-only here):
it writes the ISO9660 boot disc as MODE2/2352 sectors with a correct
EDC/ECC and copies the license area from a disc the developer owns. The
built image stays gitignored.
README: the macOS/Linux flow (headless on both backends), and why
icache_miss reads +16 on the current ROM but +14 in the 2026-06-27 log:
the mmio loops moved the loop top from word 1 to word 0 of its cache
line, so each of its two misses refills one more word. Records the
Beetle values re-measured on macOS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
beetle_cyc_watch_* were link stubs that always answered hits=0: the
Beetle-side sampler lived in a local beetle-psx tree that never reached
docs/. tools/cycle_compare.py and tools/cycle_testrom/measure.py (ruler
#2) therefore had no oracle to compare against.
Implement the comparator here, driven by the new per-instruction PC hook
in docs/beetle_guest_cycles_hook.patch. Semantics match the native
cyc_watch in debug_server.c: an entry is taken before the anchor
instruction is fetched and carries the absolute guest cycle count of
everything before it; with an end anchor each entry is one A->B pass;
n is clamped to the native ring size (1024). The hook is installed only
while a watch is armed, so an idle oracle pays one untaken branch per
instruction.
This makes psx-beetle require the patched Beetle tree; the stubs hid
that, but the other oracle hooks (rtrace/irq/guest cycles/CDC peeks)
already needed hand-landed Beetle changes. exc_ring stays a stub: no
tool reads it and its hook was never published.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three portability fixes for the psx-beetle oracle target, none of which
changes behaviour on MinGW or Linux:
- beetle_cdc_decode_volume redeclared PSX_CDC at block scope inside an
extern "C" function. GCC accepts that; clang gives the declaration C
linkage and rejects it against psx.h's C++ declaration. psx.h already
declares PSX_CDC, so drop the redeclaration.
- The debug server's client socket is non-blocking, and send_raw dropped
the connection on the first EWOULDBLOCK. macOS starts a TCP send buffer
at 128 KB, so a large reply (read_ram of the BIOS ROM or all of RAM)
reached the client as a truncated JSON line. Wait for the socket to
drain (select, 15 s budget, as debug_server.c's send_all_blocking does)
and only drop the client on a real error.
- .gitignore had "beetle-psx/", which matches a directory but not the
symlink docs recommend when the Beetle checkout lives outside the tree.
"beetle-psx" matches both.
Also note the non-Windows make platforms in the target's comment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two failures hit while migrating a title on Windows 11:
- `audit` crashed with UnicodeEncodeError on the "->" mark (U+2192) when the
console code page can't encode it (cp1250 here). main() now reconfigures
stdout/stderr with errors="replace", so reports degrade those characters
instead of aborting.
- record_framework_pins failed. subprocess.run(["bash", ...]) lets
CreateProcess search System32 first, which starts WSL's bash; it can't see
D:/ paths, and a CRLF checkout of record_pins.sh breaks any bash anyway.
It now resolves bash via shutil.which, passes POSIX-style paths, and falls
back to the existing git rev-parse snapshot when the script can't run.
Most of this was done by Claude Opus 5.5; I reviewed and tested it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The plain present path copied the VRAM image into the swapchain with
vkCmdBlitImage, alpha included. VRAM alpha is the PSX mask/STP bit, 0 on
most pixels, and AMD's compositor honours it even with an OPAQUE
compositeAlpha: parts of the window went see-through (8x8 black/white
blocks of whatever sat behind it). Reported in #411.
vk_renderer_present_vram/_wide now draw the displayed rect with a
fullscreen triangle whose fragment shader writes alpha = 1 (new
present.vert/.frag). The pass is self-contained and created lazily; if
any of it fails to build, presents fall back to the old blit. Its render
pass ends in TRANSFER_DST_OPTIMAL so finish_present (OSD + present
barrier) is unchanged. Wide surfaces gain SAMPLED usage for it.
Checked on an RX 7900 XT: intro/logo screens render correctly, no new
validation-layer messages.
Most of this was written by Claude Opus 5.5; I reviewed and tested it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Winsock sockets are inheritable by default. Any child process the runtime
spawns (e.g. the overlay autocompile's `cmd.exe /C python compile_overlays.py`)
inherits the debug server's listening socket; if the game exits while the
child is still running, the child keeps 127.0.0.1:<debug_port> bound and the
next launch cannot bind it (clients get connection refused / talk to a dead
listener).
Observed: after closing the game mid-autocompile, `netstat -ano` showed :4370
LISTENING under the exited game's PID, held by the surviving python child;
killing the compile freed it. Fix: SetHandleInformation(HANDLE_FLAG_INHERIT, 0)
right after socket(). No behavior change on non-Windows.
This change was made with AI assistance (Claude Opus 5.5, Anthropic) and
reviewed/tested by the submitter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found with the Khronos validation layer on AMD RX 7900 XT (Windows, mingw),
where D24S8 is unavailable and choose_ds_format() picks the combined
D32_SFLOAT_S8_UINT:
- VUID-VkImageMemoryBarrier-image-03320: every view/barrier/clear on the
canonical and wide depth-stencil images named only the STENCIL aspect.
Without separateDepthStencilLayouts, a combined depth/stencil format must
be transitioned with both aspects. Added DS_ASPECT and used it for the
image views, barriers and vkCmdClearDepthStencilImage ranges.
(vkCmdClearAttachments keeps the stencil-only mask - that is legal.)
- VUID-vkCmdClearDepthStencilImage-pRanges-02659: the canonical DS image is
cleared with vkCmdClearDepthStencilImage but lacked
VK_IMAGE_USAGE_TRANSFER_DST_BIT (the wide DS images already had it).
- VUID-vkCmdCopyImageToBuffer-dstBuffer-00191: staging buffers are used for
readbacks (vkCmdCopyImageToBuffer) but were created with TRANSFER_SRC only.
Validation messages over the same 20 s boot (SmackDown 2, Vulkan):
53 -> 3 (the remaining VUID-vkQueueSubmit-pSignalSemaphores-00067 swapchain
semaphore reuse is untouched). The fix also cleared the downstream
VkImageMemoryBarrier-oldLayout-01213 and vkCmdDraw-None-09600 reports.
This change was made with AI assistance (Claude Opus 5.5, Anthropic) and
reviewed/tested by the submitter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New projects describe themselves to community lists (recomp.board et al.)
via .recomp.json at the repo root: game, system PS1, type recomp,
toolchain psxrecomp, status exploring, and original region + serial.
write_recomp_json.py takes the serial from disc_probe.json and derives
region from the Sony serial prefix, falling back to --region (which
defaults to USA and would mislabel PAL discs). It only fills missing
fields, so re-running it never clobbers a maintainer's edits.
Both gitignore templates ignored /*.json at the root, which would have
silently dropped the file from the initial commit; un-ignore it.
Closes#406
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>