Commit Graph

518 Commits

Author SHA1 Message Date
Matthew Stanley a95d8c77ee Merge remote-tracking branch 'origin/master' into feat/guarded-packed-wide-cull 2026-10-02 16:51:02 -07:00
Matthew Stanley 1ed0dfd22d Recover saturated winding at guarded native-wide rendering branches 2026-10-02 16:47:59 -07:00
Matt Stanley 973d93a907 Merge pull request #456 from Alexbeav/up/bios-scph5500-5501-2
bios: add the SCPH-5500 and SCPH-5501 profiles
2026-10-02 15:35:49 -07:00
Matt Stanley 7f72d7ed7b Merge pull request #452 from Alexbeav/up/changethread-self-clears-pc
traps: clear pc after a same-thread changethread
2026-10-02 15:35:45 -07:00
Matt Stanley bbb669d9f9 Merge pull request #462 from Alexbeav/up/tcc-module-preamble-include
tools: give tcc the .c.inc fragments a bios module includes
2026-10-02 15:35:29 -07:00
Matthew Stanley c29e934308 Add guarded packed-coordinate culling for native-wide adapters 2026-10-02 14:12:20 -07:00
Matthew Stanley 2163035e9a netplay local viewport: projection renderer, hi-res half present, state gate
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 16:02:08 -07:00
Matt Stanley f60aae2159 Merge pull request #465 from RetroPortingToolKit/feat/ws-auto-ui-in-place
widescreen: auto-UI in-place anchoring, untextured HUD quads, backing panels
2026-10-01 14:45:48 -07:00
Matt Stanley 6347bb49fd Merge pull request #466 from RetroPortingToolKit/fix/migrate-probe-preserves-config
project studio: probe_disc_refresh merges identity, never overwrites game.toml/seeds
2026-10-01 14:45:44 -07:00
Matthew Stanley 354e3d79a4 project studio: probe_disc_refresh merges disc identity instead of overwriting game.toml and seeds
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 11:39:00 -07:00
Matthew Stanley 85c88d269c widescreen: auto_ui_anchor = "in_place" squashes each HUD run about its own centre
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 11:13:30 -07:00
Matthew Stanley eb65bf96aa tests: pin rebuild mtime clamp without lutimes and --cmake-extra precedence
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 11:02:33 -07:00
Alexandros Mandravillis 93ece71c8c tools: give tcc the .c.inc fragments a bios module includes
A BIOS module's glue.c includes overlay_dispatch_preamble.c.inc and psx_bios_module_glue.c.inc by name. The tcc path compiles against a BOM-stripped copy of the include directory, and that copy held only *.h, so every tcc module build stopped at "include file 'overlay_dispatch_preamble.c.inc' not found" and the runtime fell back to the bundled BIOS.

Copy .inc files into the tcc include copy as well. Game overlay shards were not affected: compile_overlays inlines the preamble text instead of including it.
2026-10-01 11:36:09 +03:00
Alexandros Mandravillis ff4be21c90 bios: add the SCPH-5500 and SCPH-5501 profiles
The v3.0 Japanese (SCPH-5500, 09/09/96) and US (SCPH-5501, 11/18/96)
retail BIOS images join SCPH-1001, SCPH-101 and SCPH-5552, so every v3.0
region (J, A, E) has a profile.

Both images match SCPH-1001 v2.2 byte for byte in the kernel,
ROM [0x80, 0x18000). SCPH-5501's reset vector is identical too; SCPH-5500's
differs in 24 bytes. The shell differs in both. So each profile is
SCPH1001.toml with its own identity (name, id, rom, sha256 pin, stem): the
copy windows and install slots are kernel properties and carry over
unchanged. The shell runs through the dirty-RAM interpreter, as for
SCPH5552.

Each image gets its own seed list, derived by the same rule as
phase2_ghidra_seeds_SCPH5552.json: the SCPH-1001 corpus filtered to seeds
whose 64-byte ROM window is identical in both images (534 kept, 569
dropped), plus the two architectural vectors and 6 A0/B0/C0 kernel-table
targets. The files carry only addresses, labels and rationales, plus the
SHA-256 of both ROMs.

- psx_bios_known_images.h: CRC-32 FF3EEB8C (SCPH-5500) and 8D8CB7E4
  (SCPH-5501), both 512 KiB. A build that does not link these backends
  builds one from the player's own dump as a loadable module, like
  SCPH-5552, and the netplay lobby can name the image.
- main_cli.cpp: map SCPH5500 / SCPH5501 dumps to their profiles.
- build_cli.py: package both profiles with the CLI framework.
- docs/BIOS_SELECTION.md: list them among the module-built images.
2026-10-01 09:28:13 +03:00
Matthew Stanley 7c7133fbb4 Integrate MMX6 adaptive renderer with current framework master
Preserve current segmented dispatch, active-plan mod hooks and software scale limits. Extend function filters through the active plan, retain the last-store pointer with overlay ABI 25, and keep default config fingerprints stable. Refresh BIOS emitter stamps after byte-identical regeneration and reconcile regression fixtures.
2026-09-30 20:23:16 -07:00
Matthew Stanley 9ea70f3566 Merge current master while retaining the Ape Escape patch release pin 2026-09-30 19:09:45 -07:00
Matthew Stanley 6901aac851 Discover scheduled overlay prologues through their first basic block 2026-09-30 18:58:25 -07:00
Alex Vanderveen 60919f5a87 bios: build a retail backend from the player's own dump as a loadable module
Bundled releases link OpenBIOS only; retail-BIOS-derived C never ships.
"Bring your own BIOS" now works without it: when a dump matching a shipped
profile (psx_bios_known_images.h) is selected, the runtime builds that
backend on the player's machine with the overlay toolchain the release
already carries, caches it, and loads it as one more registered backend.

- runtime/include/psx_bios_module.h: the module contract. An overlay-style
  self-contained shared library (no imports from the executable) exporting
  psx_bios_module_{abi,codegen_hash,init,backend}; reaches the runtime only
  through OverlayCallbacks plus a new PsxBiosModuleCallbacks table (traps,
  dirty-RAM/kernel-bless probes, fntrace, log_probe) and pointer aliases of
  the globals the generated dispatcher touches. Its #defines rewrite the
  emitter's own extern lines, so the generated C compiles unchanged.
- runtime/include/psx_bios_module_glue.c.inc: module-side forwarders and the
  four exports, compiled after overlay_dispatch_preamble.c.inc.
- runtime/src/psx_bios_module.c: host side. Cache path
  <exe>/cache/bios/<os-arch>/bm<abi>_<codegen hash>_f<flavor>/<STEM>_<crc>,
  ABI + codegen-hash gated load, callback fill, and a blocking build via
  overlay_toolchain/bios_module_build.py (gcc via compiler.txt or PATH,
  else tcc), then psx_bios_register().
- psx_bios_backend.c: the registry is now a mutable table seeded from the
  CMake-generated psx_bios_builtin_registry by a constructor, with
  psx_bios_register(); every existing walker keeps working.
- main.cpp: validate_bios_for_launch acquires a module (announcing a first
  build) before declaring a mismatch; discovery accepts a buildable dump;
  the launcher verify reports it usable ("ready" / "compiled on first
  launch") with no Generate & rebuild; the BIOS row shows whenever the
  toolchain is present; bundled_only respects module support.
- emitter: the CPS-marker constructor is not emitted in a module build.
- overlay_api.h: OverlayCallbacks gains a struct tag;
  overlay_loader_callbacks() exposes the host table before loader init.
- tools/bios_module_build.py (ships in the toolchain): identity + profile
  SHA-256 gate, staged emitter tree, emit, three-TU compile, ctypes
  self-check, atomic publish. release_stage.py stages psxrecomp-bios, every
  bios/*.toml, the seeds and the builder into overlay_toolchain/.
- Tests: runtime/tests/test_bios_module_host.c against three fake modules
  (good / wrong ABI / wrong hash) on real dlopen; recompiler/tests/
  test_bios_module_build.py builds the bundled OpenBIOS end to end and reads
  the descriptor back; guards extended.

Verified on Twisted Metal 4 (bundled build + staged toolchain, headless):
first launch with --bios SCPH1001.BIN builds and loads the module and boots
on image=SCPH-1001; the second launch loads it from cache.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-30 19:26:29 -04:00
Matthew Stanley e63a23b74c Merge latest master for segment E; preserve completed IsC accuracy notes 2026-09-30 15:06:59 -07:00
Matthew Stanley 4717aacd63 Merge latest master for segment D; retain both landed accuracy fixes 2026-09-30 15:05:40 -07:00
Matthew Stanley 913822f358 Merge latest master for segment C; retain IsC notes and bundled-release test 2026-09-30 15:04:16 -07:00
Matt Stanley 1b99be0b8e Merge pull request #436 from RetroPortingToolKit/refactor/emitter-runtime-pc
Segment-aware code B: route every baked guest PC through runtime_pc()
2026-09-30 15:02:46 -07:00
Matthew Stanley 6534b3ddad Merge latest master for segment design; preserve landed IsC and segment notes 2026-09-30 15:02:17 -07:00
Matthew Stanley 962d949f1e Merge remote-tracking branch 'origin/master' into fix/framework-tests-followup-20260930
# Conflicts:
#	runtime/tests/test_packagers_never_format_cache_tag.py
2026-09-30 14:27:29 -07:00
Alex Vanderveen a7c0db2c63 tests: register test_bundled_release_layout with CTest
check_test_registration.cmake fails configure for an unregistered test
file, which broke every title's emitter build at the new pin.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-30 17:15:01 -04:00
Matthew Stanley 50e292748b tests: keep synthetic DLL loader failures noninteractive on Windows 2026-09-30 14:07:37 -07:00
Matthew Stanley f77e7a570c test: read generated overlay C fixtures byte-preservingly 2026-09-30 13:00:11 -07:00
Alexandros Mandravillis 8f8325a2fe traps: clear pc after a same-thread changethread
With the HLE scheduler (the default), ChangeThread to the thread that is
already running returns 1 from psx_request_thread_switch. psx_syscall case 3
then returned 0 with cpu->pc still holding the SYSCALL's own address.
Compiled callers fall through on 0, but the dirty-RAM interpreter transfers
whenever cpu->pc is non-zero, so it re-executed the SYSCALL forever.

A game that yields from its main thread this way hung at the kernel's
SYSCALL (EPC 0x650) as soon as that kernel code ran interpreted, for
example when its body did not match the compiled kernel.

Clear cpu->pc on that continuation, as the SYS01/02 continuations do. The
guest-exception syscall path returns before case 3 and is unchanged.

syscall_change_thread_self links the production traps.c into a fixture
with a one-thread TCB table at -O0 and -O2. fixture_link.py stubs every
other runtime seam with abort(), so an unexpected call fails the test.
2026-09-30 22:14:24 +03:00
Matt Stanley c604cea4d5 Merge pull request #432 from Cyanoxide/feat/configurable-depth24-trailing-margin
Make FMV depth24 trailing margin configurable per title
2026-09-30 11:09:04 -07:00
Shokunin fdc354543d tools, tests: the segment probe loads an overlay; ledger guard overlay-segment-shards
docs/SEGMENT_AWARE_CODE.md §5.7, §7. The synthetic probe gains an overlay
phase that loads code the way a game loads an overlay. After the three
probe_run sequences it:
- leaves the critical section the BIOS started it in, because the kernel's
  CD driver waits on CD interrupts;
- reads its own EXE file back from the disc through the BIOS file calls
  (B0:32 open, B0:34 read, B0:36 close; the kernel DMAs the sectors) into
  RAM at 0x000A0000;
- calls the copy of a position-independent ov_run there through KUSEG,
  KSEG0 and KSEG1, timing each run with Timer 2.

It records the T2 deltas and ov_getpc links at R+0x28-R+0x3C, and the file
descriptor and byte count at R+0x40/0x44. In the runtime that code is
CD-DMA'd RAM code. Capture records its entries with their segments, and
compile_overlays.py builds a shard per segment.

The phase replaces the `j .` spin with a `j` of the same size, so every
earlier label keeps its address. The static probe code and every result
word it wrote before are unchanged, and so are its probe_run cycle-watch
hits on Beetle and native. Beetle on both BIOSes: T2 56 / 56 / 82 and links
0x000A0A2C / 0x800A0A2C / 0xA00A0A2C, the same shape as probe_run.

The ledger gains the regression guard overlay-segment-shards (§5.7). It
builds the capture record overlay_capture.c writes for the probe's
disc-loaded ov_run (schema v3), splits it with capture_segment_views(), and
compiles each view as the fragment pass does, with compile_overlays.py's own
helpers and the real recompiler. It requires:
- every PC each shard bakes, and its names, to be in its segment;
- the manifest's S and F records;
- ov_run's straight run to cost Beetle's fetch-model cycles: line leaders
  cached at KUSEG and KSEG0, +4 per fetch at KSEG1.

KNOWN_GAPS stays empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:43:46 -07:00
Shokunin eda98a9973 recompiler: overlay views move config code sites into their segment
A capture view is compiled at `segment | phys` in whichever segment its
entries ran (docs/SEGMENT_AWARE_CODE.md §5.7), but the recompiler matched
the game config's exact-match code sites as spelled. The config names
overlay code by its bytes, which run in any segment. The runtime keys mod
function-entry hooks by physical address, and the interpreter fires them at
every entry, whatever its segment. So a KUSEG or KSEG1 shard of a hooked
overlay function emitted no psx_mod_function_entry call: the hook fired on a
cold cache (interpreted) and not on a warm one (the new shard).

overlay_codegen_config() (segment_variants.cpp) moves every exact-match
site kind that D's rebase_codegen_config() moves, and whose physical address
lies in the image in a segment that maps RAM, into the view's segment. It
shares the site list with rebase_codegen_config(), which keeps its
link-segment match and its split pre-pass setting. main_psx applies it in
overlay mode. A KSEG0 view with KSEG0-spelled sites, as every pre-§5.7
overlay compile has, is unchanged.

Tests:
- segment_variants_test checks overlay_codegen_config() in each segment's
  view: sites spelled in any RAM segment move, and addresses outside the
  image, KSEG2 and 0x20000000-0x7FFFFFFF spellings and the physically
  matched kinds stay.
- overlay_segment_keys compiles a v3 capture entered at all three segments
  with mod_function_entry_funcs spelled at KSEG0 and KSEG1. Every segment's
  shard must emit both hooks at its own PCs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:43:46 -07:00
Shokunin fa05858e24 tools: compile one overlay shard per segment with entries
docs/SEGMENT_AWARE_CODE.md §5.7. compile_overlays.py splits each capture
into one view per segment with entries (capture_segment_views). A v2
capture has no dispatch_entry_segments and comes back unchanged, KSEG0 only;
in v3, a dispatch entry that no segment list names is a KSEG0 entry. A view
spells load_addr and every PC in its segment, holds only that segment's
dispatch entries, and carries all the execution evidence, which has no
segment. A malformed segment record fails that capture loudly.

Two kinds of record also get a view where they saw no dispatch entry:
- a record with no dispatch entry in any segment (execution evidence only,
  which the runtime writes) is one KSEG0 view, exactly as a v2 reader reads
  it, so classification, forced interiors, declared entries and prior
  manifests apply as before;
- declared_view_segments(): a --force-interior PC inside the record (KSEG0)
  asks for the KSEG0 view, and a game.toml [[overlays]] table asks for the
  segment its load address, or else each of its entries inside the record,
  is spelled in.

Each view compiles like any capture, region and fragment passes alike:
- the image is compiled at `segment | phys`, so the recompiler names and
  bakes it in that segment;
- helpers that spell PCs without an image address use the view's segment
  (image_segment(); KSEG0 by default, so a KSEG0 compile is unchanged);
- the shard lands in segment_cache_dir(): the tag directory for KSEG0,
  seg-kuseg/ and seg-kseg1/ otherwise.

The manifest writer adds `S <segment>` for KUSEG/KSEG1. The runtime-contract
parser keeps F entries' full VA, applies the S rules, and refuses a
manifest whose segment differs from its directory's. Exports are looked up
by their full VA.

Capacity accounting counts each leaf's segment directories in the leaf's
namespace, since the runtime's candidate table is shared, and tells shards
apart by segment and name. The static generator emits the exact PC per row,
so KUSEG, KSEG0 and KSEG1 rows of one word coexist, with a segment gate
over the segments it compiled. A KSEG0-only table emits the same text as
before. Non-KSEG0 static namespaces are ov_s0_ (KUSEG) and ov_s5_ (KSEG1).
The static isolated-fragment pass keys its images by segment too, so each
(entry, image) demand compiles against its own segment's view and one entry
dispatched in two segments gets a fragment in each. --force-interior PCs
stay KSEG0 and force KSEG0 views.

coverage_vault.py unions dispatch_entry_segments when it merges variants,
making a v2 record's entries explicit KSEG0 entries, and crops it when it
compacts regions.

Output for v2 captures is unchanged: the 52 R4 shard sources and manifests
compiled from D's race captures are byte-identical to D's.

Tests:
- new overlay_segment_keys (CTest). It runs capture views, manifests and the
  cache layout, then the real compile_overlays.py driver, in process, on a v3
  capture of an overlay entered at KUSEG and KSEG1, and checks:
  - each shard lands in its directory with S and full-VA F records;
  - every baked PC is in its segment, and KSEG1 charges a fetch per
    instruction;
  - a second run is served from each segment's cache;
  - a KSEG0 view compiles byte-identically to the same bytes captured as v2;
  - --static emits per-segment rows and namespaces, and an isolated
    fragment per segment for an entry with no callable boundary;
  - a dispatch-less v3 record with --force-interior compiles byte-identically
    to its v2 reading, and a KUSEG-only record still gets its forced KSEG0
    fragment;
  - a KUSEG/KSEG1 view compiled into a cache holding a KSEG0 manifest of the
    same bytes merges none of it, and the empty-primary reconciliation counts
    only the view's own segment's fragments;
  - the real loader runs the shards: links, store-PC stamps and fetch tags in
    each segment, fetch cycles equal to Beetle's model, KSEG0 interpreted.
- coverage_vault: segment unions and crops.
- compile_overlays_static_split: a KUSEG row beside a KSEG0 row dispatches
  by exact PC; unmapped-segment entries are refused. This replaces the
  KSEG0-only rejection.
- aot_overlay_discovery: the capacity namespace lists the segment
  directories.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:43:46 -07:00
Shokunin a0ae0c4f6a runtime, recompiler: compare call-contract return PCs exactly
psx_call_contract and the BIOS dispatch loop compared a return PC with the
call's link physically ((a ^ b) & 0x1FFFFFFF). A PC carries a segment, and
the link a call writes is in the segment of the body that made it
(docs/SEGMENT_AWARE_CODE.md §5.5). A guest that returns to another
segment's alias of the call site did not return to that body, so letting
the suspended C continuation run there would execute it with the wrong
segment's PCs. §5.5 deferred these checks to this PR so one call contract
has one rule. All five are exact now:
- psx_call_contract (cpu_state.h): the bail-resolve PC and the $ra check;
- the emitted BIOS dispatch loop: the return boundary, the bail resolve, the
  wild-return test and the exception-stack straddle.

Since B routes every baked link through runtime_pc() and C/D compile each
body for the segment it runs in, a legitimate return always matches in
full. The ledger gains the exact-return-contract guard: it compiles
psx_call_contract and checks that another segment's alias starts a bail and
resolves nothing, and that the emitted OpenBIOS dispatch has the four exact
checks and no masked one.

OpenBIOS and SCPH-1001 dispatch C change in those four lines; game C does
not change. cpu_state.h is in the codegen hash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 00:24:07 -07:00
Shokunin 94be8f6386 recompiler, runtime: compile BIOS segment variants; SCPH-1001 enters its kernel at 0xA0000500
SCPH-1001's reset code enters its relocated kernel through the uncached
alias (lui $a0,0xA000; addiu $a0,$a0,0x500; jr $a0 at ROM 0xBFC0044C).
Beetle charges those four instructions as uncached fetches, 20 cycles; the
BIOS dispatch normalized 0xA0000500 to the kernel body compiled for runtime
0x00000500, which charges cached fetches, 11. That is the -9 cycles left at
SCPH-1001's kernel entry since #429 (docs/SEGMENT_AWARE_CODE.md §3.3).

A BIOS seed that is a runtime PC inside a relocated copy window, in another
segment than the window runs in, now asks for a segment variant (§5.4): the
function at those ROM bytes and its direct-edge closure (branch and J/JAL
targets that leave it, the call return and fall-through past its end) are
emitted again with the seed's segment. Every PC a variant computes from its
own PCs is in that segment (links, fetch tags and the uncached test, IRQ
resume, branch, J and fallthrough targets, store and exception PCs), a KSEG1
variant charges a fetch per instruction, and a jump-table case in another
segment is dispatched instead of continuing locally. Variants and their
continuations get exact-PC rows in a segment_variant_table. A variant seed
that names no emitted function entry stops the build, and so does one in a
segment that does not map physical memory (0x20000000-0x7FFFFFFF, KSEG2).

The dispatch still finds the word by its normalized key (and applies the
kernel-bless guard); a new psx_bios_hit_body() then picks the body for the
exact PC. A variant compiled for that PC runs. Any other PC in another
segment than the one the word's home body was compiled for still runs the
home body, as before, but is now recorded as a BIOS segment miss in the
segment-miss ring (kind "bios", with the home PC), so a new alias entry is
loud instead of silent (§5.5). psx_bios_key_home_pc(), emitted from the
address model, gives each key's home runtime PC: its copy window's runtime
address, or the ROM run in place. The ring, TCP segment_misses and the exit
report carry each entry's kind; tools/collect_game_misses.py lists a BIOS
segment miss for the BIOS seeds instead of writing it into the game's.

SCPH-1001's seeds gain 0xA0000500 (kernel_entry_kseg1): a 4-instruction
KSEG1 variant of kernel_trampoline_0. Every BIOS's dispatch C gains the two
helpers and calls psx_bios_hit_body() where it called the home body. A BIOS
without variant seeds (OpenBIOS, SCPH-101, SCPH-5552) keeps its full C;
SCPH-1001's full C gains the variant body and its dispatch C the one-row
segment_variant_table.

New bios_segment_variants test: OpenBIOS with KSEG1 variants of kernel
0x12BC (closure 0x6F8 by jal and 0x70C by fall-through) and 0x13F4 (0x1424
by a conditional branch only) and a KSEG0 variant of kernel memset 0x540 (a
KUSEG jump table), plus a static check of SCPH-1001's seed. It compiles the
emitted lookup, with and without variants, and queries every variant row,
every home key's home PC (checked against the PC each function body was
compiled for) and every other segment's alias of it. Seeds in a segment
that does not map physical memory stop the build, and a runtime PC in its
window's own segment stays a discovery seed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 00:24:07 -07:00
Shokunin 6a76eb452b recompiler: compile segment variants of game code from segment-qualified seeds
A seed that names the image's bytes in another segment than its link
segment (0xA00100F0 for a KUSEG-linked EXE, 0x00012345 for a KSEG0 one)
now compiles a segment variant (docs/SEGMENT_AWARE_CODE.md §5.4): the home
functions the seed reaches through direct edges (calls, tail jumps, split
pieces, fall-throughs, call returns; a seed at a CPS continuation roots its
owner, an alias entry its alias group) are compiled again through a view of
the image in that segment. The variant's names, rows and every PC it bakes
are that segment's, and a KSEG1 variant charges a fetch per instruction
(#429's rule through B's runtime_pc()). A cached variant is the home C with
the segment swapped. Exact-match config sites move into the variant's
segment, so a variant gets every hook the home body gets. A seed that names
no home dispatch entry stops the build, and so does a seed in a segment
that does not map physical memory (0x20000000-0x7FFFFFFF or KSEG2: Beetle's
addr_mask leaves them unmasked, so they address no RAM and are no alias of
the image's bytes).
tools/collect_game_misses.py reports such a segment miss instead of writing
it as a seed.

The dispatch table holds a row per compiled segment of a word, adjacent and
in PC order; when any word has more than one row the lookup (indexed and
binary search) searches the word's rows for the exact PC. Two rows for one
PC remain a build error. A title without variants gets the same dispatch
source as before.

A jump-table case whose value is in another segment than its body is no
longer a local case: the default tail-transfer dispatches it with its full
PC. This applies to every compile. In a variant it is the common case (a
KSEG0 variant of KUSEG-linked code). A home or overlay compile has one only
when its table holds another segment's PCs of its own bytes, for example a
KSEG0-compiled overlay shard of code that runs at KUSEG. Such a case used to
continue in the KSEG0 body with KSEG0 links and fetch tags. Now it
dispatches: a game-text PC with no row in its segment is a recorded segment
miss, and #417's gate sends an overlay PC to the interpreter; both run it
at its own PC (PR E returns overlay code to native per-segment shards). R4's
game C and its 26 overlay shard sources are unchanged.

Ledger: segment-variants and kseg1-fetch-charge close (KNOWN_GAPS is empty)
and become regression guards; resume-pc-segment now requires each row's key
and resume PC to be in its body's segment, and segment-variants checks every
PC a variant body bakes. New segment_variants_test; kuseg_dispatch_lookup
and emitter_runtime_pc_test cover variant rows, closure, equality with the
home C, per-instruction KSEG1 charging, foreign jump-table cases at home and
in variants, data stubs (no row), alias groups declared for every shard and
seeds outside the physical segments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 00:24:07 -07:00
Shokunin d67f0f9396 recompiler: compile a KUSEG-linked EXE for its link segment; exact dispatch
A PS-X EXE header may carry KUSEG addresses (Alien Resurrection SLUS-00633,
Kula World SCES-01000); the BIOS then runs the program at KUSEG PCs, so its
links, EPCs and I-cache tags are KUSEG. The parser folded such a header to
KSEG0, so the whole program compiled for a segment it never runs in, and
the dispatch lookup matched any segment by physical address
(docs/SEGMENT_AWARE_CODE.md §3.2, §5.3, §5.5).

- EXE parser: keeps the header's addresses. link_segment() is the load
  address's segment; the load address and entry must share it and lie in
  the 8 MiB RAM window of KUSEG, KSEG0 or KSEG1 (KSEG1 compiles as an
  uncached home segment). phys_load_address()/contains_phys() do byte
  addressing; the BSS overlap check and the text_size bound are physical.
- function_analysis: the three JAL scans take the target's top bits from
  the delay slot's PC instead of assuming KSEG0 (unchanged for KSEG0).
- Seeds are range-checked by physical address. A seed in the link segment
  is an entry (0x000100E4 was silently dropped before). A seed for the same
  bytes in another segment asks for a segment variant (§5.4): it is listed
  in a WARNING, never folded into the home body. Seeds outside the image are
  counted. The producer_range, cross_call_allow and hosted_interior
  directives have no variant form: a foreign spelling is refused with the
  link-segment one instead of a bare "invalid" error.
- Config code sites that are matched exactly (widescreen and cull sites,
  mod hooks, hot/data-shard funcs, the VSync-query function and its
  event-horizon return PCs, persisted options) and name the image's bytes
  in another segment are refused with the spelling that matches, since they
  would now match nothing. Kinds matched by physical address (byte patches,
  the full-word-guarded cull keep/angle/aspect-cone and signed-X-bound
  sites, dome call sites) reach the same bytes in any spelling and are not
  refused. Overlay compiles are exempt until PR E.
- tools/collect_game_misses.py wrote every dirty-RAM miss as a KSEG0 seed,
  which a KUSEG-linked image now reports instead of compiling. It writes
  them in the link segment (--segment, --game-toml, or a segmented text
  range; it refuses to guess from a physical one) and adds the
  segment_misses rows at their full PC.
- Dispatch source moves to game_dispatch_emitter.cpp. The lookup indexes
  the physical word, then requires row.addr == addr; a second row for one
  word is an error until variants share the table. psx_game_address_in_text
  stays physical, so a segment miss takes the clean-text-miss path (the
  previous commit records it).

For a KSEG0 title the game C is byte-identical; its dispatch file changes
only in psx_game_find_entry (the exact-PC compare) and its comment.

Tests:
- segment_aware_codegen closes link-segment, fetch-tag-segment,
  irq-resume-segment, resume-pc-segment, store-pc-segment,
  home-seed-accepted, alias-fetch-coherence and segment-miss; they stay as
  regression guards, and resume-pc-segment now also checks the dispatch
  rows' keys and resume PCs.
- kuseg_dispatch_lookup: KUSEG rows, names and baked PCs; aliases miss while
  text stays physical; variant seeds reported; a function reached only by
  jal and a SaveState-style continuation are discovered in a KUSEG image
  (kills the two discovery scans reverted to KSEG0; the third, the
  invalid-JAL data heuristic, reads the same bits either way); mixed-segment
  headers rejected; a KSEG1 image charged per instruction; foreign config
  sites refused; the RAM-mirror contract as before.
- kuseg_dispatch_lookup also compiles the lookup's binary-search form (a
  table spanning 2 MiB or more) and queries every segment's alias of each
  row; refuses a KSEG0 event-horizon site and KSEG0 seeds directives with
  their KUSEG spelling; accepts a KSEG0 signed_x_bound site and applies it.
- emitter_runtime_pc_test: the dispatch rows follow runtime_pc() and keep
  the compile identity in func_ names (kills B's two surviving mutants: 76
  and 12 failing checks); two rows on one physical word are an error.
- collect_game_misses (new, tools/tests): link-segment seeds from each
  source, segment misses at their full PC, no guessing, no duplicates,
  against a fake server using the runtime's own summary row format.
- reachable_discovery_test: the parser keeps KUSEG, accepts KSEG1, rejects
  mixed segments and non-RAM load addresses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 21:57:34 -07:00
Shokunin 980274a405 recompiler: route every baked guest PC through runtime_pc()
Segment-aware code, rollout PR B (docs/SEGMENT_AWARE_CODE.md §5.2).

Game/overlay emitter: CodeGenerator gains a code segment and
runtime_pc(addr) = code_seg | phys. Links (including a jalr in a branch
delay slot), fetch tags and the uncached-fetch test in emit_pre_icache,
interrupt resume PCs, CPS exit PCs and continuation keys, store-PC
stamps, the reserved-instruction EPC, the slice resume PC,
call_by_address and stale-guard targets, the call contract's return PC,
cosim PCs, the VSync-query hook's base PC, the data stubs'
unknown-dispatch PC and the dispatch rows' addr/resume_pc all go through
it. func_/block_ names, .ranges and entry-hook ids keep the compile
address. The VSync-query hook is not emitted for an uncached code
segment: its hand-timed body charges cached fetches only. The segment
defaults to the image's own, so runtime_pc(addr) == addr for every
existing input and KSEG0 titles regenerate byte for byte.

BIOS emitter: StrictTranslator::translate(d, runtime_pc) stamps stores,
the syscall EPC and the break and unaligned-access PCs with the PC the
code runs at; the psx_ldd_ temporaries keep the ROM address. The
full-function emitter passes relocate_ra() to every translate() call and
publishes the runtime PC on a fallthrough. Relocated kernel and shell
code therefore stamps its RAM address, as the interpreter does.

memory.c re-keys, in the same change, the seven SCPH-1001 store-PC keys
that named ROM addresses of relocated code: RAM-0 filters 0x00000F00
(ROM 0xBFC10A00), 0x80056EB4, 0x800585E4, 0x80058788, 0x80059C50 (ROM
0xBFC3EEB4/405E4/40788/41C50), the opt-in Tomba EvCB key 0x00001CE4 (ROM
0xBFC117E4) and the GP0 source key 0x80050B1C (ROM 0xBFC38B1C). Without
the re-key the new stamps would silently disable all seven. A RAM PC is
ambiguous where the ROM key was not, so each key goes through
scph1001_relocated_store(pc, rom): it matches only while the active
image is SCPH-1001 and the word at pc is still the ROM word copied
there. Other BIOSes and game code at those addresses never match, as
before.

Tests: emitter_runtime_pc_test (new, CTest, run with PSX_CPS unset and
set to 0) compiles a small program through generate_all_functions with
the code segment left default, KSEG0, KUSEG and KSEG1, in CPS,
CPS-overlay and legacy mode. It requires every emission path to be
reached and checks every guest-visible PC class, the identity keys,
physical-only differences and KSEG1 per-instruction fetch charging, and
the translator's runtime PCs in every PC-bearing form. The ledger gains
two regression guards: bios-runtime-pc (OpenBIOS output, plus every
translate() call site) and store-pc-keys-runtime (memory.c keys against
SCPH1001.toml's windows). KNOWN_GAPS is unchanged: B closes no ledger id
on its own (§5.2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 20:10:45 -07:00
Shokunin d51a34a766 docs: record the segment-aware decisions and the store-PC correction
The owner settled §10's four questions on 2026-09-29, all as recommended:
- I-cache tags follow Beetle; the hardware bit-31 difference is recorded
  in ACCURACY_BURNDOWN axis 4.
- Segment misses in static game code interpret loudly until the title is
  regenerated.
- Extra segments are requested through segment-qualified seeds.
- The overlay cache uses per-segment subdirectories.

The doc called g_debug_last_store_pc a debug/identity hook. It is not:
memory.c's RAM 0x0-0xF store filters compare it with exact PCs in every
build, the interpreter stamps the full PC, and #420 (ABI v24) forwards
overlay stamps to the host. §3.1 now lists it as a baked PC, and §5.2
routes it through runtime_pc(). Consequences:
- Option A, re-measured with the 1,261 store-PC stamps in the three R4
  shards: +3.8 % / +27.6 % __TEXT (was +3.2 % / +27.2 %).
- New ledger id store-pc-segment (10 KSEG0 stamps in the synthetic EXE's
  home bodies), closed by §5.3. The ledger now has 10 known gaps.
- §9: several filter keys are ROM addresses of BIOS code that runs
  relocated in SCPH1001. The BIOS emitter stamps ROM addresses and the
  interpreter stamps runtime PCs, so the keys must be re-keyed when the
  BIOS stamp moves to runtime_pc().

§8 notes that rollout PR A (fix/uncached-fetch-per-insn, based on master)
is being prepared. A has since landed as #429, so the ledger does not
list bios-kseg1-fetch-charge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 18:20:52 -07:00
Shokunin 9f8ee72f4d docs: design segment-aware code, with a KUSEG-linked acceptance ledger
Compiled code bakes KSEG0 into link values, CPS/IRQ resume PCs and I-cache
tags. KUSEG-linked EXEs run KUSEG PCs through those bodies (and their KUSEG
seeds are dropped), and overlay code executed at KUSEG/KSEG1 stays
interpreted after #417. Until #429, both emitters also charged the uncached
KSEG1 fetch only at cache-line leaders, so compiled BIOS ROM code was 4
cycles short on 5,477 of OpenBIOS's 9,592 KSEG1 instruction sites (Beetle
and the interp charge every fetch).

docs/SEGMENT_AWARE_CODE.md recommends per-segment compiled variants (code
identity = full VA, emitter segment as a per-compile constant, exact-segment
dispatch, per-instruction fetch in uncached bodies, segment-aware capture
and cache keys) over segment-relative emission, which measures +3.2 % /
+27.2 % __TEXT on R4 shards.

tools/segment_testrom/gen_segment_exe.py emits a synthetic KUSEG-linked
PS-X EXE that also works as an oracle probe. The new ctest
segment_aware_codegen runs the real recompilers on it and on OpenBIOS and
checks emitted PCs, the emitted dispatch lookup and fetch cycles (through
runtime/src/psx_icache.c) against a Beetle ReadInstruction transcription.
It passes only while the observed gaps equal its ledger (9 today).

#429 (rollout A) fixed the BIOS undercharge on master before this landed,
so KNOWN_GAPS does not list bios-kseg1-fetch-charge: the OpenBIOS check
finds no uncharged KSEG1 site and stays as a regression guard that reports
the id as a new gap. The last commit of this series brings the doc up to
date with #429.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 18:20:52 -07:00
Matthew Stanley 8f3b6778a0 AOT extract: resolve discovery's BIOS profile from the framework, fail closed
Normal-mode discovery ran psxrecomp-game with no --project-root and ignored its exit code, so from a cwd without bios/ every producer silently lost its discovered entries (Tomba 2 shipped 54 native pairs instead of 81).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 16:43:58 -07:00
Shokunin c981b3a2d7 bios emitter: test cache-line starts on the runtime PC
The cached line-leader rule charges a fetch at every 16-byte line start of
the runtime PC. The BIOS emitter tested bits[3:0] of the ROM address instead
and assumed every copy window preserves them. OpenBIOS copies its kernel from
ROM 0x1FC1E4D4 to RAM 0x500, which shifts them by 4. Each kernel line
crossing was charged one instruction early, where it is a hit, and the real
crossing went uncharged. 1,152 kernel instruction sites differed from the
interpreter.

The test now uses the runtime PC that relocate_ra already computes. In the
OpenBIOS kernel, 823 misplaced fetch sites go and 773 are added at runtime
line starts. Retail profiles are unaffected: SCPH-1001's copy windows are
16-byte aligned and SCPH-101/5552 declare none. The uncached_fetch_charge
ctest now also replays every cached OpenBIOS run from a cold cache against
the interpreter's fetch path and the Beetle transcription.

R4 (OpenBIOS, recompiled kernel, HLE boot) reaches the game entry 6,013
guest cycles later than with the uncached fix alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 12:27:57 -07:00
Shokunin 7342e6bcd1 emitters: charge an uncached (KSEG1) fetch before every instruction
Beetle's ReadInstruction never fills an I-cache line for a fetch at
0xA0000000 or above: every such fetch costs +4 and clears the pending load
give-back. The dirty-RAM interpreter charges that by fetching at every PC.
Both static emitters emitted psx_icache_fetch only at cache-line leaders,
which is exact for cached code only. In OpenBIOS, 5,477 of the 9,592
in-place ROM (KSEG1) instruction sites were uncharged: 21,908 cycles short
per pass through that code, each also missing its give-back clear.

Both emitters now charge a fetch before every instruction whose runtime PC
is uncached. The predicate is psx_fetch_uncached in psx_instr_cost.h, which
psx_icache.c now uses too. The A0/B0/C0 call-vector native stubs charge one
fetch per executed word, so a KSEG1 call pays like the interpreter. Cached
code keeps the leader rule. R4's regenerated game C is byte-identical, and
the OpenBIOS diff is insertions only. psx_instr_cost.h joins the codegen
hash list, since the emitters fold it into emitted code.

Tests:
- ctest uncached_fetch_charge runs psxrecomp-bios on OpenBIOS and compares
  per-instruction fetch charges three ways: the compiled fetches, the
  interpreter's fetch path (psx_icache_fetch_interp) at every PC, and a
  Beetle transcription.
- uncached_fetch_codegen_test checks the game emitter at KSEG1 and KSEG0.

Both tests fail on master. This closes bios-kseg1-fetch-charge in the
segment_aware_codegen ledger (#419).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 12:27:57 -07:00
Cyanoxide 4aa72128fa Make the depth24 trailing margin configurable per title 2026-09-29 20:00:35 +01:00
Matt Stanley 470f03b782 Merge pull request #430 from RetroPortingToolKit/feat/fp-identity-tool
tools: generic A/B guest-identity check (fp_identity.py)
2026-09-29 11:24:04 -07:00
Matthew Stanley 2bf4739f44 Merge master into feat/internal-resolution-settings: regenerate TCP_COMMANDS.md
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 11:22:53 -07:00
Matt Stanley f8a6f4f185 Merge pull request #425 from RetroPortingToolKit/feat/internal-resolution-gl
Raise the GL internal-resolution ceiling to the driver's limits
2026-09-29 11:22:28 -07:00
Matthew Stanley 914d8cb305 Merge master into feat/frame-rate: reset_on_load onto #420's fingerprint struct
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 11:22:02 -07:00
Matt Stanley d5e41d7b7d Merge pull request #423 from RetroPortingToolKit/feat/frame-rate-blend
Add a flip-aware frame-interpolation source (opt-in)
2026-09-29 11:21:11 -07:00
Matt Stanley b2f72d7cce Merge pull request #422 from RetroPortingToolKit/feat/adaptive-widescreen
Add [widescreen.cull] bgez_sites and clip_edge_x_load_sites
2026-09-29 11:20:58 -07:00
Matt Stanley f9ad37d25f Merge pull request #418 from RetroPortingToolKit/fix/muldiv-deferred-deadlines
runtime: publish deferred CPU work before mult/div deadline checks
2026-09-29 11:19:57 -07:00