Commit Graph
83 Commits
Author SHA1 Message Date
Joe MattielloandClaude Opus 5.5 aa5b73bd2b ci: use RUNNER_TEMP for scratch files the self-hosted Studio runner would keep (#1084)
The Studio runner keeps /tmp between jobs, so regression-macos-arm64's
miniretro clone failed on the next run ('destination path /tmp/miniretro-src
already exists', exit 128; #1082). Clone into a fresh RUNNER_TEMP dir, and
move the EEPROM lifecycle test's scratch files there too.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-23 17:23:45 -04:00
e7009c440a ci: trim develop-push runs, debounce nightly, route macOS to Studio runner (#1074)
* ci: stop re-running PR-covered workflows on develop pushes; debounce nightly; route macOS to Studio runner

Drop develop from push: in c-cpp, app, regression-test, acid-test, installer,
jaguar-demos, microbench, jaguar-toolchain and cheat-import (PRs already ran
them); ignore develop in pull-request.yml. release.yml's nightly waits 10 min
in its guard job so a merge burst builds once. macOS rows of c-cpp/app/
regression and the unsigned iOS/tvOS nightly rows use the self-hosted Studio
runner on trusted events only, with a VJ_STUDIO_RUNNER=off kill switch.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

* ci(spm): find blitter_simd objects independent of SwiftPM build layout

Xcode 26 SwiftPM keeps objects under .build/out/Intermediates.noindex/...
instead of <triple>/debug/virtualjaguar.build, which broke the vtable check on
the Studio runner. Search by object name and fail loudly if one is missing.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

---------

Co-authored-by: dry <dry@x>
Co-authored-by: Claude Sonnet 5.5 <[email protected]>
2026-10-22 23:24:46 -04:00
Joseph Mattiello 00d205caa4 Merge master (v3.10.0) back into develop
Co-Authored-By: Claude Opus 5.5 <[email protected]>

# Conflicts:
#	.github/workflows/cheat-import.yml
#	Makefile
2026-10-21 23:43:06 -04:00
Joe MattielloandClaude Opus 5.5 9c5656e271 perf(dsp): build the untimed DSP loop without GDB code when nothing is armed (#1057)
* perf(dsp): build the untimed loop without GDB code when nothing is armed

DSPExecUntimed() is now compiled twice from dsp_exec_untimed.h: a plain
instance with no breakpoint test and no idle-skip gate, and an armed
instance used only while gdbArmedDSP is set.  DSPExec() picks one per
slice, alongside the existing timed/untimed split.

The armed instance keeps the breakpoint test where it was, after the IRQ
dispatch and the PC-escape bail, so a breakpoint on an ISR's first
instruction still fires.  Arming is sampled once per slice, as before.

Preprocessed, the plain loop is develop's loop with the GDB lines
removed and nothing else changed.

Refs #1054

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(debug): record the #1054 DSP loop specialisation and its layout caveat

The parked specialisation now ships, so the design doc's "parked" and
"~2% unexplained" text no longer describes the code.  Records the A/B/B/A
numbers.  It also records that code layout moves this loop by about +/-1%:
compiling the check out makes real games slower, and the port beats the
ceiling on identical C.  The claim stays narrow: yarc gains, games are
neutral.  It also notes that the DSP_HOOK control arm will now read
about 0.

Refs #1054

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(dsp): keep the local-RAM opcode fetch inside dsp_ram_8

The six fetch sites (both exec loops and the four delay-slot fetches)
read dsp_ram_8[off] and [off + 1] whenever dsp_pc was below base +
0x2000.  So an odd PC at base + 0x1FFF read one byte past the array.
cppcheck flagged it (arrayIndexOutOfBoundsCond) once the code moved
into a changed file.  The bound is now base + 0x2000 - 1, the same form
gpu.c already uses.  That one address now goes through DSPReadWord(),
which aligns.  Every other PC is byte-identical.

clang-tidy now skips src/jerry/dsp_exec_untimed.h the way it skips the
*_simd_* fragments: on its own it is only undeclared dsp.c statics.
Its code is still linted through dsp.c via HeaderFilterRegex.

Refs #1054

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-21 02:30:22 -04:00
Joseph Mattiello 3c683169ef ci(core): share compile-only release builds and preserve CI artifacts 2026-10-20 18:39:49 -04:00
Joseph Mattiello b8164d81b9 ci: share source validation runner and ensure Ruby manifest checks 2026-10-20 17:55:51 -04:00
Joseph Mattiello 2165519e97 ci: allow ref-scoped temporary runners for lightweight checks 2026-10-20 17:34:02 -04:00
Joseph MattielloandClaude Sonnet 5.5 e873883e33 feat(debug): core side of the app debug interface (vjdebug.c) with socket-less halt pump
src/debug/vjdebug.c implements struct vj_debug_iface: side-effect-free peek
(JaguarReadByte DEBUGGER), policy-checked poke, both RISC register banks, RISC
disassembly, attach/halt-pump/breakpoints on the existing GDB halt machinery
(a host can attach without a socket; GDBHalt pumps it and never blocks when
nothing could resume it), a one-walk OP display-list snapshot taken before the
OP rewrites HEIGHT/DATA, a blitter launch log ring, TexReplaceReload, and the
frame counter.

The interface is handed over once via VJ_ENV_SET_DEBUG_IFACE, only to a
frontend that set VJ_CAP_DEBUG (RetroArch never does).  Detached cost: one
global test per OPProcessList call and one per blitter launch.  Statics reset
on unload and deinit.

test/test_debug_iface.c drives it on yarc.j64: who gets the interface, peek ==
jaguarMainRAM, the poke policy, halt/step/continue/detach through a scripted
host, breakpoint re-hit, OP snapshot, blit log, and an identical frame + RAM
hash with everything switched on.

Refs #652 Refs #833

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-13 20:23:06 -04:00
Joseph MattielloandClaude Sonnet 5.5 8b9065d86e feat(core): free-form clock scale, gdb timeout clamp, private frontend event channel
Parser-only emulation change: virtualjaguar_{m68k,risc}_clock_scale also accept
a percentage ("175" or "175%", clamped 25..400) after the listed values
(0.5x/1.5x/2x/3x), which map exactly as before; anything else the old ladder
did not know still resolves to stock. virtualjaguar_gdb_halt_timeout is clamped
to 3600 s.

New src/core/vj_frontend_ext.{h,c}: a RETRO_ENVIRONMENT_PRIVATE HELLO/EVENT
channel (ABI 1). retro_init asks once; a frontend that does not answer (RetroArch)
latches it off so no event is ever sent; retro_deinit resets the latch. Events
(LINK_UP/DOWN/STATS, WIRE_SPEED, CRASH_WATCHDOG) are emitted from retro_run next
to the existing log lines, which are unchanged. JLinkStatsLast() exposes the
numbers behind the stats line.

A typed link host that was never a picker row no longer draws the 'dropped off
the LAN' warning.

Refs #833 Refs #888

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-13 16:42:39 -04:00
Joseph MattielloandClaude Opus 5.5 42f1c488da test(cd): synthetic CHD fixture with virtual pregaps (#774)
Every committed CHD fixture was PREGAP 0, so the virtual-pregap path #755
fixed had no coverage. synth_jagcd_vpregap.chd has three session-2 tracks
with 2/3/5-sector INDEX 00 runs that the pinned chdman records as VAUDIO
pregaps counted inside FRAMES (the Myst (USA) layout). Data sectors carry
per-track, per-sector tags so a misplaced read fails on content.

test_cd_chd checks each track's TOC MSF, that every pregap sector reads
silence, and that every INDEX 01 sector reads its own tag. It passes on
develop and fails on f28ade2^1, reading silence at track 3's INDEX 01.
jagcd-chd-check must exit 2 on the fixture, in make test and in CI.

Refs #774

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-01 23:36:04 -04:00
Joe MattielloandClaude Opus 5 a40b202abd ci(build): guard the MSVC job's source list against Makefile.common drift (#679) (#721)
* ci(build): guard the MSVC job's source list against Makefile.common drift (#679)

scripts/check-package-sources.sh already does this for Package.swift.
Nothing did it for the msvc-check job, whose list lives inline in the
workflow, is maintained by hand, and is validated only by pushing and
waiting for Windows. That gap cost real time three times in one day
(#624, c89-lint.sh's missing -Isrc/debug, and #662 -- where a brand-new
src/debug/ directory built everywhere except MSVC x64/x86 while ~35 other
checks passed).

New scripts/check-msvc-sources.sh, wired into `make lint` (so drift fails
before a push) and into the "duplicated lists in sync" CI job next to its
sibling. Parses only the FIRST cl.exe step; the second compiles
blitter.c + blitter_simd_sse2.c alone to prove the SSE2 shape and is
narrow on purpose, so flagging it would make the check cry wolf.

Two lists, deliberately kept apart:

- EXCLUDED -- design, each with a reason. Only the two arch-gated
  blitter_simd variants qualify.
- KNOWN GAP -- debt, not design: 30 sources the job has never compiled
  despite its step being named "Compile all sources". Folding these into
  "excluded" would launder them, which is exactly what #679 warns
  against; the rule is that this list may shrink, never grow. A NEWLY
  added source lands in neither list and fails the check, which is the
  #662 case and the whole point.

Closing that 30-file gap means adding them to a Windows-only job that
cannot be tested from here, days before a release. Doing it in the same
change would make a red MSVC job ambiguous -- guard first, then the gap.

Acceptance from the issue, all three verified by actually breaking things:
- Removing src\debug\gdbstub.c from the job: FAILS, naming the file.
- Adding a source to Makefile.common only: FAILS, naming it (exit 1).
- The SSE2-only second cl.exe step: not flagged.

One bug found in this script by its own review: the include-dir
comparison was VACUOUS on first write. Makefile.common spells them
-I$(CORE_DIR)/src/..., the regex matched a bare path, mk_inc came out
empty, and comm against an empty list passes on nothing -- the same class
of failure this file exists to prevent. mk_inc is now in the emptiness
guard, and the parse is scoped to the unconditional `INCFLAGS :=` block
because the later `+=` is msvc2003-gated and must NOT be demanded of the
modern job. 14 include dirs are now really compared.

Verified: make lint clean, make TEST_EXPORTS=1 test exit 0, zero FAILs.

Refs #679

Co-Authored-By: Claude Opus 5 <[email protected]>

* ci(build): guard both cl.exe step markers, not just the first (#679)

From the Kimi review on PR #721, and it is right.

`sed -n '/start/,/end/p'` whose END pattern never matches runs silently to
EOF. So renaming only the second step's name would have folded that step
into the parse and quietly broken the "ONLY the first cl.exe step"
invariant the check depends on -- with no error, because the `[ -s ]`
guard only catches the START pattern failing. The error message already
said "the step names this parser keys on", plural, while checking one.

Benign today purely by luck: blitter_simd_sse2.c is in Makefile.common
(so it never trips the stale branch) and excluded (so it never trips the
missing branch). Luck is not an invariant.

Now symmetric: both markers must be present, and the extracted range must
TERMINATE on the end marker rather than run off the end of the file.

Verified by breaking each one separately:
- rename the END marker only  -> exit 1, names the missing marker
- rename the START marker only -> exit 1, names the missing marker
- clean tree                   -> exit 0

Shell-only change; `make lint` clean (all three guards) and the three
issue acceptance criteria re-verified.

Refs #679

Co-Authored-By: Claude Opus 5 <[email protected]>

---------

Co-authored-by: Claude Opus 5 <[email protected]>
2026-08-31 20:31:32 -04:00
Joe Mattiello efc4c7d8af perf(simd): SSE2 ports of OP phrase stores and TOM scanline converters (#687)
Port the NEON fast paths merged in #670/#672/#682 to x86 SSE2 so Intel
Linux/Windows/macOS builds take the same wins:

- src/tom/op_simd_sse2.h: 16bpp and 24bpp OP phrase stores (movq
  load/store, cmpeq+and/andnot/or select for TRANS lane masking).
- src/tom/tom_scan_simd_sse2.h: 16bpp-direct, 24bpp and RGB16
  scanline converters plus the 2x hi-res gather/expand kernels.
  The 24bpp converter needs no vld4-style deinterleave: x86 is
  little-endian, so a raw dword load plus mask/shift reassembles
  XRGB8888 in three ops.

Dispatch in op.c/tom.c mirrors the NEON branches (#elif on
BLITTER_SIMD_HAVE_SSE2 from blitter_simd_arch.h) with the guard
conditions copied verbatim, so exactly one (or neither) fast path
compiles. SSE2 only -- no SSSE3/SSE4; the buildbot x86 baseline
stays intact. No build-flag changes needed: x86_64 has SSE2 as
baseline and Makefile.common already appends -msse2 wherever it
selects the SSE2 blitter (i686 CI rows included); a generic i386
build without SSE2 stays scalar via the capability macro.

c89-lint.sh: skip the new include-only headers in the default pass
(same as the NEON ones) and extend the per-arch pass to compile
op.c/tom.c per arch, which is the only host-side syntax coverage
the non-native fragment headers get.

c-cpp.yml clang-tidy: broaden the exclusion regex from
blitter_simd_* to all src/{tom,jerry}/*_simd_{neon,sse2,scalar}.{c,h}
fragments -- include-only files that cannot be linted standalone.
2026-08-27 04:02:41 -04:00
Joe Mattiello 0d451c4e03 feat(debug): GDB remote stub Phase 1 — protocol engine, 68K target, loopback transport (#662)
* feat(debug): RSP packet framing for the GDB stub

Refs #652

* feat(debug): RSP run-length decode and command dispatch

Refs #652

* feat(debug): bounds-checked m packet for the GDB stub

Every guest address is validated by the target op before any read; the
engine turns refusal into E01 and never guesses a range.

Refs #652

* feat(debug): 68000 register and memory target ops

Refs #652

* feat(debug): loopback-only TCP transport for the GDB stub

Binds 127.0.0.1 only, never INADDR_ANY. Verified by observation:
netstat shows 127.0.0.1.<port> LISTEN, never *.<port>.

Refs #652

* feat(debug): core options and lifecycle for the GDB stub

Two Restart-gated core options (virtualjaguar_gdb_stub, virtualjaguar_gdb_port)
read raw via environ_cb in retro_load_game, matching the enhancement-hook
gate's reasoning: a developer-facing option must not be settable by a
per-title DB row. On enable, opens the loopback listener, inits the RSP
session against the Jaguar target ops, and shows a one-shot SET_MESSAGE_EXT
banner. Serviced once per frame from the top of retro_run() -- one poll()
and two comparisons when disabled or idle. All new statics (including the
packet accumulation buffer) are reset in both retro_unload_game() and
retro_deinit(), since iOS cannot dlclose the core.

Verified: savestate digests are byte-identical across stub-disabled and
stub-enabled-idle runs of test/tools/hires_state_digest (three checkpoints,
SHA-identical output). netstat confirms the live core binds
127.0.0.1.<port>, never *.<port>.

Also fixes two build-system gaps this change exposed: scripts/c89-lint.sh
was missing -Isrc/debug (libretro.c now includes gdbstub.h), and
Package.swift's coreSources/headerSearchPath arrays had drifted from
Makefile.common per the exact class of bug scripts/check-package-sources.sh
exists to catch -- it now reports clean. `swift build` itself still fails
in this environment on an unrelated, pre-existing manifest error
(defaultLocalization not set) reproducible on the unmodified manifest, so
full SPM compilation could not be verified end-to-end here.

Refs #652

* test(debug): end-to-end GDB attach probe

Scripted RSP client (test/tools/gdb_attach_probe.py) since the #581
toolchain does not ship a GDB. Run live against the actual core (not
just the fake-target unit tests): attach, qSupported, halt reason, 144-hex
register read, memory read, and bounds refusal on a wild address -- all
passed against ./virtualjaguar_libretro.dylib with the stub enabled.

Answers both of docs/gdb-stub-design.md's open questions, recorded there
with evidence:
- #581 toolchain ships no m68k-elf-gdb (checked the built tree after
  `make jaguar-toolchain-build`; PIN lists five tools, none a debugger).
- rln emits no DWARF: its output is classic mc68k COFF (not ELF), `-g`
  produced a byte-identical link to the same build without it, and no
  toolchain on this host can even parse the format as an object file.
  Every "source-level debugging" claim in the design doc is corrected to
  symbol-level (rln -m's load map) accordingly.

Refs #652

* fix(debug): correct overclaims and harden the GDB stub's Phase 1 edges

Post-review accuracy pass, three fixes:

- gdbtarget.c's non-perturbing comment overclaimed: it verified every
  who-gated side effect but said nothing about unconditional MMIO read
  side effects (e.g. CD BUTCH+2's DSCNTRL ack, reached only via the
  16/32-bit path this file's byte-granular reads never take). Narrowed
  to state exactly what was checked and what a debugger read through
  MMIO can still legitimately perturb, same as any processor touching it.
- docs/gdb-stub-design.md: the scripted RSP probe passes without ever
  sending the low-level +/- ack byte real GDB requires before
  QStartNoAckMode is negotiated -- a real m68k-elf-gdb would stall on its
  first qSupported. Documented as a named Phase 2 gap rather than left
  implicit in a probe that doesn't check for it.
- gdbsock.c: added the same #pragma comment(lib, "ws2_32.lib") under
  _MSC_VER that jlink_tcp.c (the file this one deliberately mirrors)
  already carries, for parity on a raw MSVC compile outside the
  Makefile's own -lws2_32 link line.
- libretro.c: a hand-edited config with a non-numeric gdb_port value
  used to silently ask the OS for an ephemeral port while the startup
  banner kept reporting the bogus requested number; now rejected in
  favour of the documented default.

Also confirmed jni/Android.mk and the MSVC buildbot targets both build
through Makefile.common's own SOURCES_C/INCFLAGS rather than a third
hand-maintained list, so neither needed the src/debug update Package.swift
did in the previous commit.

No behavior change to the wire protocol or the packet-framing engine.
Determinism re-verified byte-identical after these edits.

Refs #652

* ci(msvc): compile src/debug in the MSVC check

libretro.c now includes gdbstub.h, but the MSVC job's hand-maintained
source list and include flags had no src\\debug -- so cl.exe failed to
open the header on both x64 and x86 while every other target built fine.

Same class of drift as the SIMD blitter include-path miss: Makefile.common
carries the real source list, and these by-hand CI compiles silently fall
behind it. Only the first cl.exe invocation needs it; the second compiles
just the blitter for SSE2.

Refs #652
2026-08-27 01:40:06 -04:00
Joseph MattielloandClaude Opus 5 5f0925f058 build(spm): drop unsafeFlags -- they make the package unconsumable by version
Kimi review on #625, and it is a real defect that defeats the point of the PR.

SwiftPM REFUSES to build a VERSIONED dependency (`from: "x.y.z"`) on any
package whose targets carry unsafe flags:

    error: 'app': the target 'virtualjaguar-libchdr' in product
           'VirtualJaguar' contains unsafe build flags

Only path and branch/revision dependencies are exempt. So a frontend doing the
natural thing -- pinning a release tag of this repo -- could not build, which
is exactly the "depend on it as a package instead of vendoring it" workflow
this manifest exists to enable.

My verification could not have caught it: `swift build` inside the repo builds
this as the ROOT package, and the root is exempt from the restriction. The
spm-build CI job had the same blind spot. Same shape as the boolean.h
correction earlier on this branch -- a test that structurally cannot observe
the failure it is cited as evidence against.

The flags only silenced unused-function/variable warnings in libchdr's unity
TU. Not worth making the package unusable; removed.

Added a CI job that consumes the package the way a frontend actually would --
a throwaway tagged clone plus a tiny consumer depending on it by version -- so
the restriction is tested rather than remembered. Two things about that job
were established by experiment rather than assumed:

  - It must run `swift build`, NOT `swift package resolve`. Resolve SUCCEEDS
    with unsafe flags present; the graph is only validated for a build.
  - The clone goes in a directory named after the repo, because SwiftPM derives
    package identity from the URL's last path component and not from `name:` in
    the manifest.

Also from the same review: check-package-sources.sh now uses `set -eu` rather
than `set -e`, and `command rm` in its trap. An unset variable expanding to
empty is precisely how a drift guard starts silently passing on nothing, and
this repo's shells alias rm to `rm -i`, which blocks forever with no tty. These
guard scripts get copied from, so the precedent matters.

  swift build (root)                 clean
  scripts/check-package-sources.sh   66 core + 13 libretro-common agree

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-26 20:33:15 -04:00
Joseph MattielloandClaude Opus 5 534ded45f9 build: ship a Package.swift so Apple frontends stop vendoring this repo
Closes the last item on #614.

Apple consumption of this core currently requires a fork branch plus a
downstream manifest that re-declares every source file. That second copy of
the file list is the problem: the Provenance wrapper kept one and drifted by
27 files over seven releases before anyone noticed, and a missing
-DBLITTER_SIMD_<ARCH> in it meant every arm64 build inlined the SCALAR
blitter while the linked vtable was NEON (#612).

This manifest builds the C core only -- no Swift, no frontend glue -- so a
frontend depends on it as a package instead of vendoring it. It is inert for
every other build system: make, jni/Android.mk and the MSVC projects never
read it, and nothing here changes a flag they use.

Two guards, because a second copy of a list that changes every release is
what rots:

  scripts/check-package-sources.sh diffs Package.swift's arrays against
  Makefile.common, which stays the single source of truth. It fails loudly
  and names the files, never auto-fixes -- same posture as the
  cd-boot-matrix and version_fallback guards. Verified it catches both
  directions: a file added to Makefile.common but forgotten here, and a
  stale entry here that Makefile.common dropped.

  A new spm-build CI job actually runs `swift build`. Nothing else in CI
  reads this file, so without it a manifest that does not even parse would
  merge unnoticed and only break the frontend. That job also asserts exactly
  one blitter_simd_ops definition survives when all three SIMD variants are
  compiled together -- the invariant #617's arch guards exist to hold, which
  no Makefile target can check because it compiles only one variant.

Target names are prefixed (virtualjaguar-libretro-common,
virtualjaguar-libchdr). SPM requires them unique across the consuming app's
whole package graph, and generic names collide: a frontend carrying two
cores that each vendor CHD cannot build if both call the target "libchdr".
That is not hypothetical -- it broke the Provenance build this week.

path "." for the core target because libretro.c sits at the repo root and
SPM requires sources under the target path. The explicit sources list means
only named files compile, so docs/, test/ and scripts/ are ignored; the two
subdirectories owned by other targets are excluded so SPM does not reject
overlapping paths.

HAVE_COCOATOUCH and __GCCUNIX__, which the downstream manifest defines, are
deliberately absent: neither appears anywhere in the core sources.

  swift build                          clean, 80 files
  scripts/check-package-sources.sh     66 core + 13 libretro-common agree
  make -j                              0 errors (unaffected)
  make TEST_EXPORTS=1 test             0 test(s) failed
  one vtable, and it is NEON on arm64  verified via nm on the SPM objects
  blitter hot path                     47 NEON vector instructions

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-26 20:33:15 -04:00
Joseph MattielloandClaude Opus 5 27150e103f ci: give the by-hand SIMD test compiles the libretro-common include path
Dropping src/core/boolean.h in the previous commit broke four CI jobs --
build-macOS arm64, build-Linux aarch64, and both Windows MSYS2 builds -- at
the "Run SIMD blitter tests" step:

  test/../src/tom/blitter_simd.h:29:10: fatal error: 'boolean.h' file not found
     29 | #include <boolean.h>

The include path was already wrong; the stale duplicate was hiding it.
blitter_simd.h includes <boolean.h>, libretro-common owns that header, and
these by-hand compiles passed only `-I src -I src/core -I src/tom`. They
resolved it through src/core purely because the deleted copy happened to sit
there.

Makefile.common has always passed -I libretro-common/include, so no normal
build was affected -- only these two hand-written compile lines in c-cpp.yml,
which is exactly why a shadowing duplicate header can sit in a tree for years
without anyone noticing.

Verified with the exact CI command on this tree:

  before: fatal error: 'boolean.h' file not found
  after:  56103 tests, 56103 passed, 0 failed   (arch build)
          56103 tests, 56103 passed, 0 failed   (scalar cross-check)

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-26 20:33:00 -04:00
Joseph MattielloandClaude Opus 5 96d0b1852f build: committed version.h fallback for build systems that can't run a script
src/core/version.h is generated by scripts/gen-version-h.sh and gitignored:
it carries the short git rev, so committing it would dirty the tree on every
build. The Makefile and jni/Android.mk both run the generator at parse time,
so for them the header always exists.

Build systems with no hook to run a shell script before compiling have no
version.h at all and failed on libretro.c's #include outright. The
Provenance SwiftPM manifest is the case this exists for; it is the last of
the four SPM-compat fixes tracked in #614.

libretro.c now prefers the generated header and falls back to a committed
src/core/version_fallback.h. The __has_include test is nested rather than
`#if defined(__has_include) && __has_include(...)`: the standard says #if
short-circuits, but older preprocessors that lack __has_include can still
choke parsing the call in the right-hand operand. Every compiler without it
here is Makefile-driven and takes the #else, where the generated header
always exists.

CORE_BASE_VERSION is a deliberate copy; the Makefile stays the source of
truth. Nothing rewrites the fallback at build time, on purpose: the
generator runs at Makefile *parse* time, on every build including
`make lint`, so a generator that edited a tracked file would dirty the
working tree on every build -- feeding a false "-dirty" into
scripts/build-id.sh and the VJ_EXPECT_BUILD guard, and recreating the
silent-overwrite footgun this replaces.

The copy is checked instead of fixed, the same shape as the
docs/cd-boot-matrix.md drift guard: a new c-cpp.yml job fails loudly if the
two diverge, and version-bump.yml now edits and commits both on a release
bump (it previously did `git add Makefile` alone, which would have dropped
the new file).

GIT_VERSION falls back to " unknown-rev", spelled so it cannot be mistaken
for a real rev in a bug report. It cannot reach the VJ_EXPECT_BUILD guard,
which only runs against Makefile builds.

Verified:
  libretro.c -fsyntax-only with version.h absent   0 errors (the SPM case)
  fallback expands to                              "v3.5.1" " unknown-rev"
  built dylib reports                              v3.5.1 d86659d-dirty
                                                   (generated wins, not the fallback)
  drift guard, fallback forced to v9.9.9           correctly reports DIVERGED
  make -j                                          0 errors
  make TEST_EXPORTS=1 test                         0 test(s) failed
  scripts/c89-lint.sh libretro.c                   passed

scripts/c89-lint.sh keeps generating the header before linting -- that is now
a deliberate choice, not a workaround, since the Makefile build is what the
lint models. Its comment says so, because deleting those two lines would
silently switch the lint to the fallback path.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-26 02:15:43 -04:00
dependabot[bot] 71a2f70850 ci: bump actions/cache from 4 to 6
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-08-24 11:11:25 +00:00
Joseph MattielloandClaude Opus 5 4fa775d5cb feat(test): add a GitHub release-asset backend to the ROM store
At 22 MB a private GitHub repo is simpler and free, and the earlier "avoid
GitHub" note did not apply to it: that was about LFS, whose bandwidth is sold
in $5/50 GB packs. Regular git operations and release assets are not metered
that way, and nothing here is near a limit (100 MB/file, 2 GB/asset).

A release ASSET rather than files committed to a repo: git keeps every version
of a binary forever, so re-dumping a 4 MB ROM would add 4 MB to every clone
permanently, while an asset is replaced in place. The whole Tier-1 set travels
as one zip -- nine assets would be nine round trips and nine chances to end up
half-updated, and at this size partial transfers buy nothing.

Backend is inferred: VJ_ROM_GH_REPO set -> github, else s3. Both stay
supported, because Tier 2 (GB-scale CD images) will want R2's economics where
asset and repo ceilings bite first.

CI passes both secret groups; whichever is configured wins. VJ_ROM_GH_TOKEN
must be a fine-grained PAT with read-only Contents on the ROM repo -- the
built-in GITHUB_TOKEN is scoped to THIS repository and cannot read another
private one, which surfaces as a 404 rather than a permission error, so it is
worth stating in the workflow rather than leaving someone to debug it.

Docs also record the non-cost risk: these are commercial ROMs plus a BIOS
image, a takedown strike lands on the account or org rather than the file, and
Atari SA does act on Jaguar IP -- so prefer a personal account over the org
that hosts the emulator itself.

Verified against a stubbed `gh` implementing the release verbs over a
directory: publish creates the release and uploads a 17.5 MB zip; cold fetch
unpacks and verifies all 9; warm fetch never contacts the release; a missing
token exits 77 naming the GITHUB variables (the first version printed the S3
ones, which sends a GitHub user hunting a credential that has no bearing on
the skip); and a tampered archive is rejected with "fetched archive does not
match the manifest", exit 1, rather than being left where the next run's fast
path would trust it.

That tamper case initially "passed" because the packed ROMs carry mode 444
from the read-only corpus, so the dd that was meant to corrupt one failed with
Permission denied and the test proved nothing. Chasing the wrong result to its
cause is what turned it into a real check.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-20 20:48:15 -04:00
Joseph MattielloandClaude Opus 5 28326f0ad6 feat(test): Tier-1 private ROM store -- manifest, fetch, publish, CI wiring
Most checks worth running here need a commercial ROM that cannot be
committed: the audio pair, the clipping sentinels, the #406 DRAM-scale gate,
blit memoisation, the lightgun probe, and any cachegrind A/B on a real game
rather than a GPU-heavy demo. Without them `make test` exits 0 having skipped
all of them -- the Skyhammer sentinel looked for "Skyhammer_(1999).jag",
matched nothing, and reported success for months.

The full corpus is ~24 GB. The subset the suite actually asks for is 9 files,
22 MB. test/roms/tier1.patterns is the source of truth and every entry in it
is copied from a real find-rom.sh call site in the Makefile or test/tools, so
the list cannot drift from what the tests request; adding a title without a
test that wants it just grows the download. 22 MB fits GitHub's 10 GB Actions
cache with four orders of magnitude spare, so steady-state egress is ~zero and
only a manifest change re-downloads.

scripts/rom-store.sh has four verbs -- manifest / publish / fetch / verify --
over any S3-compatible store (R2 recommended for zero egress; nothing is
R2-specific). Behaviour chosen against this repo's silent-skip history:

  * exit 77 = SKIP (no store configured), deliberately not 0, so a caller can
    tell "no store" from "store is broken". CI renders those as a notice and a
    warning respectively.
  * verify-before-rename: a download lands at <file>.part and is size- and
    SHA-256-checked before it takes the real name, so a corrupt transfer can
    never be reused as a valid cached copy.
  * fetch is idempotent and offline-clean: all-present-and-correct never
    contacts the store.
  * manifest and publish share one resolver over tier1.patterns. The first
    version had publish re-glob the manifest basename, which made
    "[BIOS] Atari Jaguar (World).j64" unfindable by its own name, because
    `find -iname` reads "[BIOS]" as a character class matching one of B/I/O/S.
    Caught by the round-trip test, not by review.

CI fetches into test/roms/tier1 (gitignored), caches on the manifest hash, and
sets ROMS_PRIVATE_ROOT only when the fetch produced a verified tree -- pointing
it at an empty directory would make find-rom.sh miss everything and turn every
ROM-gated check into a silent skip. Fork PRs have no secrets and keep skipping
exactly as they do today.

No credentials are committed and the script never creates a bucket, sets an
ACL, or prints a secret. The bucket must be private.

Verified end to end against a local MinIO S3 server in podman: bucket created,
all 9 files published, cold fetch + verify clean, warm fetch contacts nothing,
a deliberately corrupted file detected and re-fetched, missing credentials
exiting 77 rather than 0, every Makefile find-rom.sh pattern resolving against
the fetched tree, and finally the real tests passing against it --
test_audio_presence PASS on Iron Soldier and test_audio_clipping PASS on
Skyhammer (RMS 3083.3, 0.000% saturated), both loaded from the fetched copy.

Setup, daily use and the Tier-2/3 plan: docs/test-rom-store.md

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-20 20:40:59 -04:00
Joe Mattiello 3bb33e8740 Merge pull request #488 from libretro/feat/369-replacement-pipeline
feat(texreplace): texture replacement pipeline, tier 1 (#369 deliverable 2)
2026-08-18 00:50:03 -04:00
Joe Mattiello 394eda2d9e Merge pull request #483 from libretro/feat/481-voice-modem
feat(jerry): Jaguar Voice Modem emulation — Ultra Vortek netplay over netlink (#481)
2026-08-18 00:25:53 -04:00
Joseph MattielloandClaude Opus 5 34eaae89c5 feat(texreplace): texture replacement pipeline, tier 1 (#369 deliverable 2)
Present community pack art in place of a title's own blitter tiles.
Packs live in <system_dir>/vj_texpacks/<CRC32>/<hash16>.png -- the
same hashes dump mode writes, so authoring is: dump, redraw, move one
directory over.  PNG decode (chunk walk + miniz tinfl inflate +
unfilter, ~150 lines C89) happens ONCE at load into a host hash map;
never any file I/O at blit time.

Host-only, presentation-riding architecture: at launch the source
window is hashed with the shared texdump helpers and the destination
window is described pre-dispatch (the engines write back the pointer
registers as they run); the blit dispatches completely stock; post-
dispatch, every destination word that equals its captured source word
(the per-pixel straight-copy witness) gets pack RGB888 stored into
the shadow framebuffer, value-tagged.  The OP's existing shadow
presentation path shows the art.  The witness makes wrong models
harmless: transparent, shaded, or exotic pixels never store.

The emulated machine is untouched -- zero savestate fields, zero RAM
writes, zero bus-model time; savestates, rewind, run-ahead and
netplay are bit-identical with or without a pack.  Packs are true
color (no RGB->CRY quantization exists in the pipeline); pack alpha
< 128 keeps the stock pixel.  Statics fully reset in retro_deinit /
retro_unload_game (iOS never dlcloses), and pack teardown invalidates
the surface so orphaned entries cannot outlive their pack.

Option virtualjaguar_texture_replace: default disabled, visible only
when a pack directory exists for the loaded content, runtime-
toggleable.  Tier 1 covers 16bpp source tiles at 1x on straight-copy
blits presented via the CRY 16bpp OP path; indexed and >1x tiers are
designed in notes on #369.

Part of #369.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-17 23:49:00 -04:00
Joseph MattielloandClaude Opus 5 d30ea52e66 feat(jerry): emulate the Jaguar Voice Modem for Ultra Vortek netplay
New src/jerry/voicemodem.c: a virtual modem state machine in front of
the existing jlink transport (issue #481).  Speaks the protocol Ultra
Vortek's own driver expects (docs/voice-modem.md): $FFFF wake -> $B800,
command-word echoes, DTMF digit relay via $8A2n/$6800, $8100 carrier
query with the $A4FC status follow-up, $B1xx ring indications, and
$F0xx/$F301 data framing.  'Dial' resolves to the netlink TCP /
netpacket / loopback session; the two modem instances talk 2-byte
frames over that transport.  No voice, no telephony.

Selected by new core option virtualjaguar_uart_device (default jaglink,
unchanged behavior); JagLink/CatBox path is untouched apart from the
burst-end hook rename (JLinkTxBurstEnd) which the modem uses for its
end-of-packet marker.

Modem session state is deliberately NOT in savestates: it is host-side
exactly like jlink's sockets; loading a state mid-call behaves as a
pulled phone line (LOST PHONE CONNECTION, redial).  All statics reset
in VMReset, reached from retro_init/retro_deinit via JLinkClose (iOS
static-state rule).

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-17 22:50:00 -04:00
Joe Mattiello 997cb50e72 Merge branch 'develop' into feat/cart-bios-loader 2026-08-17 22:35:47 -04:00
Joseph MattielloandClaude Opus 5 aaeb3a03d6 feat(texdump): texture dump mode — capture, dedupe, PNG + manifest (#369)
Deliverable 1 of issue #369, implementing docs/texture-dump.md as
approved.  Every unique blit source tile is captured ONCE per session
at the shared launch site (beside BlitMemoLaunch, before fast/accurate
dispatch), keyed by FNV-1a 64 over
'VJTD'|ver=1|src_bpp|w:u16|h:u16|raw source bytes as stored (LE).
The palette is deliberately NOT part of the key: replacement happens
at blit time and the blitter never sees a palette — CLUT lookup is an
OP display-time concept, so palette is advisory manifest metadata.

Source-channel decode verified against docs/jtrm-blitter.md (B_CMD
bit 0 SRCEN reads A2, or A1 when DSTA2 bit 11); per-pixel addressing
transliterates the engines' PIXEL_OFFSET_* formulas.  SRCEN-gated;
windows over 1 MB or outside populated address space are skipped.

First sight renders a preview PNG via in-tree miniz
(tdefl_write_image_to_png_file_in_memory_ex, enabled by
MINIZ_DEFLATE_APIS in LIBCHDR_CFLAGS so the theos_ios path — which
bypasses the unity.o rule — gets it too) and appends a manifest.tsv
row through the libretro VFS under
<system_dir>/vj_texdump/<cart CRC32>/.  <=8bpp previews go through a
first-sight TOM CLUT snapshot (CRC recorded; later palettes append
sighting rows, never new files); 16bpp preview interpretation is the
new virtualjaguar_texdump_16bpp option (cry|rgb|both), visible only
while virtualjaguar_texture_dump (disabled default, runtime-toggleable,
no restart) is enabled.

Dedupe set: open-addressed uint64 table, 2^17 entries, allocated on
first enable, freed and every static reset in retro_unload_game /
retro_deinit (iOS cannot dlclose).  ZERO savestate fields, no
STATE_VERSION bump — the emulated machine cannot observe the feature.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-17 20:59:36 -04:00
Joseph MattielloandClaude Fable 5 2331e4a85b feat(core): external cart boot ROM loader with known-hash identification
Extends 'Cart BIOS Type' with a 'custom' value that loads a 128 KB boot
ROM from the system directory (jagboot.rom, boot.rom, boot0.rom, or a
named '[BIOS] Atari Jaguar...' file), identified by CRC32 against a
four-entry known-image table and falling back to the embedded Series K
image if nothing usable is found. Delivers the design agreed in issue
#469: list known BIOSes explicitly, then support a generic loader.

- src/core/biosdb.{c,h}: small CRC32 lookup table (Series K, Model M,
  Stubulator '93/'94) sourced from #469's hash-table comment; all four
  entries cross-checked against real dumps in this session
- libretro.c: load_external_cart_boot_rom() / try_load_cart_boot_rom_file()
  mirror the existing CD BIOS loader's search pattern; unrecognized
  images load anyway with a warning (custom images are the point)
- k/m selections and the jagboot_m.rom override are unchanged
- dist/info: new optional jagboot.rom firmware entry + notes line
- test/tools/test_biosdb.c: table-integrity unit test, no core needed
- test/test_cart_bios_loader.c: end-to-end retro_load_game() coverage
  (file present/absent, k/m unchanged, wrong-size rejection, filename
  search-order priority, and a real recognized-image round trip)

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-17 20:55:44 -04:00
Joseph MattielloandCursor 7c341df3ae fix(core): boot JaguarDemos BootIntros via real BIOS path
Auto-enable the cart boot ROM for jagcrypt/GPU-only carts under default
HLE, add embedded Model-M BIOS selection, and probe BootIntros in the
JaguarDemos suite with BIOS+PAL so 46/49 committed intros light up.

Co-authored-by: Cursor <[email protected]>
2026-08-17 18:01:06 -04:00
Joseph MattielloandCursor 7ab050928d fix(ci): compile CHD fixtures without make, restore ABI flush
The stamp-skip for standalone CHD goals was always true: an indented
$(if) left whitespace, so make test relinked production objects with
link-test.T and vjtrace was missing. Compile the CHD tools with $CC
instead, and fix chdman-tools.yml continuation indent.

Co-authored-by: Cursor <[email protected]>
2026-08-16 01:40:26 -04:00
Joseph MattielloandCursor 3f8b16648c test(cd): add standalone CHD fixture tests and run them on Windows CI
make test is skipped on MSYS2, so a libchdr-only binary plus the
committed synth CHDs now gates CHSE, TOC, and the ATARI header on
every host row. The core harness also checks gap silence and extract.

Co-authored-by: Cursor <[email protected]>
2026-08-16 01:29:50 -04:00
Joseph MattielloandCursor 540c483dae fix(cd): compile vendored libchdr on Clang C99 and MSVC CI
unity.c included stdio before libchdr_chd.c's fseeko feature tests, so
Clang -std=c99 failed implicit-decl. The MSVC cl.exe line was missing
the libchdr include path and unity.c. Align the shipped jagcd-chd-check
defines with the core and note how the synth fixtures were made.

Co-authored-by: Cursor <[email protected]>
2026-08-16 01:29:50 -04:00
Joe MattielloandClaude Opus 5 e6dbb11169 fix(build): flush all objects on a TEST_EXPORTS mode change (#454)
* fix(build): flush all objects on a TEST_EXPORTS mode change

TEST_EXPORTS=1 adds -DVJ_TRACE, so it changes object *content*, not just
the exported-symbol list. The mode-switch hook deleted the library plus a
curated VJTRACE_HOOKED_OBJS list of "objects that use vjtrace" -- and that
list had gone stale: src/tom/blit_memo.o was missing from it, so

    make TEST_EXPORTS=1 -j8
    make -j8

failed to link with "_vjtrace_emit, referenced from _BlitMemoLaunch in
blit_memo.o". The reverse direction was worse: it linked cleanly while a
stale object kept the no-op macro expansion, producing a traced build that
silently records nothing.

Delete $(OBJECTS) outright on a mode change instead. No list -- hand-written,
grepped, or -MD-derived -- can be trusted, because a file can pick up the
VJT_* macros through an indirect include. Cost is one full rebuild per flip
(~21s at -j8 without ccache); the curated list already recompiled most of
the heavy objects.

CLAUDE.md claimed the switch was handled automatically; that was only true
in the plain -> TEST_EXPORTS=1 direction. Rewritten to match.

Verified from clean in all four orders (TE=1->plain, plain->TE=1,
plain->plain, TE=1->TE=1): all link, and a rerun in either mode recompiles
zero objects (no churn). test/tools/vjtrace_selftest.sh passes after a
plain -> TE=1 flip, proving the ring actually receives events rather than
merely exporting vjtrace_*.

Co-Authored-By: Claude Opus 5 <[email protected]>

* fix(build): exempt `make -n` from the mode flush, gate the flip in CI

Two follow-ups to the object flush.

The parse-time $(shell) also fired under `make -n`.  That was tolerable
when the flush was nine objects; with the whole object list in scope a dry
run silently costs a full rebuild, so detect -n and skip it.  GNU make
encodes -n two ways: normally the short options collect into MAKEFLAGS'
first word with no leading dash ("n", "ns"), but under make 3.81 (stock
macOS) a long option empties that group and -n reappears as its own word.
Both are matched.  A false positive would skip the flush and bring the
stale-object bug straight back, so only the letter group is scanned --
long options and the `--`/NAME=value words carrying command-line variables
are dropped, and `make platform=android` cannot false-match.  Verified on
make 3.81 and 4.4.1 across -n, -j8 -n, -s -n, --dry-run and both orderings
with --no-print-directory: every dry run leaves .link-mode and the objects
untouched, every real build still flips.

CI built plain -> TEST_EXPORTS=1 (build step, then `make test`) but never
the reverse -- the direction that actually broke.  Add a final plain `make`
to the host rows of c-cpp.yml's build job.  It runs after the artifact
upload so it cannot hand a production-ABI library to the dlsym-based steps
or to the uploaded artifact.

Four-way matrix re-verified after the -n change: all orders link, reruns in
either mode recompile zero objects.

Co-Authored-By: Claude Opus 5 <[email protected]>

---------

Co-authored-by: Claude Opus 5 <[email protected]>
2026-08-15 01:02:59 -04:00
Joe Mattiello 622846a33b Merge pull request #334 from libretro/perf/blitter-t2k
build(msvc): select the SSE2 blitter for MSVC 2015/2017 x64 and x86
2026-08-06 00:57:59 -04:00
Joseph Mattiello 3c1bd23b84 ci(clang-tidy): don't lint the SIMD impl headers standalone
clang-tidy runs over PR-changed files, and this PR is the first to touch
blitter_simd_{neon,sse2}.h since the ops moved into them. Those headers
are include-only fragments -- each #errors unless blitter_simd.h pulled
it in -- so linting one directly can only fail, which is what happened:
"unknown type name 'BLITTER_SIMD_INLINE'" on the sse2 header and a
soft-float ABI #error out of arm_neon.h on the neon one.

The exclusion list already covered the arch .c files (only one arch is
ever built, so the other two aren't in compile_commands.json). Extended
it to the .h files, and to scalar for symmetry. c89-lint's per-arch pass
still covers all of them, compiling each the way the build does.

Verified against this PR's own diff: the two changed headers are the
only C files in it, so the job now takes the "no relevant C files
changed" path and exits 0.
2026-08-06 00:45:19 -04:00
Joseph Mattiello 2550f0468a build(android): give ndk-build a real blitter SIMD arch
jni/Android.mk builds its flag set from scratch (COREFLAGS) and never
passes $(CFLAGS), and under ndk-build none of Makefile.common's
detection inputs exist -- no `platform`, no `ARCH`, and $(CC) isn't a
triple it recognises. So every Android ABI, x86_64 included, fell
through to blitter_simd_scalar.c, and -DBLITTER_SIMD_<ARCH> never
reached the compiler either.

Map TARGET_ARCH_ABI to an arch before the include (arm64-v8a and
armeabi-v7a to neon, x86 and x86_64 to sse2, anything else left to
Makefile.common's scalar default), and add the define to COREFLAGS.
BLITTER_SIMD is reset first because APP_ABI=all re-includes this file
once per ABI in one make process -- a stale `neon` would otherwise try
to build the NEON source for riscv64.

No -msse2 for the x86 ABIs: the NDK x86 baseline is SSSE3.
No LOCAL_ARM_NEON: NEON is the armeabi-v7a default from NDK r21 and
r26 dropped non-NEON ARMv7 outright; CI pins r26d.

Makefile.common now keeps the define in BLITTER_SIMD_DEFINE and folds
that into CFLAGS, so Android.mk can take the one thing it needs without
inheriting host flags.

CI: the Android rows now assert both halves. The arch .c is checked by
name in obj/local/<abi>, and blitter.c's compile line is checked for
-DBLITTER_SIMD_<ARCH> via `ndk-build -n`. The second check is the
load-bearing one -- since each arch .c self-selects (27c59f2), a missing
define just makes blitter.c inline the scalar ops and link happily
against the neon/sse2 vtable. Nothing would fail; it would only be slow.
2026-08-06 00:25:02 -04:00
Joseph Mattiello 8e7eaeb5dd ci: give the SIMD blitter test its BLITTER_SIMD_<ARCH> define
The "Run SIMD blitter tests" step compiled blitter_simd_<arch>.c with no
BLITTER_SIMD_<ARCH> define. Since the ops moved into headers, that means
blitter_simd.h inlines the scalar ops and the arch header redefines
every one -- so the step failed on all seven native build rows (Linux
x86_64 GCC/Clang, Linux i686, Linux aarch64, macOS arm64, both MSYS2).
Same root cause as the msvc-check split in the previous commit.

Also gitignore /nul: the windows_msvc* Makefile blocks run
`reg query ... 2>nul`, which on a non-Windows host creates a file
literally named `nul`. Commit one and every Windows checkout dies with
`error: invalid path 'nul'` before it compiles anything -- which is what
took out both MSVC jobs on the first CI attempt here.
2026-08-05 23:36:38 -04:00
Joseph Mattiello 41fb59c90c ci(msvc): let cl.exe be the last command in each msvc-check step
`shell: cmd` has no `set -e` -- the step's exit code is the last
command's. Both steps ended with `echo ... passed`, which returns 0 over
any cl.exe failure, so the job could not go red. Dropped the echoes.

This also revises what the previous commit claimed: msvc-check wasn't
"broken but unpushed", it would have stayed green through the
duplicate-definition error it was about to hit.
2026-08-05 23:35:10 -04:00
Joseph Mattiello 86053a3579 build(msvc): select the SSE2 blitter for MSVC 2015/2017 x64 and x86
Makefile.common's SIMD selection block never matched a single MSVC
platform name -- they're all windows_msvc*, and ARCH is unset -- so
every libretro-buildbot MSVC core fell through to blitter_simd_scalar.c.
Measured cost of the scalar fallback (NEON vs scalar, accurate blitter,
Tempest 2000) is ~5% post-inlining.

Enabled for windows_msvc2015/2017 x64 and x86 via an explicit
allowlist rather than findstring, because several MSVC platform names
must NOT match: windows_msvc2017_*_arm/_arm64 have no x86 intrinsics,
and xbox1_msvc2003 is a Pentium III (SSE, no SSE2). windows_msvc2010_*,
2005_x86 and 2003_x86 also stay scalar -- msvc-check runs VS2022 and
can't verify VS2010's C89-only C mode or the _MSC_VER < 1700 path.
No -msse2 for any of them; the existing flag block already gates it off
for MSVC, which uses /arch:SSE2.

SSE2_SET64 now takes the two-halves _mm_set_epi32 form on 32-bit MSVC
as well as on _MSC_VER < 1700. _mm_set_epi64x's availability on x86
varies by MSVC version, and cl.exe lowers the 64-bit form to the same
thing there anyway.

Verification -- this is the part that matters, since the change puts
<emmintrin.h> intrinsics inside blitter.c on MSVC for the first time:

- msvc-check compiled blitter_simd_scalar.c and blitter_simd_sse2.c in
  one cl.exe run with no BLITTER_SIMD_<ARCH> define. Since the ops moved
  into headers, that is a guaranteed duplicate-definition error -- the
  job was already broken on this branch, just not yet pushed. Split into
  one run per arch, mirroring what Makefile.common builds. The SSE2 run
  compiles blitter.c with /DBLITTER_SIMD_SSE2, so the x64 leg exercises
  _mm_set_epi64x and the x86 leg exercises the _mm_set_epi32 fallback.
- c89-lint's new sse2 pass compiles blitter.c under -std=gnu89
  -Werror=declaration-after-statement with the SSE2 header inlined.
- make platform=... print-BLITTER_SIMD_SRC confirms sse2 for the six
  allowlisted targets and scalar for uwp_arm64 / msvc2010_x64 /
  msvc2005_x86 / xbox1_msvc2003.
- test_blitter_simd built -arch x86_64: 56103/56103 pass.

Not covered: windows_msvc2019_* matches no block in this Makefile at
all and falls through to the generic else -- a missing platform block,
not a SIMD-selection bug.
2026-08-05 23:35:10 -04:00
Joseph MattielloandClaude Fable 5 53003f0ba7 ci(clang-tidy): skip deleted paths when building the file list
The job derived its file list from git diff --name-only without
excluding deletions, so removing src/bios/jagstub1bios.h and
jagstub2bios.h in the dead-blob cleanup left clang-tidy trying to lint
two files that no longer exist.  It failed the whole job with
'no input files' and 'unable to handle compilation, expected exactly
one compiler job'.

The existing exclusion regex did not cover them either: it matches
src/bios/jag.*[.]c only, so the headers slipped through.  Rather than
widen that pattern, add --diff-filter=d, which fixes the general case
for any deleted file.

Verified against this PR's diff: the list drops exactly the two deleted
headers and keeps the eight that still exist.  Running clang-tidy over
those eight locally with --warnings-as-errors='*' exits 0 (5 warnings,
all suppressed as non-user code); note the local binary is clang-tidy
22 while CI uses the Ubuntu package, so versions may differ.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-27 03:10:37 -04:00
Joseph MattielloandClaude Fable 5 d0a5fc2974 chore(bios): drop unused jagstub1/2 boot ROM blobs
src/bios/jagstub1bios.c and jagstub2bios.c define jaguarDevBootROM1 and
jaguarDevBootROM2 (0x20000 bytes each as bin2c hex tables, ~776 KB of
source apiece). Nothing has ever referenced either symbol: the only hits
outside the files themselves were build plumbing (Makefile.common, the
MSVC compile check in c-cpp.yml, the c89-lint exempt pattern). They are
inherited from the original upstream port, not staged for pending work --
the active feature/jaguar-cd-support branch does not reference them
either, and its boot-stub work uses jagbios/jagcdbios/jagdevcdbios.

Removed the four files plus their Makefile.common and c-cpp.yml MSVC
entries, and dropped the now-dead `src/bios/jagstub*bios.c` clause from
scripts/c89-lint.sh and CLAUDE.md (the surviving `src/bios/jag*bios*.c`
pattern already matched those names, so the clause was redundant even
before this change).

jagbios.c (jaguarBootROM), jagcdbios.c (jaguarCDBootROM) and
jagdevcdbios.c (jaguarDevCDBootROM) are all in use and untouched.

Effect, measured on macOS arm64: __DATA,__data shrinks 1065456 -> 803312
bytes, exactly the 2 x 0x20000 the blobs occupied; the dylib goes
2240992 -> 1976704 (-258 KB). Release CI builds 16 platforms, so this is
~4 MB of shipped dead data.

Verified: `make clean && make -j` clean with no warnings; the symbols are
absent from the linked binary (nm); `make TEST_EXPORTS=1 test` exits 0
with zero failures. The blobs remain recoverable from git history if a
future dev-BIOS feature ever needs them.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-26 22:22:13 -04:00
dependabot[bot] 9e39c1d632 ci: Bump actions/checkout from 6 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-07-26 20:25:08 -04:00
dependabot[bot] 85d1731db3 ci: Bump codecov/codecov-action from 6 to 7
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 6 to 7.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-07-26 20:23:54 -04:00
dependabot[bot] 6280db377c ci: Bump actions/cache from 5 to 6
Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-07-26 20:23:17 -04:00
dependabot[bot] 12c37649c3 ci: Bump actions/checkout from 4 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-05-11 22:16:43 +00:00
Joe Mattiello e81d6453d5 Merge pull request #173 from libretro/dependabot/github_actions/develop/actions/upload-artifact-7
ci: Bump actions/upload-artifact from 4 to 7
2026-05-06 21:38:40 -04:00
dependabot[bot] eb21f3d733 ci: Bump codecov/codecov-action from 5 to 6
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5 to 6.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-05-07 00:38:56 +00:00
dependabot[bot] bcd078c67d ci: Bump actions/upload-artifact from 4 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-05-07 00:38:48 +00:00
816c26bae9 fix(libretro): reset statics in unload/deinit, add alloc NULL checks (#160)
* test(harness): add shared test harness and DSP audio diagnostic

Introduces test/harness/ — a reusable dlopen-based test library that
eliminates boilerplate for headless core testing. Includes a DSP probe
module and the first tool built on it (test_dsp_audio_diag) which
detects PC escapes, bank init failures, and silent LTXD output.

Adds DSPGetFlags() accessor and exports ltxd/rtxd for test builds.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(libretro): reset statics in unload/deinit, add alloc NULL checks

On iOS (and any host that cannot dlclose cores), stale static state
from a previous game session bleeds into the next retro_load_game call.
This caused EEPROM data crossover, stale video dimensions, and leftover
input mappings.

Changes:
- retro_unload_game: zero video/game dimensions, clear eeprom_dirty_cb,
  wipe eeprom_save_buf, reset input arrays and option flags.
- retro_deinit: free buffers and clear dimension/callback statics as a
  safety net for frontends that call deinit without unload.
- retro_load_game: fail gracefully if videoBuffer or sampleBuffer
  allocation returns NULL instead of crashing on memset/loop.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(review): address Copilot review feedback on PR #160

- harness.c: fix INT16_MIN undefined behavior in abs computation by
  promoting to int before negation
- dsp_probe.c: guard against underflow in dump_ram when address is
  below DSP_WORK_RAM_BASE (e.g., on PC escape)
- libretro.c: make retro_deinit reset all statics matching
  retro_unload_game for full iOS safety-net coverage
- Makefile: gate -ldl with $(filter Linux,...) for dsp-diag target
  (macOS provides dlopen in libSystem)

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(review): address second round of Copilot feedback on PR #160

- harness.c: move was_playing from static to harness_audio_stats struct
  so it resets properly between test runs in the same process
- harness.c: validate ftell/fread results in harness_load_rom to prevent
  UB on empty/unreadable files
- test_dsp_audio_diag.c: suppress dump-interval output in JSON mode to
  prevent invalid mixed output

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* test(eeprom): add lifecycle test for load/unload/reload cycles

Exercises the static-reset fixes from this PR by running 3 consecutive
load/run/unload cycles with EEPROM verification. Also addresses third
round of Copilot review: JaguarDone on retro_load_game failure paths,
retro_deinit calls JaguarDone as safety net, and missing
harness_shutdown calls in test_dsp_audio_diag error paths.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(test): re-resolve dlsym pointers after each core reload

harness_shutdown calls dlclose, invalidating any previously dlsym'd
function pointers. Re-resolve retro_get_memory_data/size after each
harness_load_core to prevent use-after-free on Linux (caught by ASAN).

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(test): suppress LSAN false positive on intentional ROM buffer leak

The harness intentionally leaks ROM data (VJ core keeps the pointer).
Add __lsan_default_suppressions to prevent LeakSanitizer from failing
the CI sanitizer job on this known-benign allocation pattern.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(test): use nested #if for __has_feature (GCC compatibility)

GCC doesn't define __has_feature as a function-like macro, so using it
in a compound #if expression causes a preprocessor error. Use nested
#if/#elif to check __SANITIZE_ADDRESS__ first (GCC), then
__has_feature(address_sanitizer) only when __has_feature exists (Clang).

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(review): cast dlsym results, remove unused flags field, drop stale (void)cfg

Addresses fourth round of Copilot review on PR #160:
- Cast DSPGetRAM/DSPIsRunning dlsym results to proper function pointer types
- Remove unused `flags` field from dsp_symbols (get_flags() is the accessor)
- Remove misleading (void)cfg suppression in frame_callback

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
2026-05-04 22:14:46 -04:00
Joseph MattielloandClaude Opus 4.6 fee02bb880 ci: exclude SIMD intrinsic files from clang-tidy on x86_64
blitter_simd_neon.c uses ARM NEON intrinsics that are unavailable on
the x86_64 CI runner. blitter_simd_sse2.c has similar issues under
ARM clang-tidy. Skip both from the clang-tidy changed-files scan.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-05-03 23:59:53 -04:00
Joseph MattielloandClaude Opus 4.7 cb385d60c2 ci: UBSAN ignorelist for src/m68000 + fix profiling.md
The first sanitizer run (now correctly linking with -shared-libasan)
caught real UB in src/m68000/readcpu.c:1004 -- "shift exponent -1 is
negative".  The UAE 68K core (machine-generated, ~1.8 MB cpuemu.c)
uses negative shifts intentionally; fixing requires patching the
generator upstream and is out of scope.  We treat src/m68000/ as
opaque per CLAUDE.md.

Add .ubsan-ignorelist with src:src/m68000/* and pass it via
-fsanitize-ignorelist so the sanitizer job isolates new bugs in OUR
code without burying the signal under upstream noise.

Also fix docs/profiling.md: SIMD is currently wired into the
ACCURATE blitter's pixel kernel (BlitterMidsummer2's DATA() macro),
not the fast blitter as the doc claimed.  Caught by the blitter
spike research agent.  Cross-references issue #124 for the SIMD
widening plan.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-01 02:55:05 -04:00