mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-08 17:30:20 -10:00
develop
91
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6072a8ca5e | ci(release): use quick downloads for tagged releases (#1187) | ||
|
|
5d0a42c027 | ci(release): simplify nightly downloads and signing notes (#1185) | ||
|
|
a314dd3aa4 |
ci: GL qualification: windowed boot + every shader preset on Linux, Windows x64/arm64 and macOS (#1155) (#1160)
* feat(app): --shader PRESET|off for one run, and a [shader] result line per chain `--shader PRESET.slangp` runs this one run through that preset and `--shader off` through none; neither is written back to config.ini (like --theme). A missing or malformed preset is a usage error (exit 2) instead of a plain picture with a toast. shaders.cpp now logs one stderr line when a chain is built or cannot be: [shader] <preset> built: GLSL 410 on OpenGL 4.1 [shader] <preset> refused: ... (the GL_ARB_arrays_of_arrays limit, macOS GL 4.1) [shader] <preset> failed: ... (librashader's message) so a CI run has a record of what the chain did (#1155 GL qualification). Refs #1155 Co-Authored-By: Claude Fable 5.1 <[email protected]> * ci: GL qualification workflow: windowed boot + every shader preset on Linux, Windows, macOS One reusable workflow, gl-qualification.yml, boots the desktop app windowed with a real OpenGL context on hosted runners and renders every bundled .slangp through librashader, keeping the screenshots. Legs: Linux (ubuntu-24.04, Xvfb + llvmpipe), Windows x64 (hosted, no vendor GL: the automatic bundled-Mesa relaunch of #1138/#1143 must fire), Windows arm64 (folds in windows-gl-test.yml, which is retired), macOS arm64 (Apple OpenGL 4.1; the 15 arrays-of-arrays presets are allowlisted as refused). Per leg: one build, cmake --install into stage/, vj_shader_tests gl run directly (exit 77 = skipped = failure here), then app/test/gl-qualification/gl_qualify.py launches --shader off and every preset with --screenshot and asserts exit 0, the last [gl] renderer line, a non-black PNG, a [shader] built line and a picture that differs from the plain one. Artifacts upload pass or fail; the job summary lists failures with the [gl]/[shader] log tail. Goldens are designed (app/test/golden/gl/<os>/<preset>.png, refresh_goldens input) but not gated. Triggers: pull_request on the GL/shader/packaging paths, workflow_call (release.yml requires it on tags and release/**), nightly schedule, workflow_dispatch. Closes #1155 Co-Authored-By: Claude Fable 5.1 <[email protected]> * ci(gl-qualification): judge shaders_gl after the preset loop, fail fast, time the x64 fallback First run (41565368188) findings: shaders_gl fails on llvmpipe and on Apple's software renderer (crt-royale renders black in the 128x64 test picture) and the step stopped the leg before any screenshot existed; the x64 automatic-fallback boot hit `timeout 120` where arm64 took 3 s; the arm64 shaders_gl could not load the upstream prebuilt librashader.dll (eager D3DX9_43.dll import, #1153/#1161). - shaders_gl runs with continue-on-error and a "shaders_gl verdict" step fails the job after the preset loop, so the screenshots and logs upload either way. - gl_qualify.py --fail-fast N (default 8): the loop stops after N consecutive failed presets and the summary says how many were not run. - Windows fallback step: timeout 300 s, elapsed seconds logged (automatic-seconds.txt). Co-Authored-By: Claude Fable 5.1 <[email protected]> * ci(gl-qualification): empty-array expansion safe for macOS bash 3.2 (set -u) Co-Authored-By: Claude Fable 5.1 <[email protected]> * ci(gl-qualification): Windows leg records the boot, runs timing diagnostics, gates the loop Second run (41567744247): the x64 automatic-fallback boot did not return within 300 s although the screenshot was written (frame 20 reached); arm64 does the same in 2 s. - the loader probe (context create/draw/destroy through the bundled Mesa) runs before the app boot, timed; - the boot step records exit code, seconds and each side condition (automatic-seconds.txt) instead of aborting at the first failed check; a verdict step fails the leg later; - when the boot did not come back, three bounded experiments (the Mesa-side child run directly, --emu-thread off, video.vsync=0 seeded) go to timing.txt; - the preset loop runs only once the plain boot works (500 launches of a boot that does not return are hours of nothing); per-launch timeout 150 s. Co-Authored-By: Claude Fable 5.1 <[email protected]> * ci(gl-qualification): known-broken allowlist for presets librashader rejects everywhere The second Linux run (41567744247) built and rendered 494 of the 500 bundled presets in ~13 minutes of wall time; the six nedi bilateral-variant presets (all but variant2) fail reflection with UnknownSemantics("play"): they chain vhs/shaders/vhs.slang, whose `play` sampler is an LUT the presets never declare. A preset bug, not a GL one, the same on every leg, so it gets the same treatment as the macOS refused set: an allowlist with the reason that may only shrink (a listed preset that builds is an error). Co-Authored-By: Claude Fable 5.1 <[email protected]> * ci(gl-qualification): verdict steps run after an earlier failed step too (always()) Co-Authored-By: Claude Fable 5.1 <[email protected]> * test(app): shaders_gl renders the bundled presets at 640x480, not the 128x64 pixel-check picture On llvmpipe (Linux) and Apple's software renderer (hosted macOS) crt-royale came out black in shaders_gl: its mask passes scale by 0.0625 of the viewport and the preset says "Safe for >= 341.333 horizontal triads at viewport size", so at 128x64 they are 8x4 px. The app at 960x760 renders it (gl-qualification run 41567744247, 76% of pixels differ from the plain picture). The pixel checks keep their 128x64 (an exact 4x of the 32x16 source); the bundled loop and VJ_TEST_PRESETS render at a real-world 640x480. Refs #1155 Co-Authored-By: Claude Fable 5.1 <[email protected]> * ci(gl-qualification): every Windows Mesa launch bounded; llvmpipe single-thread and softpipe probes Co-Authored-By: Claude Fable 5.1 <[email protected]> * ci(gl-qualification): own librashader artifacts, macOS from source and sized down, release staging excludes qualification artifacts Review (Codex P1/P2 on #1160) and run 41571727219: - librashader.yml gains an `artifact_suffix` input; this workflow calls it with `-glq`, so a release run (app-package.yml + gl-qualification.yml) uploads no colliding artifact names. - release.yml's two staging sweeps exclude artifacts/gl-qualification-* (screenshots, logs and the arm64 test zip are not release assets). - macOS leg: the from-source aarch64 runtime the package ships, not upstream's prebuilt 0.12.0 (crt-black_crush-koko.slangp fails to parse on the prebuilt, parses on the pinned source); 3 launches at once in a 480x360 window (small-window.ini via gl_qualify.py --seed-config): Apple's software renderer managed 205 of 500 presets in 150 min at 960x760 and 2 jobs. - refused-<os>.txt also covers presets the leg's GL cannot COMPILE (macOS: fsr and smaa+fsr need packHalf2x16, GLSL 4.20); a listed preset that builds is an error, the set only shrinks. Co-Authored-By: Claude Fable 5.1 <[email protected]> * test(app): gl_context_integration prints unbuffered stage markers (where does the x64 Mesa probe stop?) Co-Authored-By: Claude Fable 5.1 <[email protected]> * ci(gl-qualification): tracked known failures; the x64 exit hang counted, not masked; loop before shaders_gl app/test/gl-qualification/known-failures.txt lists one failure per line (<leg> <kind> <reference>); the workflow reads it and turns exactly that failure into a recorded pass with a note. Nothing else on the leg is forgiven, and removing the line makes the leg fail again. First entry: windows-x64 exit-hang (#1167), the app never exits on the bundled MSYS2 MINGW64 Mesa 26.2.3 llvmpipe (SDL_DestroyWindow/SDL_Quit hang after "context destroyed"; LP_NUM_THREADS=0 and softpipe exit at once, arm64 Mesa exits in 2 s). gl_qualify.py: --known-exit-hang <ref> polls the launch, kills its process tree 5 s after the screenshot is complete (IEND present) and counts the preset, noting the known failure in summary.md and results.json; a launch without a complete screenshot still times out and fails. The Windows boot step accepts exit 124 when the screenshot and the [gl] lines are there, the loaders step accepts it on PASS, and shaders_gl passes without its final OK line only when nothing failed or skipped and its last check (the broken shader) ran. Every leg runs the preset loop before shaders_gl: on the macOS software renderer a SIGALRM-killed GL process left the next window creation hanging. shaders_gl's bundled loop and VJ_TEST_PRESETS render at 384x288 (crt-royale's mask passes need >= 341 px; 640x480 exceeded a 600 s alarm on Apple's software renderer), the macOS alarm is 1200 s. gl_context_integration prints unbuffered stage markers so a log shows where a probe stops. docs/agent/testing.md describes the lists. Co-Authored-By: Claude Fable 5.1 <[email protected]> * ci(gl-qualification): capture waits for core frames; unbuffered shaders_gl; macOS 640x480 + capture diagnostics Run 41727418224 ( |
||
|
|
e64e1bebee |
Merge pull request #1173 from libretro/claude/1150-azure-artifact-signing-50ffc2
ci(release): Authenticode for the Windows core DLLs, cue2cdi and the jagcd tools (#1150) |
||
|
|
92b6fa382d |
cart identity: backfill hdr_byte0 as the loader maps it; fail unchecked audits (#976)
Review fixes for PR #1170. * cart_matrix_identify.py recorded the file's first byte for images the loader maps elsewhere, so --check would mismatch fresh probe-stamped rows (Codex). It now follows ParseFileType's order: ABS/COF/JAGR/DRI headers are RAM programs (cart slot untouched, $00); a $400-marker image is a cartridge (its own byte 0); an Alpine image ((size + 8192) a whole MiB, or the $802000 header-stub shape, ported) reads the loader's $FF fill. The two shapes that depend on the raw-binary scorer (exact MiB without the marker, e.g. Rayman Demo #973, and headerless images) take the mapping from the row's own BIOS column: a run where the boot ROM ran was a cartridge mapping, one without a boot phase was not; when the column says neither, hdr_byte0 is left out for the probe to fill and --check skips only that field. Re-stamped the committed matrix: 85 rows change byte 0 only (Rayman Demo and BadCode4 (rom) to $FF, the RAM programs to $00); no reject label changes; cells byte-identical; --check clean against the private corpus. * --check exited 0 when a row's file was missing or empty, or when it verified nothing (Codex). It now exits 1 on any of those, and the backfill exits 1 when it could not stamp a row. --check compares field by field instead of the whole stamp string. * The probe's identity path dlsyms FindRomIdentifier and FindVerifiedRomVariant, which were not in the wide test ABI, so a real sweep would have stamped filedb=0 on every row (found while checking Kimi's live-core concern). Added to link-test.T and exports-test.list, and cart_boot_matrix.sh now refuses a core that lacks any identity symbol. Kimi's ordering concern itself does not hold: harness_load_rom runs retro_load_game, which loads the image (JaguarLoadFile) before probe_identity reads it; the probe now says so. * The $F6..$FE byte-0 range cites its source (JaguarCartNeedsBIOS and LooksLikeBjlRamImage in src/core/file.c, #784/#785 corpus survey). Tests: scripts/test_cart_matrix_identify.py (byte-0 mapping per loader path, row-driven decisions, stamp format, field compare, end-to-end backfill + check, and failures on mismatch / missing / empty / nothing checked). Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
7b46632305 |
site: derive BIOS_REJECT and black-video labels by cause (#976) (#1170)
The compatibility page printed "Not yet verified: BIOS_REJECT" for the real boot ROM's red "Jaguar" reject screen (#852/#744: an observed, authentic outcome, not a testing gap) and "video undetermined" for GAME_CODE + black, which read as broken. Every cell label is now derived from row data, nothing per title: * BIOS_REJECT: filedb FF_BAD_DUMP, or a verified same-title sibling (filedb FindVerifiedRomVariant, or another dump of the title that boots under the real BIOS at the same size) -> "altered or bad dump (a verified dump boots)" (badge `info`); byte 0 outside $F6..$FE -> "Unsigned image: the real boot ROM does not accept it", "(works in HLE)" only when the HLE column is GAME_CODE; byte 0 in $F6..$FE with no sibling -> "(dump cannot be verified)"; no identity data -> the bare fact. Never "Not yet verified". * GAME_CODE + black: "Runs; no picture in the test (may be audio-only or need input)"; in the BIOS column with no boot phase (bios_ran=0): "Boot ROM not used (RAM-loaded executable)". * ? (trap_loop): "Known issue: crashes at start (illegal instruction)" when the notes name the $1000 ILLEGAL guard, else "(68K exception loop)". * Rows with the same CRC32 and verdicts collapse into one (.jag/.rom twins and same-byte copies filed under two names), listing every name; filedb names are preferred for display with the file titles beneath. Data path: cart_boot_probe.c prints the image identity after the run (size, the core's jaguarMainROMCRC32, byte 0 at $800000 as the boot ROM sees it, the filedb row and its verified sibling); cart_classify.sh turns it into a per-row `<!-- id: ... -->` stamp that cart_boot_matrix.sh writes beside the build stamp; build_site.py and matrix_diff.py strip it like the build stamp. test/tools/cart_matrix_identify.py backfills or --checks the stamp from the files alone (ports the copier-header and Alpine rules), and is what stamped the committed matrix: 155 rows, --check clean, cells and build stamps byte-identical, matrix_diff "no backward moves". Tests: scripts/test_build_site_cart.py (one case per label, twin collapse, display names, end-to-end parse/render; discovered by make test and the two site workflows via test_*.py), test_matrix_diff.sh gains the identity stamp cases. Site rebuilt from the fixture data: 146 rows, 0 "Not yet verified", 4 bad-dump / 2 unverifiable / 2 unsigned reject labels. Closes #976 Co-authored-by: Claude Fable 5.1 <[email protected]> |
||
|
|
8e7fc92296 |
ci(release): Authenticode for the Windows core DLLs, cue2cdi and the jagcd tools (#1150)
The same .github/actions/windows-sign composite action app-package.yml uses, dropped into
the two remaining Windows jobs, both already on windows-latest (x64):
- release.yml `build`, Windows rows: after strip and before the upload, dist/ is signed
(virtualjaguar_libretro-windows-{x86_64,i686}.dll, cue2cdi-windows-x86_64.exe; the
-debug.tar.gz is not a PE file). The job grants id-token: write; a Windows sidecar
records the outcome like the macOS rows do.
- chdman-tools.yml: dist-jagcd/ is signed before the zip (chdman.exe, jagcd-chd-check.exe);
the three AZURE_* secrets join the workflow_call list (release.yml inherits), the job
grants id-token: write, and a Windows sidecar is uploaded next to the macOS one.
- Gate: release.yml now passes WINDOWS_EXPECT=signed under the same six-value condition as
WINDOWS_APP_EXPECT, so every Windows file is expected signed once the Azure values exist.
- Docs: README table rows for cores/tools, quota figures (~40 per app-package run, ~45 per
release run), release-process bullets; the composite's `signed` output description.
After this the only remaining step for #1150 is the maintainer's Azure enrollment.
Refs #1150
Co-Authored-By: Claude Fable 5.1 <[email protected]>
|
||
|
|
fcef4ca28e |
Merge pull request #1169 from libretro/ci/1150-azure-artifact-signing
ci(app): Azure Artifact Signing for every Windows binary; retire the SignPath stub (#1150) |
||
|
|
d7482ba3ec |
ci(app): Azure Artifact Signing for every Windows binary; retire the SignPath stub (#1150)
Every exe and dll in the Windows app packages (virtualjaguar.exe, librashader.dll, the software-opengl/ copy and the bundled Mesa DLLs, x64 and arm64) and both Inno Setup installers are Authenticode-signed with Azure Artifact Signing: SHA-256, RFC 3161 timestamp from timestamp.acs.microsoft.com, OpenID Connect login (azure/login), no certificate material or client secret anywhere. - .github/actions/windows-sign: composite action (azure/login v3.1.0 and azure/artifact-signing-action v2.0.0, both pinned by commit) that decides from its inputs whether it can sign, prints a ::notice:: and does nothing otherwise, signs, then re-reads every file with Get-AuthenticodeSignature and fails when one is not Valid and timestamped. - app-package.yml: the Windows leg becomes build (native runner per arch, install tree -> intermediate windows-stage-<arch>) -> windows-package (ONE x64 job for both architectures: the signing action does not run on Windows ARM runners, its dlib is x64/x86 only) -> windows-smoke (native runner per arch, Authenticode re-checked on the DOWNLOADED artifact, portable zip, silent install/uninstall). SignPath stub (inputs, secrets, env, steps) removed; secrets are now AZURE_CLIENT_ID / AZURE_TENANT_ID / AZURE_SUBSCRIPTION_ID plus the variables AZURE_SIGNING_ENDPOINT / AZURE_SIGNING_ACCOUNT / AZURE_SIGNING_PROFILE. - app.yml and release.yml grant id-token: write to the package job; app.yml passes the three secrets so same-repo PR packages are signed like a release. - Release gate: a second expectation, WINDOWS_APP_EXPECT, for the app zips and installers; release.yml passes `signed` when all six Azure values exist. The core DLLs, cue2cdi and the jagcd tools keep EXPECT_WINDOWS (unsigned) until their jobs sign too. The zip check now covers every PE file inside, summarised in one cell. stage.sh skips the windows-stage-* intermediates. - app/packaging/windows/verify-signatures.ps1: the shared check (files, folders, zips; -Expect signed|unsigned|report; table in the job summary). - package.sh: the local sign hook signed the x64 installer name for both arches. - Docs: app/packaging/README.md (secrets/variables, layout, Mesa decision, quota, the one-time Azure setup incl. roles and federated-credential subjects), docs/release-process.md. - release-packages.cjs: the three new jobs join the completeness list (15). Refs #1150 Co-Authored-By: Claude Fable 5.1 <[email protected]> |
||
|
|
498199a51d |
ci(app): Flatpak aarch64 leg with the shader runtime (#1156)
The `flatpak` job of app-package.yml becomes a matrix over the CPU: x86_64 on ubuntu-24.04 (unchanged: same bundle name, same artifact `app-package-flatpak`) and aarch64 on GitHub's hosted ubuntu-24.04-arm runner, producing VirtualJaguar-<ver>-linux-aarch64.flatpak as `app-package-flatpak-aarch64`. Both legs are native: the Flathub CI image ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-25.08 is a multi-arch index (amd64 + arm64), and the freedesktop 25.08 runtime and the rust-stable SDK extension ship for aarch64 on Flathub, so the Rust librashader module is compiled by the aarch64 Rust toolchain, not under QEMU. A step refuses a non-native host (uname -m must match the leg) so emulation can never creep in silently. The manifest needs no change: cargo-sources.json vendors the whole Cargo.lock (every crate of every target), `cc`, `libc`, `getrandom` and `cfg-if` are the only platform-sensitive ones and all are pure-Rust or host-compiled, and the tree has no `ring`. Per leg, the job now writes the wall time of the build, the runner and whether the flatpak-builder cache was warm to $GITHUB_STEP_SUMMARY (the issue's acceptance item), and the smoke test gains an arch check on the installed ref and on platform.machine() inside the sandbox before the same ctypes ABI check (librashader.so loads by bare name, ABI 2) that the x86_64 leg already ran. Registered the new asset everywhere the x86_64 bundle was named: the nightly release body table (release.yml), the PR package comment (app.yml), the Download page classification (build_site.py), the package tables in app/packaging/README.md and docs/release-process.md, the site's app page, and the release signing gate's hermetic test (test/release/test_release_scripts.sh now stages both bundles and asserts each is a `flatpak`-class row). verify.sh itself classifies `*.flatpak` by extension, so the gate expects the aarch64 bundle with no further change. Co-Authored-By: Claude Fable 5.1 <[email protected]> |
||
|
|
1773c623dd |
fix(release): skip per-artifact .info copies; never publish a dry run; bound the gate
- stage.sh ignores the virtualjaguar_libretro.info every build artifact carries (the Windows ones differ by CRLF) and writes the shipped .info from the checkout, as before. - The release and nightly jobs refuse to publish when the run is a dry_run dispatch, whatever the branch (Codex P1). - verify-signatures and stage get timeout-minutes: 30, so a hung check cannot hold the develop nightly, which waits for the gate to end but ignores its verdict (Codex P2). Co-Authored-By: Claude Fable 5.1 <[email protected]> |
||
|
|
4d217a3d45 |
fix(release): stage.sh accepts byte-identical duplicate assets
Every build job uploads its dist/ tree, and dist/info/virtualjaguar_libretro.info is in the repository, so each artifact carries the same .info file. The duplicate-name guard tripped on that in the first dry run; identical copies are now skipped and only differing files under one asset name fail. Co-Authored-By: Claude Fable 5.1 <[email protected]> |
||
|
|
313a18d70b |
ci(release): attest, sign and gate every release asset (#1151, #1152)
One `stage` job in release.yml now flattens the artifacts, writes SHA256SUMS.txt, signs it with the project minisign key (SHA256SUMS.txt.minisig; public key docs/release-signing.pub once the maintainer generates it) and attests build provenance for every file with actions/attest-build-provenance. A `verify-signatures` gate on a macOS runner then checks each asset against its expected signing state (scripts/release/verify.sh: macOS Developer ID + notarized, Windows `unsigned` until #1150 flips one line, AppImage and checksum signatures once their public keys are committed, attestation on everything), strict on tags and release/**, warn-only on develop so an outage cannot withhold the core nightly. Its per-artifact table (signed, notarized, attested) replaces the signing prose in the release body and the job summary. The macOS and AppImage signing steps write signing-status sidecars for the facts the gate cannot re-derive from a file (Apple's ticket on a bare Mach-O). AppImages: make-appimage.sh passes --sign --sign-key to appimagetool when VJ_APPIMAGE_SIGN_KEY is set (app-package.yml imports APPIMAGE_GPG_PRIVATE_KEY into a throwaway GNUPGHOME; the passphrase goes through APPIMAGETOOL_SIGN_PASSPHRASE). appimagetool 1.9 has no verifier, so app/packaging/linux/verify-appimage.py re-hashes the image with the signature sections zeroed and runs gpg --verify. Installers: tools/install.sh and install.ps1 verify SHA256SUMS.txt's minisign signature (pinned key) or, with gh, its attestation before trusting it; a bad one aborts, a release that predates signing warns; --no-verify / -NoVerify skips the step. Both hermetic suites cover it with fake minisign/gh. New release-scripts.yml tests the stage/sign/verify scripts (Linux also signs a real AppImage). A `dry_run` dispatch input on release.yml runs everything up to the gate on any branch and publishes nothing. Docs: key generation and secret commands in app/packaging/README.md (SignPath "dormant" text removed), release-process.md, SECURITY.md, the site's Download page (public key once committed), the nightly tracker text, and the stale "signing service" known-issue line in the v3.11.0 notes. Closes #1151 Closes #1152 Co-Authored-By: Claude Fable 5.1 <[email protected]> |
||
|
|
287bb30190 |
site, installer: fetch the install scripts from master, not develop (#1087)
The site's one-liners pointed at develop, so any installer change went live to users the moment it merged, before a release. Point every installer URL at master: the site's RAW_URL and source links, the README, the scripts' own usage text, the nightly notes, and install.sh's RAW, which --auto-update re-fetches. Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
caca12ad71 |
feat(cheats): CI-generated community cheat packs on the project site (#1071, part 1) (#1073)
* docs(cheats): community cheat store design and plan (#1071) Refs #1071 Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(cheats): map printf_notify to app toasts Refs #1071 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(cheat-import): BigPEmu API map; int_full settings omitted; printf_notify ignored Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(cheat-import): refuse side effects in message args; scope Unsupported.call to API gaps Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(cheat-import): committed cartridge identity table (hashes only) scan_cartridges.py walks a ROM directory (read-only) and writes a sorted FNV1a64 -> CRC32 table with size, title and name; no ROM bytes or paths. load_cartridges rejects duplicate fnv/crc, bad hex and format != 1. The generated cartridges.json (152 rows) lets CI map community script hashes to the CRC32 the app binds packs to without any ROMs. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(cheat-import): community cheat pack generator (#1071) build_packs.py converts every pinned community C cheat source with convert.py and writes the site/cheats tree: per-cartridge .cht packs (one per CRC32, sources merged, identical cheats deduped, same-name cheats suffixed), manifest.json, the unsupported.json worklist with per-call API usage, index.html and README. Deterministic, bounded fetch of one codeload tarball per source, --source-dir for local checkouts, --summary and --bump for the bump workflow. sources.json pins jguffer/BigPEmu_Scripts at 2421150. Co-Authored-By: Claude Opus 5.5 <[email protected]> * data(cheats): generate community packs from jguffer/BigPEmu_Scripts@2421150 23 cartridge packs (72 cheats, 9 partial) plus the unsupported.json worklist (55 entries), manifest and index, generated with build_packs.py from the pinned codeload tarball; byte-identical to a local checkout run. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(cheat-import): worklist unmatched literals beside a hit; bound fetch errors and member count - identity row for table-missing literals in a file that also has a hit - http.client.HTTPException during fetch is a FetchError (exit 2) - tar expansion cap counts a header block per member and caps member count - Collector uses the api_map given to build() Co-Authored-By: Claude Opus 5.5 <[email protected]> * data(cheats): regenerate for generator fix (unmatched-literal identity rows) Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(site): publish community cheat packs under /cheats/ build_site.py copies the committed site/cheats tree verbatim; check_site.py gains check_cheats (manifest format/sha256/bytes/CRC guard, unlisted .cht, unsupported.json, index.html links, 256 KiB manifest and 1 MiB tree limits) with unit tests. pages.yml watches site/cheats and tools/cheat-import. The converter README now documents the author's permission, the credit, the sources -> cartridges -> packs workflow and how to add a source; the community_test harness and its fixtures are removed (the committed packs and the regeneration diff are the conversion contract). Co-Authored-By: Claude Opus 5.5 <[email protected]> * ci(cheats): regen-diff job, concurrency, weekly pin-bump PR cheat-import.yml: add site/cheats/** to the path filters, a per-ref concurrency group, drop the community_test.py fetch step (the files are gone), and add a packs job that regenerates twice, diffs the runs and the committed tree, flags unmapped BigPEmu calls, then runs the site-check tests and build_site + check_site. cheat-packs-bump.yml: weekly (Mon 06:23 UTC) poll of each pinned github source; on movement, bump the pin, regenerate, validate, force-push the fixed branch cheats/pin-bump and open or refresh a develop PR (validation failures head the body instead of being dropped), then dispatch cheat-import.yml on the branch because GITHUB_TOKEN PRs do not fire pull_request workflows. test_committed_sources no longer pins the commit/date value: every bump PR would otherwise fail the unit tests; only the shape is asserted. Refs #1071 Co-Authored-By: Claude Opus 5.5 <[email protected]> * ci(cheats): gate bump workflow to the upstream repo, drop bot trailer Only libretro/virtualjaguar-libretro opens bump PRs (the Provenance-Emu fork runs crons too), with a 30 minute job timeout. The bot-authored bump commit no longer carries a Claude co-author trailer. Refs #1071 Co-Authored-By: Claude Opus 5.5 <[email protected]> * ci(cheats): pin pycparser, force LF, add job timeouts Pin pycparser==3.11 (newest release that installs on the Python 3.10 CI leg; 2.22, 2.23, 3.0, 3.11 and 3.12 all regenerate byte-identical packs). Add a root .gitattributes forcing LF for the generator and generated tree so pack bytes cannot drift by checkout. Give both jobs a 20 minute timeout. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(cheats): dedupe pack header title, refuse control-character paths The pack's first line printed the title twice when the cartridge name began with the title. Print the name alone in that case. Fetch and local-dir reads now fail closed on any path containing a control character, since names can reach ::warning:: annotations and PR bodies. Co-Authored-By: Claude Opus 5.5 <[email protected]> * data(cheats): regenerate packs with deduplicated header title Line 1 of each pack no longer repeats the title when the cartridge name already starts with it. Regenerated from the pinned source commit; two runs and a --source-dir run are byte-identical. Co-Authored-By: Claude Opus 5.5 <[email protected]> * ci(cheats): even indentation on a bump-workflow continuation line editorconfig-checker wants multiples of 2 in workflow files. Refs #1071 Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
304867bd48 | fix(ci): scope checkout ownership trust for core artifact staging | ||
|
|
3c683169ef | ci(core): share compile-only release builds and preserve CI artifacts | ||
|
|
744e9a3860 |
feat(site): serve the Jaguar CD art as one zip and list the CD database (#1013) (#1016)
build_site.py now writes "thumbnails/Atari - Jaguar CD.zip" next to the mirror. It holds only Named_*/*.png under an "Atari - Jaguar CD/" folder, so it unzips straight into RetroArch's thumbnails directory and can be offered to libretro-thumbnails#141 as-is. Entries are sorted with a fixed timestamp, so the same images always give the same bytes. The core info now lists "Atari - Jaguar CD" alongside "Atari - Jaguar", so once libretro-super builds that rdb, RetroArch offers this core for discs a scan matches. The mirror README records what still has to land upstream: the thumbnail repo, the rdb build line, and a RetroArch scanner fix (every Jaguar CD track is AUDIO, and the scanner only hashes data tracks). Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
5aafd86cb8 |
fix(site): honest cart-matrix labels for boot-ROM rejects, crashes and RAM programs (#988)
The compatibility page printed "Not yet verified: BIOS_REJECT" for the real boot ROM's red reject screen, "Not yet verified: ? (trap_loop)" for titles whose 68K crashed at start (#975), and "Boots and runs" in the real-BIOS column for RAM-loaded programs that never touch the boot ROM. classify_cart_stage now maps: - BIOS_REJECT -> plain "Rejected by the boot ROM" (authentic, #852/#744) - ? (trap_loop) -> bad "Known issue: crashes at start" - ? (bios_trap) -> bad "Known issue: crashes after the boot ROM" - GAME_CODE in the BIOS column with no handoff in its notes (cart_classify.sh bios_ran=0) -> "Boot ROM not used; runs" - GAME_CODE + black video -> "Runs; no picture in the test" The legend now explains these labels instead of quoting a stale count ("14 rows ... v3.8.0"). On the current matrix this is 202 "Boots and runs", 81 "Boot ROM not used" (all homebrew/BJL/Alpine images), 14 "crashes at start", 9 "Rejected by the boot ROM" and 4 "no picture". Splitting rejects into bad dump / unverifiable / unsigned, and merging .jag/.rom twins, needs new probe fields (crc32, header byte 0, filedb flags) and stays on #976. Refs #976 Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
3d9401f035 |
feat(site): per-page share cards and a favicon set from the app icon (#985)
* feat(site): per-page share cards and a favicon set from the app icon Every page shared one og:image, the Cybermorph true-color comparison, which reads as a small low-contrast strip in a link preview; the site had no favicon at all. - scripts/make_share_cards.py (Pillow + rsvg-convert) composes two 1200x630 cards: home.png, eight game captures from site/assets/home/ over the icon and wordmark, and app.png, zoomed crops of the desktop app captures (library, Rayman under the Lottes CRT shader, palette debugger, light theme). It refuses to write a card that is not exactly 1200x630 or is 1 MB or more. It also renders favicon.svg/.ico, icon-192/512.png and an opaque full-bleed apple-touch-icon from app/packaging/icons/virtualjaguar.svg. Outputs are committed; the stdlib-only site build only copies them. - build_site.py: SHARE_DEFAULT plus a per-page SHARE_IMAGES map replace the global SHARE_IMAGE; each page gets its own og:image, twitter:image, alt, type, width and height (read from the PNG), and the home JSON-LD image follows. Every page head links the favicon set; favicon.ico is also copied to the output root. - check_site.py: twitter:image must equal og:image, the width/height tags must match the PNG, the card must be under 1 MB, and every page must carry rel=icon and rel=apple-touch-icon links. Closes #984 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(site): opaque touch icon without the outline ring; list the new checks in check_site's docstring Refs #984 Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
edeaa81051 |
docs(site): app page download picker; lightbox and red hover on every screenshot
The app page's Get it window is now the Download page's widget, limited to the app: Nightly / Latest / older releases that ship it, platform and arch icons, file sizes, SHA256SUMS link, and the one-line installer preset to --what app (download.js composes --what and --channel, and finds its root by the .dl class so it runs on both pages). The hard-coded 3.9.0 file table is gone; the per-package notes move to an About the packages window. Captured frames (figure.shot) on the enhancements and app pages open in the gallery lightbox and take the same red hover outline as the app gallery cards. gallery.js reads a shot's window title and figcaption for the lightbox caption. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
a0e79a5d9e |
docs(site): bring the site up to v3.9.0 and generate the release log (#964)
* docs(site): generate the release log from docs/RELEASE_NOTES_v*.md
The home-page release log was hand-written and stopped at v3.7.0. It is now
the {{RELEASE_LOG}} placeholder: build_site.py reads every v3+ notes file
(summary = the file's opening tagline paragraph), shows the newest five rows
and folds the rest, and pages.yml rebuilds when a notes file changes. New
releases appear with no page edit. Also refreshes the home page for v3.9.0
(app feature list, savestate v17, RetroAchievements, debug tools, GameDrive
SD card) and drops the stale 'until the first release with the app exists'
installer sentence.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(site): bring compatibility, enhancements, performance and why-this-core up to v3.9.0
Compatibility: current as of v3.9.0; drop known problems that v3.7.1 and
v3.8.0 fixed or closed (Club Drive, #800 carts, Music Demo BIOS, Hover Strike
real-BIOS run-ahead, #737, #633); add the v3.7.1 and v3.8.0 fixes (older
ones folded); add JaGol, Ruiner Pinball and Doom link-play notes; replace the
'matrices predate v3.7.0' caveats with the current ones (14 BIOS_REJECT rows).
Enhancements: compatibility-class presets and load notice (v3.8.0), clock
percentages and idle-skip at any RISC scale (v3.9.0), DSP runaways in the
watchdog. Performance: GPU/DSP Pipeline Timing name. Why this core: the
desktop app, app RetroAchievements modes, GameDrive SD card.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(site): hide release-log rows newer than the latest release; trim home page duplication
Release notes land before the tag exists, so with release data in hand the
generated log drops versions above releases/latest (offline builds show all).
The home page keeps the app feature list in one place and shortens the
installer paragraph, so it is no taller than before despite three new rows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
|
||
|
|
8ea18fde7b |
docs(site): redesign the Download page around a release picker (#958)
* docs(site): redesign the Download page around a release picker One Downloads window instead of five sections. A Nightly / Latest / Older releases picker (buttons plus a select, progressive enhancement) switches between per-release panels rendered by build_site.py; without JS the latest release shows with links to the GitHub release list. App and core builds are grouped by platform then arch with generic inline-SVG glyphs; the core's long tail is collapsed per platform. The installer one-liners become a compact tabbed widget that adds --channel for the chosen release, and tools get a proper table. fetch_releases also fetches the last 10 tagged releases; --releases accepts an optional older[] list. Adds scripts/check_downloads.py, an offline fixture under scripts/testdata and a CI step that builds from it. Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(site): end the releases.json fixture with a newline (EditorConfig) Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
58c6aa835a |
site(download): label the Flatpak bundle and the Windows arm64 packages (#947)
v3.9.0 added VirtualJaguar-X.Y.Z-linux-x86_64.flatpak and Windows arm64 installer/zip; the Download page would have listed them under "Other" by file name. The asset filter admits .flatpak and the three get rows like the rest. Co-authored-by: Claude Fable 5.1 <[email protected]> |
||
|
|
05c0d7f422 |
Merge master (v3.9.0) back into develop
# Conflicts: # README.md # site/pages/app.html |
||
|
|
7ceacba7e2 |
site: Download page with direct links to every release file (#930)
* site: Download page with direct links to every release file A new Download page, second in the nav, lists the desktop app packages, the RetroArch core for every platform, the tools and checksums, and the nightly, each as a direct link to the asset. The home page's Download button and the app page's "Get it" point at it; the home page no longer hardcodes "latest: v3.7.0". build_site.py takes the release data as --releases FILE or fetches it with --releases-from-github (stdlib urllib, GH_TOKEN/GITHUB_TOKEN sent when set), and dies on a failed fetch so a publisher keeps its previous deployment. With neither flag the page links the release pages instead, so local builds and check_site.py keep working offline. pages.yml uses the fetch flag and gains a 6-hourly schedule, since a new release changes the app package names. The v3.8.0 release carries no app packages, so until the next tag the app section links the nightly's, with a note saying so. Co-Authored-By: Claude Fable 5.1 <[email protected]> * site: signing text matches CI: macOS notarized, Windows installer not signed yet The Download page and the app page say the macOS app is Developer ID-signed and notarized (the release workflow's notarize step now runs and passes), with no right-click > Open caveat. The README and app page from #929 said the Windows installer is signed as well; the SignPath steps are still skipped in every release run, so both now say it is not code-signed yet and SmartScreen may warn. The "SIGNING: confirm" markers are resolved and gone. Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Fable 5.1 <[email protected]> |
||
|
|
a3c50fecd7 |
docs(site): add privacy policy and terms of service pages
Plain-language Privacy Policy and Terms of Service for the Virtual Jaguar app and core, with footer links. The pages are built and in the sitemap but stay out of the primary nav (LEGAL_PAGES in build_site.py). Refs #833 Co-Authored-By: Claude Sonnet 5.5 <[email protected]> |
||
|
|
69bc186110 |
feat(site): host the Jaguar CD thumbnail set under /thumbnails/
Mirrors the libretro-thumbnails URL layout (<System>/<Type>/<Name>.png) so the desktop app can use the same URL builder with a different base. libretro-thumbnails has no Jaguar CD repository yet (libretro-thumbnails#141). build_site.py copies site/thumbnails verbatim. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> |
||
|
|
c1a712cd5c |
docs(site): add the app page with screenshots; README app section and install options
The desktop app ships in v3.8.1. README: "Two ways to play", install bullets for the app and the install.sh / install.ps1 one-liners (--what core|app|both, nightly channel), and a "The desktop app" section with screenshots, features and the package table. app/README.md gains a Netplay section. Site: new App page (Get it, library, shaders, controllers, cheats / achievements / states, netplay), nav entry, home-page hero button and "Two ways to play". Ten screenshots in site/assets/ (1280x860 PNG8, 1.1 MB total) captured from the app on real carts; the netplay shot has the host's address blanked. Refs #833 Co-Authored-By: Claude Sonnet 5.5 <[email protected]> |
||
|
|
dc34531117 |
feat(jaggd): GameDrive SD-card file API against a host folder (#783)
The GameDrive's file/dir functions were stubs returning -1, so software that keeps its data on the SD card (JagOS 'Spot', streamed assets, screenshots) could not run. Blob functions 9-16, 20-23 and 26 now work against <save dir>/jaggd-sd (system dir fallback), per the new core option virtualjaguar_jgd_sd (Read/write default, Read-only, Disabled). - Call path: each of those blob slots is one backdoor write, $C000 | function. The core services the call during that write: it reads the 68K's d0/d1/a0/a1 and leaves the result in d0 via m68k_set_reg. Safe because the 68K's JERRY writes are synchronous and the stub's move.w #imm,abs.l never touches d0; only a 68K writer triggers it. Paths and buffers move through JaguarRead/WriteByte, bounds-checked. - src/core/jaggd_fs.c: FatFs semantics, matching RetroHQ's published gdbios.h (facts only, none of their files vendored): FA_* open dispositions, write/create needs a writable card, short reads succeed, FILINFO records big-endian (22/278 bytes). End of directory returns -1 with a zeroed record. Sandbox: .., drive letters, FAT-invalid chars and symlinks refused; components matched case-insensitively. - Savestate v17 (first bump of the v3.8.0 cycle): trailing "JGF1" chunk with card-relative path, access and position per handle. Restore reopens with the original access only (never create/truncate/append), re-seeks a handle already open on the same file, closes deleted ones. - Blob grows 208 -> 338 bytes (limit 510). Tests: test_jgd's probe now drives the file API through the real blob (size 7 and the read buffer are values only the host can produce; sandbox escape returns -1); new host test test_jaggd_fs covers every disposition, read-only cards, sandbox refusals, FILINFO layout, directories, the handle limit and savestate restore. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
ef04767b03 |
ci: keep GitHub's error text when the link mutation fails
The exit-3 path redirected the mutation's stderr to /dev/null, so the job summary said 'see above' with nothing above it -- the PR #725 case ("exceeds manual reference limit") would have lost its only clue. Refs #730 Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
5471000d3d |
ci: make pr-link-issue.sh and release guard use explicit signals
Task #730: scripts/pr-link-issue.sh now signals distinct exit codes for its two failure modes, allowing the pr-issue-link workflow to classify failures properly without pattern-matching GitHub's error prose: - exit code 2: no link tag found in the PR body (author action needed) - exit code 3: tag found but GitHub API mutation failed (API error) Updated .github/workflows/pr-issue-link.yml to match on exit codes instead of error message text. This decouples workflow logic from GitHub's wording, making it robust to future API message changes. Human-readable error messages are still displayed to users; they are now inputs to the message selection rather than the selection criterion. Task #2: Fixed the build target count in the release.yml comments and guard job. The guard job and release notes incorrectly stated "16 targets" when the matrix actually builds 24 targets (22 build configs + vita-build + switch-build). Updated all references to match the real count, with a comment noting the calculation. - pr-link-issue.sh: exit codes (2 for no tag, 3 for API failure) - pr-issue-link.yml: check exit codes instead of pattern-matching errors - release.yml: 16 -> 24 targets (guard message, comments, release body text) - All YAML syntax validated; script syntax validated with bash -n Closes #730 Co-Authored-By: Claude Haiku 4.5 <[email protected]> |
||
|
|
ad371077da |
docs(site): audit pages for v3.7.0, add known-problems list, new hero
Compatibility leads with current known problems and recent fixes; the generated boot matrices are collapsed and caveated. Remove stale claims (16 platforms, savestate v12, Team Tap/Pro Controller unsupported, idle-skip off by default, no frameskip, tier-3 textures unimplemented, Iron Soldier Fast-blitter wireframe). Tighten prose on every page. Replace the 326x240 Cybermorph hero with a 2x2 mosaic of real core frames (2x resolution, true colour), 127 KB WebP. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> |
||
|
|
e671501f85 |
ci(release): hook + release guide cover the fallback header; checker nits
Review on #795: - Copilot: the pre-commit hook ran check-info-version.sh only for dist/info/ or Makefile changes; version_fallback.h now triggers it too. - Copilot: docs/release-process.md still said to bump only the Makefile and check only .info; both places now name all three copies. - Kimi: missing-file check for all three inputs, set -eu, and the version parse stops at the first closing quote (a trailing comment on the #define no longer leaks into the value -- tested with a mismatch). Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
b563b2c38b |
ci(release): check-info-version.sh checks all three version copies
The version lives in the Makefile, dist/info and src/core/version_fallback.h.
The script compared only the first two; v3.6.1's stale fallback header was
caught by a different CI job ('duplicated lists in sync') instead. Now it
fails naming the fallback file when that one drifts. Verified: passes on
develop (all v3.6.1), fails with exit 1 when the fallback says v3.6.0.
docs/agent/github.md's release step lists all three.
Fixes #734
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
||
|
|
cd74a2f6d8 |
site: link the Provenance family in the footer and declare the publisher
Adds a "More from Provenance" footer line (Provenance, iCube, iFly, wiki) and a publisher Organization reference in the SoftwareApplication JSON-LD, pointing at the Organization node published on provenance-emu.com. jaguar.provenance-emu.com is hosted by Provenance, so the sibling sites now link both ways. Co-Authored-By: Claude Fable 5.1 <[email protected]> |
||
|
|
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]> |
||
|
|
b43fb0dc55 |
perf(voicechat): SSE2 saturating mix for VoiceChatMixInto (#695)
Port the NEON fast path from #666 to x86 SSE2. New src/jerry/voicechat_simd_sse2.h mirrors voicechat_simd_neon.h: _mm_adds_epi16 is the exact vqaddq_s16 counterpart (per-lane s16 saturating add), applied to 4 interleaved stereo pairs per step via unaligned movdqu load/store. Dispatch in voicechat.c is an #elif on BLITTER_SIMD_HAVE_SSE2 after the NEON branch, with the identical mixed-fits-in-int16 gate (the SIMD sat-add only matches the scalar (int)add + clamp when both addends are int16). c89-lint.sh: skip the new header in the default pass and add voicechat.c to the per-arch pass, which is the only host-side syntax coverage the non-native fragment header gets. The clang-tidy exclusion regex from #687 already covers src/jerry/*_simd_sse2.h. |
||
|
|
8d2828ce27 |
perf(shadowfb): NEON-vectorize hi-res Stage-1 store and line resolve (#692)
ShadowHiresStoreCry's box-replication fill and ShadowHiresLineFromRAM's
2x2 block scatter ran as runtime-N scalar loops with per-subpixel
branching (blk / rdst / rblk re-tested every iteration); on Missile
Command 3D at 2x the pair was ~8.5% of profile samples. New
include-only header src/tom/shadowfb_simd_neon.h (BLITTER_SIMD_HAVE_NEON
capability guard, ARMv7-portable intrinsics only -- struct-returning
vzip_u16, no A64-only forms) replaces them at N=2 with straight-line
16-byte stores; the {value16, frac16} pattern is built as uint16 lanes
so byte layout is endian-correct by construction. Scalar loops stay as
the fallback for non-NEON builds and any future N != 2.
Byte-identical: frame_hash_ab CSVs vs develop are identical for 600
frames of yarc.j64 and Missile Command 3D at 2x; hires_box_check passes
on yarc (MC3D presents 652x480 in the headless harness on develop too,
so its box gate is inapplicable there -- pre-existing, unchanged).
|
||
|
|
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.
|
||
|
|
6264f554e6 |
feat(debug): GDB remote stub Phase 2/3/4 -- breakpoints, GPU/DSP threads, docs (#685)
Phase 2: RSP ack/nak byte handling (the gap that stalled every real m68k-elf-gdb on qSupported), Z0-Z4 breakpoints/watchpoints, c/s/vCont, register and memory writes, the blocking halt loop (GDBHalt() freezes retro_run() in place until continue/step/disconnect), a client disconnect that disarms everything and resumes, and the virtualjaguar_gdb_wait / virtualjaguar_gdb_halt_timeout core options. Per-target armed counters (gdbArmed68K/GPU/DSP) plus a 256-entry direct-mapped PC cache gate the three per-instruction hooks (M68KInstructionHook, GPUExec, DSPExec) -- one load and one never-taken branch when nothing is armed, never patching emulated memory. Memory watchpoints ride jaguar.c's pre-existing (but previously dead) bpmActive/bpmAddress1 pair via the new GDBMemWatchHit(), single-slot by design. Phase 3: GPU and DSP as GDB threads 2/3 with a custom org.atari.jaguar.risc target description (R0-R31=0-31, PC=32, FLAGS=33, fixed forever) served over qXfer:features:read, thread enumeration/selection (qfThreadInfo/Hg/Hc/qC), and monitor commands (disasm/regs/halt/trace/watch) via qRcmd. The RISC disassembler is shared, not duplicated: src/debug/gdbdisasm.h is used by both gdbtarget.c (live) and the refactored test/tools/gpu_disasm_dump.c (offline). Phase 4: docs/gdb-stub-guide.md, a user-facing guide covering attach, the frontend-freeze halt model, the RISC disassembly gap, and the symbol-level (not source-level) debugging ceiling. New accessors added or un-gated from VJ_TRACE-only in gpu.c/dsp.c (GPUGetControl, GPUSetReg/PC/Flags, DSPGetPC/Control, DSPSetReg/PC/Flags, DSPGetReg/GPUGetFlags un-gated) so the stub can read and write GPU/DSP state in every build. Verification: make -j4 clean; make TEST_EXPORTS=1 test green (0 failures, 75 suites, including 45/45 gdbstub protocol tests); c89-lint clean on every touched src/ file; savestate digests byte-identical across stub-disabled / enabled-unattached / enabled-attached-zero-breakpoints (test/tools/gdb_determinism_probe); a live RSP client verifies the ack byte and no-ack transition end to end (test/tools/gdb_attach_probe.py) and a real Z0 breakpoint halting real (synthetic, injected) 68K execution at the exact armed PC (test/tools/gdb_breakpoint_probe.py). Perf A/B NOT measured this pass: the host stayed under sustained unrelated load (other processes, load average 10-26 over repeated checks) for the whole session. VJ_GDB_STUB_DISABLE_HOOKS is wired at all three hook sites so the A/B can be run later on an idle machine -- see the PR body. Refs #652 |
||
|
|
baf682de80 |
perf(voicechat): NEON saturating mix (#666)
Vectorize VoiceChatMixInto's add+clamp over each 6-pair upsample group with vdupq_n_s16 + vqaddq_s16 so far-end mix matches the scalar [-32768, 32767] clamp. Scalar path remains for non-NEON builds, tail pairs, and mixed values that do not fit in int16. |
||
|
|
6edac105d4 |
perf(op): NEON fast path for 16bpp fixed-bitmap phrases (#672)
Store a full 4-pixel phrase with vld1/vst1/vbsl when the scalar loop would have done four hi/lo byte stores with no RMW, REFLECT, or shadow-FB hooks. Byte-identical to the existing walk on LE hosts. |
||
|
|
94b99cd687 |
perf(tom): NEON scanline converters for direct/24bpp/RGB16 modes (#670)
Vectorize the pwidth_scale==1 hot paths with ARMv7-portable NEON (vld1q/vrev16q/vshrq/vmovl and vld4_u8), keep scalar tails, and add VJ_RESTRICT so autovec still applies. BGEN stays compiler-vectorized. |
||
|
|
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 |
||
|
|
f119191031 |
test(tools): audio_wav_dump -- raw WAV capture from a savestate, plus sample cadence
Every audio tool in the tree reduces the stream to a statistic: RMS, peak, saturation density, non-silent fraction. Those catch loud-broken and silent-broken audio, and they are all blind to a *spectral* complaint. The AvP report in #633 -- "a strange parasitic noise running during gameplay" -- is a tone sitting under otherwise-correct audio, and test_audio_clipping passes it cleanly (0 saturated samples, RMS 1652.7, no hot frames). This writes the samples out instead, so they can be listened to or run through an FFT. It reuses the shared harness for its CLI, which is what gets it to the scene: --load-state reaches gameplay from a RetroArch save, where these reports come from and where no other audio tool can currently go. The harness installs its own audio callback during harness_load_rom(), so ours is installed after and the core keeps whichever was set last. It also reports the per-frame sample cadence, because the other half of a "noise on Windows/Linux but not here" report is the frontend resampler: a core emitting a jittery sample count per frame can whine on a strict resampler and sound clean on a forgiving one, from an identical stream. Ruling that out is a one-line answer instead of a theory. (AvP measures min=max=800, spread 0 -- rigid, so not that.) Used to produce the #633 evidence: 92% of energy below 200 Hz, 0.001% above 8 kHz, no discrete peak above 2 kHz -- i.e. no macOS repro, which is what moved that issue from "patch the DAC" to "find out the reporter's platform". Hand-built like its siblings, so it joins the c89-lint exempt list for the same stated reason (builds against test/harness/, which is outside $INCLUDES; C99 harness) and .gitignore's hand-built-binary block: cc -O2 -Wall -std=c99 -I. -I./test/harness -I./libretro-common/include \ -o test/tools/audio_wav_dump test/tools/audio_wav_dump.c \ test/harness/harness.c -ldl -lm Refs #633 |
||
|
|
7b66c02018 |
ci(pr-issue-link): create the link from a body tag instead of demanding a manual step (#644)
* ci(pr-issue-link): create the link from a body tag instead of demanding a manual step
Every new PR here went red on creation for a step that cannot be performed
until after the PR exists: the linked-issue check failed, and the fix was a
manual click in the Development panel that does not re-trigger the job.
The premise behind that was stale. This job's own comment -- and
docs/agent/github.md -- asserted "there is no public API to create that
link, so it has to be done in the UI" and "don't burn time on a gh flag or
GraphQL mutation". The mutation exists:
addCloseIssueReferences -- "Adds one or more pull requests as manually
linked closing references on an issue."
That is exactly the Development-panel relationship, and it is idempotent
(verified against PR #641/#632, already linked: re-running changed nothing;
and end-to-end against #642/#633 by unlinking, re-linking from a body tag,
and confirming the result).
So the job now makes the link rather than complaining that nobody did:
* scripts/pr-link-issue.sh <pr> [issue] [--repo o/n] does the work and is
runnable by hand. No issue argument -> scan the PR body for a tag.
Already linked -> no-op, so it is safe to run unconditionally.
* The workflow runs it and only fails when there is no tag to act on. Its
failure summary now says to edit the description, because `edited`
re-runs the job and it links then -- no push required.
Accepted tags, case-insensitive:
Closes #123 / Fixes #123 / Resolves #123 / Refs #123
<!-- link-issue: #123 --> invisible in the rendered body
The hidden form wins over the keywords, so a PR can discuss one issue in
prose and link a different one deliberately. The keywords still do nothing
on GitHub's own side here -- GitHub honours them only when the PR targets
the default branch, which is `master` while PRs target `develop`. They are
markers for this job.
Security: the job runs pull_request_target with a write token, so it checks
out the BASE ref (actions/checkout's default for that event) and never the
PR head -- the script it executes is the trusted copy from the target
branch, not one the PR could have edited. The only untrusted input is the
body, parsed to an integer and validated to be an existing issue (not a PR)
before use. Permissions widened by exactly one: issues: write, which is what
the mutation needs.
Refs #643
* fix(pr-link-issue): ignore link tags inside code blocks
Caught by the feature's own PR. #644 documents the accepted tag syntax with
a fenced example:
Closes #123 Fixes #123 Resolves #123 Refs #123
<!-- link-issue: #123 -->
The scan matched that example before reaching the real `<!-- link-issue:
#643 -->` at the bottom of the same body, and linked the PR to issue #123 --
a closed OP performance spike with nothing to do with it. Any PR that
explains this feature would have done the same, which makes it exactly the
kind of body the parser has to survive.
Strip fenced blocks and inline code spans before scanning. Anything inside
code is a sample, not an instruction.
Verified: re-running against #644 now yields "PR #644 -> linked to #643",
and a synthetic body with an inline `Refs #111`, a fenced `Closes #222` and
`<!-- link-issue: #333 -->`, plus a real `<!-- link-issue: #444 -->` outside
any code, selects 444.
Refs #643
|
||
|
|
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]> |
||
|
|
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]> |
||
|
|
f7ba81eef0 |
tools: add a cross-platform core installer, and link it from the site
Users are hand-picking .so files off the releases page and getting it wrong.
The failure is silent: RetroArch says only "Failed to open libretro core".
The specific trap is that builds are named by board AND word size -- rpi4 is
32-bit armhf, rpi4_64 is aarch64 -- and `uname -m` is the wrong way to choose.
Raspberry Pi OS shipped a 64-bit kernel with a 32-bit userland for years, so
uname reports aarch64 while every binary on the system is 32-bit. The core has
to match RETROARCH, not the kernel.
tools/install.sh reads the word size and machine out of the frontend's own
binary (ELF class + e_machine on Linux, the running slice on macOS) and aborts
before writing anything if the downloaded core does not match.
Finds the core directory by reading RetroArch's own libretro_directory out of
retroarch.cfg rather than carrying a per-distro table of paths. That is
authoritative everywhere, including the read-only-root distributions where a
guessed path would simply be wrong, and it does not rot when a distro moves
things. RetroPie is the one special case, because it keeps each core in its own
package directory that emulators.cfg points at instead.
POSIX sh, safe to pipe into a shell. Offers latest/nightly/pinned-tag, an
optional systemd-timer or cron auto-updater, --dry-run, --list, and --yes.
Prompts read /dev/tty because stdin is the script when piped; with no tty it
takes defaults rather than hanging.
Testing found two bugs worth recording:
- Under zsh -- macOS's default shell, so a likely invocation -- `for a in
$archs` does not word-split, so the universal-binary slice picker never
matched and silently chose x86_64 on an Apple Silicon Mac. Verified: sh
said macos-arm64 and zsh said macos-x86_64 on the same machine. Now a
split-free `case`, and all four shells agree.
- Piped into a shell, $0 is the shell name and not a file, so --help ran
`sed` on "sh" and the re-run hints printed "sh --dest ...". Both now go
through a SELF that falls back to the curl one-liner.
Verified: ELF detection agrees with file(1) on all 6 real Linux builds across
i386/arm/x86_64/aarch64; all 14 board+wordsize combinations map to assets that
exist in the release; syntax clean under sh, bash, zsh and dash, which all
choose the same build; discovery finds the real RetroArch on this machine by
config; install, re-install with backup, and the post-install load check all
work end to end -- RetroArch loaded the freshly downloaded core.
build_site.py gains {{RAW_URL}}, derived from REPO_URL so the curl one-liner on
the site cannot drift from the repository the rest of the page points at.
Supersedes the RetroPie-only script this replaces, which was never released.
Co-Authored-By: Claude Opus 5 <[email protected]>
|