Include mod_plugins/mod_runtime and restore master’s four-arg file picker
so the integrate←master merge builds.
Co-authored-by: Cursor <cursoragent@cursor.com>
A misconfigured overlay_autocompile_cmd silently degrades an entire session to
the interpreter. The diagnosis machinery for this already existed and was good
— autocompile_report_broken_once() fires after 3 consecutive failures and
explains exactly what to check — but it writes to fprintf(stdout), and the
shipped runtime links -mwindows: a GUI-subsystem binary with no console. The
warning is emitted into nothing on precisely the builds people run.
Observed cost: a fresh worktree ran 100% interpreted for a whole session
(runs=8 fails=8 shard_ok=0, dispatch_native=0) because game.toml named a
recompiler path the documented build recipe does not produce. The only symptom
was "it feels slow", and it invalidated a performance A/B before anyone thought
to query the counters by hand.
Three changes:
- Validate the command's path arguments at CONFIGURE time. The existing check
resolves only the first token (the interpreter); it never looked at
--recompiler, which is the argument that actually breaks — it is hardcoded
per-title and disagrees with where the recipe builds. Paths now resolve
against the child's working directory and are checked before any compile is
attempted, rather than the truth emerging after three failed spawns.
Advisory, not fatal: the runtime still runs exactly as before, just not
silently.
- Record WHY in one string, exposed as degraded/degraded_reason in
autocompile_status over the TCP debug server. Rule 3's answer rather than
more printing. Keeps the FIRST cause, which is the useful one.
- Stamp it into psx_last_run_report.json. This is the part that prevents a
repeat: a field you have to query is a field nobody queries. A degraded run
now says so in its own artifact, so "was that measurement valid?" is
answerable after the fact instead of having to be suspected.
test_autocompile_degraded (new, registered in CTest) builds the real failure
shape and asserts it is caught at configure time, that the reason names the
offending flag, that the first cause is retained, and that the reason is
escaped so it cannot break the JSON for every consumer.
Also fixes a pre-existing %d/LONG format mismatch on last_exit that this
change's argument shuffle surfaced; warnings on autocompile.c go 2 -> 1 (the
remaining array-bounds one in watch_thread is pre-existing and untouched).
NOT done here, deliberately: the underlying cause is that every title's
game.toml hardcodes a build-directory path (Tomba 2 names a worktree-relative
dir the recipe never creates; MMX6 hardcodes an absolute path into one
checkout). Fixing that means having CMake record the real recompiler location
and touching six title repos — its own change. This one makes the failure
impossible to miss.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resolve stash conflict in psx_lobby_client by keeping getpeername-based
relay rewrite with the RFC1918-aware path, and land pending rollback
hookup/runtime changes.
Reverses the default from a423bb9c. The feature stays exactly as merged and is
still opt-in from the launcher's Display panel; only the default changes.
Reasoning: "structurally safe" is not the same as "validated". Perspective UVs
cannot crack a mesh the way geometry_correction does — no vertex moves, and a
polygon without proven projection provenance just keeps the PS1's affine
interpolation — but the feature has only been looked at on ONE title and ONE
renderer (Ape Escape, OpenGL, supersampling 2). Defaulting it on quietly changed
texture mapping for every title and for Vulkan, none of which had an isolation
run. That is a departure from hardware output shipped without evidence, which is
not how the faithful floor is supposed to work.
So both [video] knobs now default off, for different reasons worth keeping
straight: geometry_correction because it is BROKEN at the coverage we can reach,
with no control anywhere; perspective_texturing because it is unvalidated beyond
one title, with a launcher control so players can turn it on.
Test now pins both defaults off and its converse case checks the direction that
actually matters — perspective_texturing alone must not drag geometry_correction
on with it, since that is precisely what the launcher checkbox does.
39/41 recompiler tests pass; mod_load_acceleration and release_zip were already
failing beforehand.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Master fixed the embed_spirv.py concurrency race independently and better
(tempfile.TemporaryDirectory cleans up on early-return error paths too), so the
rebase conflict was resolved in master's favour and our mkdtemp version
removed. This branch stays SPU-only.
Records the trap that invalidated the first Tomba 2 measurement: game.toml's
overlay_autocompile_cmd names a recompiler path, and when it is absent
autocompile fails every attempt, no shard is ever built, and the runtime
silently interprets everything. Tomba 2 expects a worktree-relative path the
documented build recipe does not produce; MMX6 hardcodes an absolute path into
the main repo that works only by luck of local layout. Check autocompile_status
before trusting any perf number from a fresh worktree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The first FMV A/B was run on a fresh worktree whose overlay shard cache was
cold AND whose autocompile could never work: game.toml points at
psxrecomp-v4/recompiler/build-t2/psxrecomp-game.exe while the documented build
recipe builds the recompiler at build-recompiler/ in the worktree root, so it
failed 8/8 with WinError 2 and the runtime silently ran interpreted
(dispatch_native = 0). The '40 fps / ~19 underruns per second' attributed to
master in the previous commit was that broken setup, not master.
Re-measured against the fully warm cache with the pre-SPU exe as a control in
the same worktree: interp_share 0.7813 vs 0.7775, exc_share 0.2386 vs 0.2381,
39.1 vs 38.0-38.4 fps. Identical within noise, so the DSP work is free.
Separately characterises the real pre-existing defect: the FMV is 96% native by
dispatch count but 78% interpreted by wall clock with 24% in exception
handling, even warm.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Measured, not asserted:
- BIOS LLE boot proves the reverb engine runs and rings down after the dry
signal stops; noise LFSR observed shifting with correct parity feedback.
- Tomba 2 intro FMV A/B against the exact parent commit shows the added
per-frame DSP work is performance-neutral (min p50 40.0 vs 40.3 fps) and
does not starve the CD bus or the host sink. The FMV's 40 fps and its
~19/s host underruns are PRE-EXISTING on master.
- Records all ten places the documentation ran out as an oracle-comparison
work list, and the two merge prerequisites (rebase onto moved master; bump
recomp-ui to 99558ee or no title compiles).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes the four gaps named in issue #103 and lands the separable, verified
parts of PR #102. See docs/internal/SPU_FIDELITY_103.md for the per-piece
disposition and the oracle-verification queue.
SPU DSP (issue #103), clean-room from hardware documentation:
- Reverb: the documented 22050 Hz engine — vLIN/vRIN input, same-side and
crossed different-side IIR reflections, 4-tap comb early echo, both
all-pass filters, vLOUT/vROUT output, and the work-area address advance.
Gated on SPUCNT bit 7 for WRITES only; reads, IRQ checks, address advance
and output always run, as on hardware. Addressing is confined to
[mBASE, 0x80000). Per-voice sends from EON, CD send from SPUCNT bits 0+2.
- Noise: 16-bit LFSR with the documented parity feedback, clocked at the
SPUCNT bits 8-13 rate; NON voices substitute it for their ADPCM sample.
- Volume sweeps: all 24 voice L/R plus main L/R. Bit 15 selects sweep vs
direct; sweep is a live envelope on the same rate machinery as ADSR, and a
guest read of a sweeping register returns the live level.
- Capture buffers: CD L/R and voice 1/3 output written to SPU RAM, so a game
that parks the IRQ address there still gets its interrupt.
- SPU IRQ (I_STAT bit 9): checked at every SPU RAM access class — FIFO, DMA
both directions, ADPCM block fetch, capture writes, reverb work-area
accesses — gated on SPUCNT bit 6, mirrored in SPUSTAT bit 6, acked by
clearing SPUCNT bit 6.
The CD-only fast path in spu_render was removed rather than extended: the
SPU now has per-frame work that must run in every path, and an FMV (XA audio
with zero active voices) is exactly the CD-with-reverb case.
This is a clean-room implementation from the psx-spx/nocash register map and
documented algorithm. Beetle's source was deliberately not consulted: it is
GPL-2.0-or-later and this project is PolyForm Noncommercial. PR #16 was
parked for exactly that reason and PR #13's reverb was separately wrong (it
gated on bit 15 of dAPF1, an address offset, instead of SPUCNT bit 7). The
one place this knowingly deviates from hardware is the 22.05 -> 44.1 kHz
reconstruction filter, which the documentation does not specify; it is
isolated in a single function and flagged for oracle comparison.
CAUSE.IP2 (from PR #102):
IP2 is combinational on R3000A — it mirrors the interrupt line and falls the
instant the guest acks I_STAT or masks the source. It was being OR'd in at
delivery and never cleared, in TWO places (the compiled path and
psx_interpreter.c, the latter missed by PR #102), leaving a phantom pending
interrupt that can spin a kernel dispatcher looping on CAUSE.IP & SR.IM.
psx_irq_refresh_cause_ip2() is now the sole writer of bit 10, refreshed at
raise, I_STAT ack, I_MASK write, HLE context restore and power-on. Verified
against the Beetle oracle's irq.cpp/cpu.cpp rather than asserted, and
covered by test_cause_ip2_combinational.
Mid-dispatch audio pump (from PR #102), with a fix: routed through a gate
that mirrors sdl_audio_update's mute/sink decision. Pumping unconditionally
from the VBlank edge, as submitted, would have pushed real audio during every
turbo-load hard mute and defeated the freeze-in-place mute model.
SPU RAM DMA readback (from PR #102): DMA4 in the SPU->RAM direction wrote
literal zeros; SPU RAM is readable memory and titles carry state through it
across an Exec boundary.
Also fixes an unrelated build race found on the way: tools/embed_spirv.py
wrote its intermediate SPIR-V to <source>.spv, so the two runtime targets
that embed the same shaders raced on one path and one deleted the file the
other was reading.
Co-Authored-By: Alexandros Mandravillis <Alexbeav@live.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A contributor bringing up a new title hit four dead-ends in a row, three of
which were the same missing step wearing different masks: `regen_bios.sh`
builds the BIOS emitter but never configures it, and nothing said so.
- "no recompiler build dir found (set PSXRECOMP_BIOS_BUILD)" pointed at an
env var when a fresh clone has no CMake tree to point it at. Both discovery
failure and an explicit override now name the actual missing step and print
the configure command.
- Setting PSXRECOMP_BIOS_BUILD skipped the CMakeCache.txt check that discovery
applies, so an unconfigured directory reached `cmake --build` and died with
cmake's bare "Error: could not load cache". Validate the override too.
- A CMake configure that FAILS still writes CMakeCache.txt — the cache lands
before the generate step, which never runs after a FATAL_ERROR. So "has a
cache" did not mean "is buildable": the directory left behind by his failed
SDL and BIOS-backend configures had a cache and no build.ninja, and the
build reported only `ninja: error: loading 'build.ninja': GetLastError() =
2`. Read the generator out of the cache, check for its build files, and say
what happened. Discovery skips such a directory and keeps looking rather
than selecting it and dying; an unrecognized generator falls through to
cmake instead of inventing a failure.
All errors now go to stderr (the old one went to stdout).
Docs: the README's framework build block configured the recompiler and jumped
straight to the runtime build, omitting the required BIOS regen between them —
exactly the hole he fell into. It is now the same numbered 1/2/3 sequence
docs/BUILDING.md already used. BUILDING.md gains troubleshooting entries for
the two CMake half-configured-directory errors, the fresh-clone "No recompiled
BIOS backend available", and PSXRECOMP_BIOS_BUILD being resolved relative to
the framework root rather than the shell's cwd.
Verified: both new failure paths and the half-configured-directory case
reproduce and now self-explain; discovery precedence (build-t2 over build),
explicit-override precedence, Ninja/Makefiles/unknown generators, and a CRLF
cache all behave; end-to-end `regen_bios.sh --config bios/OpenBIOS.toml`
still emits (Dispatch entries: 3911).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two PGXP-family knobs now default differently, on purpose.
geometry_correction stays OFF and stays out of the UI: it moves vertices, and at
the ~5% coverage a position-keyed lookup can reach (93% of lookups ambiguous), a
corrected triangle meets an uncorrected neighbour and their shared edge splits.
Partial coverage is a visible defect there, not a partial feature.
perspective_texturing has no such failure mode, which is why it can ship on. It
never moves a vertex — it only changes how UVs are interpolated inside a polygon
whose SWC2 projection provenance is already fully proven. A polygon that does not
qualify simply keeps the PS1's affine interpolation, so neighbours can never
disagree about a shared edge and coverage below 100% degrades to "some polygons
look better" rather than "meshes crack".
Layering is unchanged and the faithful path is still one key away:
[video] perspective_texturing = false in game.toml or settings.toml opts out, and
settings.toml still wins over game.toml. The launcher row seeds from this default,
so the checkbox now presents as ticked.
Test updated rather than deleted: it pins the DIFFERENCE between the two defaults,
and gains a converse case proving that naming geometry_correction alone does not
disturb perspective_texturing's new default. 38/41 recompiler tests pass; the 3
failures (mod_load_acceleration, release_zip, sdl3_main_single_include) were
already failing before this change.
Not visually validated at the new default: perspective textures were confirmed
clean on Ape Escape / OpenGL / supersampling 2 only. Vulkan and the other titles
inherit the default without an isolation run of their own.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Renamed from feat/pgxp-geometry-correction: this branch is now the shippable
line, and its headline feature is perspective-correct textures.
States plainly which of the two settings a player can reach. perspective_texturing
keeps its Display row. geometry_correction keeps its code and its config
plumbing, but has no control on any settings surface and stays default-false, so
reaching it requires hand-editing a toml. It was not excised because G1.8 asked
for it to remain testable, and a setting whose implementation has been deleted is
not testable.
Adds the standing constraint that matters more than either: do not surface it or
change its default until the census moves off 5.2% hit / 93.1% ambiguous on a
real title with the OFF control run first. At partial coverage this is not a
partial feature, it is a visible defect.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three runs on Ape Escape, same scene: off/off clean (the control, which also
clears the 92-commit framework and 64-commit UI jumps), geometry-only lines,
perspective-only clean.
perspective_texturing never moves a vertex — it only changes UV interpolation
inside a polygon with fully proven provenance — so neighbours cannot disagree
about a shared edge and nothing cracks. It is safe to ship.
geometry_correction moves vertices at ~5% coverage, so corrected and
uncorrected triangles meet along shared edges. The seams trace polygon
boundaries, confirming G1.4 and retiring G1.7's misread of the artifact as
scene-spanning lines. Not tunable; needs the full PGXP dataflow.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two problems found after G1.6 was written, both of which undercut the whole
G1.1-G1.6 narrative:
The artifacts are long straight scene-spanning lines, but the stated mechanism
(cracks between corrected and uncorrected triangles) would produce short seams
tracing mesh silhouettes. The coverage measurements are real but were fitted to
the wrong picture.
More seriously, the control was never run: it was never confirmed the lines are
absent with both toggles off on these builds. A 92-commit framework jump and a
64-commit recomp-ui jump both landed underneath this work and are equally
plausible causes.
Records the control to run first, and one concrete candidate if the feature is
implicated: precise_consumed() is skipped on the !s_raster_ok software-fallback
branch, so a stale override could reach a primitive it was never computed for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
User verified seams on Ape Escape AND Tomba 2 with the exact-table plus
ambiguity-gate build. G1.5 claimed the gate fixed the cracking; it did not, and
G1.4's original conclusion stands.
Both wrong turns came from concluding on a single observation rather than a
controlled A/B: first from native captures that structurally could not show the
defect, then from one in-game frame where the corrected ~5% happened not to
border a visible silhouette.
The gate did remove vertices inheriting a neighbour's fraction, but ~5% coverage
still mixes corrected and uncorrected triangles inside a mesh and those seams
are visible. The crack is a property of the boundary, not of the error
magnitude, so no coverage ratio short of near-total is shippable.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Wires the two [video] settings into the recomp-ui Display page: seeds ls.* from
settings.toml, writes the player's choice back, and sets the new
has_geometry_precision capability. Geometry precision is a property of the PS1
pipeline rather than of any particular disc, so every PSX title exposes it.
Pairs with recomp-ui feat/psx-geometry-precision.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
G1.4 claimed the meshes still cracked after the exact-table + ambiguity-gate
change and called it visually confirmed. That was wrong and unverified: the
user reports the same scene now clean at window resolution.
The verification was done with the TCP screenshot, which resolves native 15-bit
VRAM — and geometry correction exists only in the supersampled mirror, so a
native capture erases it and reports a clean frame regardless. The conclusion
then leaned on counters. screenshot_hires now closes that gap.
Technically this reframes the defect: ambiguity was the harm (a wrong fraction
displaces a vertex by up to a whole pixel in an arbitrary direction), while
missing coverage only leaves sub-pixel mismatches at shared edges. So coverage
now bounds the benefit rather than causing damage, and full value propagation is
an improvement path rather than a prerequisite.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
screenshot/screenshot_file read native 15-bit VRAM via gpu_display_pixel_rgb.
That is correct for faithfulness work but BLIND to any enhancement that only
exists in the supersampled mirror: at supersampling >= 2 the geometry-correction
sub-pixel vertices, SSAA edges and perspective UVs are resolved in the hi-res
surface and gone once pixels are packed back to native.
This bit hard. Native captures of Ape Escape looked clean while the user was
looking at a visibly cracked frame, so a real regression got reported as fixed
and the argument moved to counters. An instrument that cannot show the defect
is worse than no instrument.
screenshot_hires routes gr_render_display_hires — the same present path the
window uses — and falls back to the native resolve (reporting scale 1) when
there is no hi-res surface, so it always answers truthfully about real output.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tried the cheap fix and measured it rather than arguing it. With the references'
exact direct-indexed table and ambiguity gate, Ape Escape attract gives 5.2%
hits, 1.7% never-recorded, and 93.1% AMBIGUOUS — several distinct projections
rounded onto the same pixel.
So the tracking is reaching the vertices; the rounded screen position is simply
not a unique key, and that share is irreducible for any position-keyed scheme.
It also explains the severity of the first attempt: without the gate those 93%
were answered with another vertex's fraction rather than skipped.
Rules out both the bigger-cache and coverage-gate options by measurement. Only
precision travelling with the data through the CPU dataflow gives a clean
result.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The geometry side cache was an 8192-entry HASHED table with no ambiguity check.
Unrelated screen positions shared a slot, so a vertex could be handed a
different vertex's sub-pixel fraction, and eviction changed which triangles got
corrected from frame to frame — moving seams across every mesh.
Both references index this table exactly, one slot per reachable SXY
(beetle-psx vertexCache[0x800*2][0x800*2]; DuckStation 2048x2048), and gate it
on an ambiguity flag so a pixel that two DIFFERENT projections rounded to is
never used. Do the same: direct index, first writer wins, flag on a conflicting
value, allocate lazily on enable so the faithful default costs nothing, and
fail closed to the faithful path if the allocation fails.
Adds a lookup census (attempted / hit / miss_unrecorded / miss_ambiguous) on
geom_correction. With an exact table a miss_unrecorded is a genuine tracking
coverage gap rather than a cache artifact, which is what distinguishes 'the
cache was lossy' from 'the vertex path never reaches us in a matchable form'.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ape Escape A/B showed moving seams across meshes. Measured with the new
geom_correction counter: under 10% of triangles get sub-pixel positions, and
correction is all-or-nothing per triangle, so every corrected/uncorrected
neighbour pair is a seam — and because the cache is keyed on the ROUNDED
position it changes winners frame to frame, hence the jitter.
Read both vendored references (beetle-psx/pgxp, duckstation cpu_pgxp.cpp).
They shadow the whole dataflow — a value per RAM/scratchpad word plus per-GPR
and per-GTE-register shadows — and propagate it through 47 instruction classes
with a Validate() that drops stale precision. Their value-keyed vertex cache is
only a last-resort fallback, and even that is fully direct-indexed (no hash
collisions), ambiguity-gated, and perspective-disabled.
What is parked here is only that fallback, degraded to an 8192-entry hashed
table with no ambiguity check and no primary path — so a vertex can inherit a
different vertex's fraction. Records the port cost, including the point that
matters most here: these hooks are emitted C on the hot path, so they must be
gated at codegen time rather than runtime.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both [video] enhancements are silent no-ops on anything they cannot prove is
projected geometry: a vertex whose sub-pixel fraction was never cached, or a
packet whose position words lack full GTE projection provenance, just draws the
faithful way. So the enable flag alone says nothing about whether a given title
benefits, and the existing hit counters had no reader.
geom_correction reports the flag plus the free-running geometry_vertex_hits and
perspective_triangles totals, so per-title bring-up can tell an engaged
correction from an inert one before anyone argues about screenshots. Sample
twice and diff for a per-window rate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The native-wide centre-only fast path read batch x extents with a truncating
(int) cast. With geometry_correction those are fractional, and truncation
rounds a negative x TOWARD the centre — so a batch just touching the left
margin could be misclassified as fully inside the 4:3 frame and have its
wide-mirror draw skipped, leaving a one-pixel sliver unmirrored. Take floor
for the low edge and ceil for the high edge instead; whole-valued positions
(the faithful path, and every prim with the feature off) are unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Makes the parked projection/perspective work reachable and gets it into the
renderers people actually use. Addresses issue #92 (PGXP-style geometry and
texture correction).
The GTE side cache, SWC2 RAM provenance, GP0 triangle preparation and the
software-renderer consumption path already landed in 2ceaf5a ("park projection
and perspective correction", from kem0x PR #14), but were unreachable: the
generic setters gte_geometry_correction_set() / gpu_texture_correction_set()
had no production caller, and the corrected vertices went straight to
sw_set_precise_triangle(), bypassing the renderer vtable — while the default
renderer is OpenGL.
This adds:
- [video] geometry_correction and [video] perspective_texturing, in game.toml
and settings.toml, both defaulting false. Independent knobs; the launcher
seed marks them present so a save round-trips a hand-edited key instead of
dropping it. Startup names the active pair, and notes when geometry
correction is on below supersampling 2 (where it cannot be seen).
- gr_set_precise_triangle() / gr_set_perspective_triangle() on the
GpuRenderBackend vtable, so gpu.c hands the correction to whichever backend
is live rather than to the software rasterizer directly. NULL entries no-op.
- OpenGL and Vulkan consumption. Positions are already float end-to-end, so
sub-pixel vertices drop in; the readback dirty rect widens by one pixel to
cover them. Perspective UVs use a per-vertex a_q weight and clip coordinates
pre-multiplied by w = 1/q, letting the hardware's own perspective divide
interpolate a smooth UV varying beside the affine noperspective one. a_q == 0
— the default, and what every prim gets with the feature off — makes w
exactly 1.0 and selects the affine varying, so the faithful path is the
pre-feature pipeline unchanged.
The PS1-visible GTE SXY FIFO stays integer either way: guest-side screen-bounds
culling and SXY readback are untouched, and this remains visual-only.
Also fixes a pre-existing concurrent-build race found while validating: every
runtime target runs embed_spirv.py over the same shaders, and each wrote
"<shader>.spv" next to the source, so under -j N one process deleted the file
another had just written (FileNotFoundError on a shader that compiled fine).
The intermediates now go to a per-invocation temp dir.
Parked-work provenance retained; see ENHANCEMENTS.md G1.
Co-authored-by: Kareem Olim <kareemolim@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Root-caused on Tomba 1: overlay_autocompile_cmd's bare 'python' resolved
through PATH to the devkitPro MSYS2/Cygwin python (usr/bin precedes the
Windows install), which segfaults (exit 0x0B00, signal 11) under the
kill-on-close job spawn BEFORE writing a byte — so every compile failed
with '(no output captured)' and nothing pointed at the cause; the title
silently ran interpreted.
At configure time, resolve the command's first token exactly as
cmd.exe /C will (SearchPathA with .exe), print the binding once, and
warn loudly when msys-2.0.dll/cygwin1.dll sits beside the resolved
interpreter, recommending the Windows launcher form 'py -3 ...'.
Also fail loudly up front when the token doesn't resolve at all.
Doc'd in ASYNC_OVERLAY_COMPILE.md. Title game.toml fixes (python ->
py -3) ship separately in each game repo.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Salvaged from PR #13 (feat/launcher-stb-revamp, commit 2ba52e97) by
NyperYuhgard. Master fatal-halts on any DMA start for channel 5; this
moves words to/from RAM directly (device side unemulated: RAM-to-device
reads-and-discards, device-to-RAM zero-fills) and completes the
transfer with the normal IRQ path.
Note: zero-filling from an unemulated expansion device is weaker than
the fail-closed psx_fatal_halt default the other channels follow; kept
as submitted because the contributor exercised it on a real title.
Trimmed from the original hunk: unused DMA_PIO_CYCLES_PER_WORD macro
and mdec_finish_async_{in,out}_transfer wrappers nothing references.
Co-Authored-By: NyperYuhgard <noreply@github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>