77 Commits
Author SHA1 Message Date
Joe Mattiello 8700a9336c feat(cheats): convert nested constant password tables (#1120) 2026-10-29 20:06:57 -04:00
Joe Mattiello 82d18eb5d4 feat(cheats): ignore unreferenced static constant tables (#1119) 2026-10-28 22:22:12 -04:00
Joe Mattiello 615a5588d4 feat(cheats): accept constant locals in registration metadata (#1113)
* feat(cheats): accept constant locals in registration metadata

* fix(cheats): inspect macro expansions in purity checks
2026-10-28 18:58:57 -04:00
Joe Mattiello ee4952328d feat(cheats): convert read-only integer lookup tables (#1097) 2026-10-26 00:53:41 -04:00
Joe Mattiello c1653da97b feat(cheats): expand bounded integer settings into freeze choices (#1091)
* feat(cheats): expand bounded integer settings into freeze choices

* fix(cheats): retain cartridge identity in integer analysis failures
2026-10-25 22:23:21 -04:00
Joe MattielloandClaude Opus 5.5 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]>
2026-10-23 22:25:40 -04:00
Joe MattielloandClaude Opus 5.5 a1d9c4807a feat(app): Community cheats in the Cheats window (#1071, part 2) (#1076)
* feat(app): sha256 for verified downloads

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

* feat(app): CRC-keyed community cheat set in CheatManager

CheatManager loads <communityDir>/<CRC32>.cht for the running cartridge by CRC,
independent of the filename/title-matched database set. The pack is guarded by
vj_rom_crc32 like a revision-bound database set (unbound or mismatched: every
cheat unsupported plus a restriction), crc 0 (discs) never loads one.

Toggles persist under cp.<id> (database keeps on.<id>); ids are unchanged so an
updated pack keeps toggles for unchanged cheats and a changed cheat comes back
off, and cp.* keys that match no cheat are pruned on load. apply() orders
database, community (5000+i), then manual codes (10000+i). New public API:
loadChtText, setCommunityDir, reloadCommunity, communitySet,
removeCommunityToggles, CheatOrigin on isEnabled/setEnabled.

Also corrects two stale comments in cheats_test.cpp (sha256 vectors, linked
sources).

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

* fix(app): drop dead prune loop, document setEnabledByName fall-through

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

* feat(app): community cheat store service

CheatStore fetches the community manifest, installs a cartridge's pack after
verifying it (size, sha256, vj_rom_crc32 guard, parse), tracks per-pack state,
and feeds CheatManager. Network and worker are injected hooks; results are
applied on the main thread through pump().

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

* fix(app): harden cheat store against throwing hooks, racing remove, message floods

A throwing fetch hook can no longer leave stop() hanging (RAII job slot plus
catch); remove() refuses while an install for the same CRC is in flight; and
manifest messages are capped at 8 entries of 200 bytes.

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

* feat(app): Community cheats section in the Cheats window

Wires CheatStore into the app (#1071): the store, its one-request worker and
the http::get fetch hook (any status but 200 is an error, the store's cancel
flag is passed through) are set up in cheatsInit with downloads gated by the
single Library "Download art and cheat packs" switch (library.art, label
renamed) and cheats.community.url as the base URL override. The store is
pumped every frame, told of game load/close, and stopped (cancel first, then
the worker) before the cheat manager goes.

The Cheats window gains a "Community cheats" section between Database cheats
and Manual codes (install / update / remove / check states, credit line,
collapsed conversion notes, 256-write warning, hardcore line); both sections
share drawCheatRows, which now passes CheatOrigin. Buttons are disabled while
a request is in flight and stay live in hardcore, where only toggles are off.

--cheats-community-install is the headless twin (fetch the manifest, install
the loaded cartridge's pack); cheat_community_install runs it against a
loopback server serving site/cheats, then offline, then with nothing to
install from.

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

* docs(cheats): describe the community cheat store (#1071)

Privacy page: a row for the project website's cheat store (when it is asked,
what is sent: the pack index and the one pack named by the cartridge's CRC32,
nothing else) and cheats/community/ in the local-files list; the art row
follows the renamed "Download art and cheat packs" switch. App page, app
README, the converter README and WHATSNEW describe installing packs from the
Cheats window.

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

* fix(app): marshal cheat store game events to the main thread, report turned-off cheats

The store is main-thread only but cheatsAfterLoad / cheatsOnClose run on the
emulation thread in threaded mode. They now post the load / close
(CheatStore::postGameLoaded / postGameClosed, last post wins) and pump()
applies it before finished requests, so refresh(), announce() and the message
toasts stay on the main thread.

After an Update, the section says how many cheats that were on changed and
came back off (CheatManager::lastPrunedEnabled, CheatStore::turnedOff; cleared
by the next load or Remove). A store that was never reached says so instead of
"No community pack", and Check for updates is shown disabled rather than
hidden while checking or with downloads off.

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

* fix(app): keep an unreadable community pack removable, pump the store during a halt

The Cheats window derived Remove (and Update) from cm.communitySet(), so a
<CRC>.cht that exists but does not parse showed sidecar text with no way out.
Remove and Update now follow the store state alone; only the cheat rows need
the parsed set, and an unreadable or restricted pack gets a one-line notice.
The database "no set matches" status is dimmed when a community pack already
supplies cheats, a greyed Update says downloads are off, and haltedFrame pumps
the store so installs finish during a debugger halt.

Also documents the emulation-thread toast/notice contract in cheatsAfterLoad
and tidies style (comma-operator statements, std::set, include order).

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

* test(app): keep the community store fixtures byte-exact on Windows

The fixture pack's sha256 and size are pinned in the fixture manifest.
Git for Windows' autocrlf turned its LF into CRLF on checkout, so
cheats_unit failed verifyPack on every Windows app job. Mark the
fixtures -text so they are checked out exactly as committed.

Refs #1071

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

* fix(app): headless community install repairs a locally edited pack

--cheats-community-install skipped install() when the installed pack's
bytes no longer matched its sidecar (EditedLocally), then failed its own
Installed check, although the Cheats window offers Update in that state.
Install there too when the store offers the pack. The loopback smoke test
now hand-edits the installed pack and expects the command to replace it.

Refs #1071

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-23 03:23:05 -04:00
Joe MattielloandClaude Opus 5.5 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]>
2026-10-23 02:45:05 -04:00
Joe MattielloandClaude Sonnet 5.5 97cb2040d1 fix(app): Neuro follow-ups: release input on drop, bounded stop, hardcore block, indicators, privacy (#1025) (#1027)
- Agent: a dropped or failed link clears the queue and any hold_buttons (a held
  button no longer outlives the server); stop() clears stale events.
- WsClient: the name lookup runs on an abandoned-on-stop helper thread, so stop()
  never waits on getaddrinfo; reject masked server frames and reserved bits;
  1 MiB message cap; byte caps on the event and send queues; jittered backoff;
  a "://" in the URL path is not a scheme.
- RetroAchievements hardcore blocks Neuro (disconnect + release, Settings notice,
  toast); connects again when hardcore ends.
- Toasts on connect/disconnect and an "AI plays P1" chip on the background draw
  list; the server is told (and actions fail) while the in-game menu has the pads
  or P1 is a mouse.
- Settings > Neuro: URL field follows the config and applies on click-away; warns
  on a non-loopback ws:// server; settings are re-read 4x a second, not per frame.
- JSON: a lone high surrogate no longer swallows the pair behind it.
- privacy page row, docs/neuro.md (hardcore, indicators, emulation-thread note).
- tests: wrong Accept, oversize/masked/reserved frames, flood cap, hold released on
  disconnect, hardcore no-connect/release/reconnect, input gating, surrogates, URLs.

Co-authored-by: Claude Sonnet 5.5 <[email protected]>
2026-10-19 17:16:58 -04:00
Joe MattielloandClaude Opus 5.5 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]>
2026-10-17 17:54:03 -04:00
Joseph MattielloandClaude Opus 5.5 1aa8cba81b docs(site): compatibility page current to v3.9.1 (Xenowings, loader fixes)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 21:28:53 -04:00
Joseph Mattiello c569ae043a Merge fix/976-compat-labels into release/v3.9.1 (#988, compatibility labels) 2026-10-15 21:27:59 -04:00
Joseph MattielloandClaude Opus 5.5 6eba8375c6 fix(site): honest cart-matrix labels for boot-ROM rejects, crashes and RAM programs
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]>
2026-10-15 21:27:33 -04:00
Joe MattielloandClaude Opus 5.5 56f49a720c docs(site,app): every package bundles librashader; drop the arm64-only note (#986)
Since #955 and #953, librashader is built from source in CI, so the macOS
app (both slices), the AppImage, the .deb and the Flatpak all ship it,
and Windows already did. The app page still told Linux users to install
librashader themselves and warned that Intel Macs had no shaders.

app/packaging/README.md had the same stale "Known gap" bullet. The
rewritten Universal bullet had been appended to the end of the OpenGL
bullet instead of replacing it, so it is now moved back into place.

Refs #950

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-15 19:37:31 -04:00
Joseph Mattiello 22f31c606a Merge docs/app-page-librashader into release/v3.9.1 (app page shader notes) 2026-10-15 19:34:19 -04:00
Joseph MattielloandClaude Opus 5.5 bb050eb4d5 docs(site,app): every package bundles librashader; drop the arm64-only note
Since #955 and #953, librashader is built from source in CI, so the macOS
app (both slices), the AppImage, the .deb and the Flatpak all ship it,
and Windows already did. The app page still told Linux users to install
librashader themselves and warned that Intel Macs had no shaders.

app/packaging/README.md had the same stale "Known gap" bullet. The
rewritten Universal bullet had been appended to the end of the OpenGL
bullet instead of replacing it, so it is now moved back into place.

Refs #950

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 19:34:13 -04:00
Joseph Mattiello 4de6c5e451 Merge develop into release/v3.9.1 (#985; #982/#983 squash copies, already present) 2026-10-15 19:26:32 -04:00
Joe MattielloandClaude Opus 5.5 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]>
2026-10-15 18:41:09 -04:00
Joseph Mattiello a9efcfef47 Merge develop into release/v3.9.1 (#970, #978, #980)
site/pages/app.html: took develop's generated {{APP_DOWNLOADS}} picker (#970) over the cut's hand-edited 3.9.1 filename table; the picker reads file names from the GitHub releases.
2026-10-15 16:49:06 -04:00
Joseph MattielloandClaude Opus 5.5 4aa28cb759 docs(site): home page screenshot strip, 16 games, six across
Replaces the 2x2 hero mosaic with a sideways-scrolling strip of gameplay
frames (six visible on desktop, three on tablets, two on phones; scroll
snap, beveled scrollbar, Prev/Next buttons from gallery.js). Every card
opens in the gallery lightbox with the same red hover as the app page.

Twelve new captures from core 5d2ea0b8 at 2x internal resolution, true
colour and the Accurate blitter: Doom, Wolfenstein 3D, Missile Command 3D,
Atari Karts, Iron Soldier, Cybermorph, Super Burnout and Checkered Flag
from the headless harness with scripted input, and Tempest 2000, Raiden,
Defender 2000 and Ultra Vortek through crt-lottes, crt-aperture,
crt-easymode and crt-royale in the desktop app (slang-shaders e1d75632).
The four mosaic frames (Alien vs Predator, Iron Soldier, Skyhammer, Hover
Strike) stay as cards. WebP: 480px thumbnails, 960px full size.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 02:15:06 -04:00
Joseph MattielloandClaude Opus 5.5 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]>
2026-10-15 01:41:56 -04:00
Joseph MattielloandClaude Opus 5.5 4bcf285e31 chore: bump version to v3.9.1, add release notes
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 01:00:43 -04:00
Joe MattielloandClaude Opus 5.5 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]>
2026-10-15 00:19:20 -04:00
Joe MattielloandClaude Opus 5.5 5bbbc99de0 docs(site): refresh the app gallery (RetroAchievements art, Jaguar CD shots, gdb recipe, even grids) (#959)
* docs(site): refresh the app gallery: RetroAchievements art, Jaguar CD shots, gdb recipe, tighter layout

RetroAchievements: the grey badge squares came from the mock session itself,
not from a rendering bug. A --ra-mock session never uses the network
(updateImageNetwork() forces it off) and the image cache only reads
<data-dir>/ra-cache/, so a fresh capture data dir had nothing to draw. The shots
are retaken with that cache seeded from the public media.retroachievements.org
images of a real Jaguar set (Ultra Vortek, 18 achievements, in the demo
library), and a mock fixture dir holding the real public achievement list and
Jaguar / Jaguar CD game lists. Account, progress and unlock dates are invented;
no real account is involved.

Jaguar CD: discs boot and run in the current app, and box art now downloads
for the CD tab, so app_library_cd.png is retaken and a Blue Lightning CD
gameplay card is added.

Galleries now hold an even number of cards (Playing 8, Look & feel 8,
RetroAchievements & Discord 4 after dropping the empty-looking Profile card,
Developer tools 8) and the grid is fixed at 4 columns (2 below 60rem, 1 below
34rem) in a marked "/* app page */" block at the end of style.css, with
tighter gaps and captions and two-column bullet intros. Developer tools gains
the gdb recipe and a link to docs/gdb-stub-guide.md.

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

* docs(site): scope the app-page h3 spacing, fix CD art and BIOS wording, README alt text

The h3 margin override was site-wide; it is now limited to #gallery. The Jaguar
CD box art comes from the project's thumbnail mirror (libretro-thumbnails has
no Jaguar CD repository), and a CD boots without any BIOS file, so the captions
say that instead of naming libretro-thumbnails and the built-in CD BIOS.

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-15 00:05:54 -04:00
Joe MattielloandClaude Opus 5.5 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]>
2026-10-15 00:04:42 -04:00
Joseph MattielloandClaude Opus 5.5 76fcc8b3b0 docs(site): retake desktop-app gallery shots for themes, fonts, RetroAchievements, Discord and debug tools
New captures of the v3.9.x app (demo library, fresh temp data dir, no
accounts): Light and two custom themes over the same library view, the theme
editor, Settings > Interface and the font picker (installed fonts drawn in
their own typeface, favorites, recents, debugger font samples), the
RetroAchievements sign-in, This game, Jaguar games and Profile views from the
built-in mock backend, the Settings > Discord tab with Discord off, and the
palette, blitter log, texture browser and audio scope tools. Video settings
is retaken now that theme and font moved to the Interface tab.

The font shots come from the feat/app-font-picker branch (#943 was not merged
yet); the README row and app page get matching cards and captions.

Closes #938

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-14 12:46:33 -04:00
Joseph MattielloandClaude Opus 5.5 ce2038680d docs(site): privacy page — RA token lives in the OS credential store; fix app versions
The token moved to Keychain / Credential Manager / libsecret in #923 with a
config.ini fallback; the page still described plain-text storage. Discord and
the app shipped as 3.9.0, not 3.8.1.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-14 02:08:46 -04:00
Joseph Mattiello 05c0d7f422 Merge master (v3.9.0) back into develop
# Conflicts:
#	README.md
#	site/pages/app.html
2026-10-14 01:44:05 -04:00
Joe MattielloandClaude Fable 5.1 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]>
2026-10-14 00:59:54 -04:00
Joseph MattielloandClaude Sonnet 5.5 b63cb3a613 docs(site): app page screenshot gallery with a lightbox
Replace the long feature list with a responsive grid of screenshot cards
grouped by Library and artwork, Playing, Look and feel, RetroAchievements
and Discord, and Developer tools, each with a one-line feature summary.
Click-to-enlarge uses a small dialog-based lightbox in assets/js/gallery.js
(no library; Esc closes, arrow keys move, focus returns to the card).

Fresh captures of the v3.9.0 app (demo library, no personal data): library
tabs, resume prompt, state manager, controls overlay, input, cheats, video
settings and four debug tools including the CPU debugger halted. Adds
Windows ARM64 and Flatpak downloads, system requirements, Discord Rich
Presence and privacy/terms links; the signing text carries a SIGNING:
confirm marker.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-14 00:52:16 -04:00
Joseph MattielloandClaude Opus 5.5 ad23b8120e chore: version v3.9.0 (was v3.8.1)
The release grew from a fix release into the desktop app debut, debug tools and timing
work, so it ships as v3.9.0. Version bumped in all three places; release notes renamed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-14 00:33:51 -04:00
Joseph MattielloandClaude Sonnet 5.5 e22aa0a2c0 docs(site): app page CRT example with an aperture-grille shader, aspect-correct
Regenerate the plain / CRT pair from one saved game moment and one window
config (aspect fit, no integer scaling, no stretch; only shader.* differs),
and use the bundled crt-gdv-mini-ultra-trinitron preset instead of CRT-Royale's
default: visible vertical aperture-grille stripes, mild bloom, not too dark.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-13 23:51:45 -04:00
Joseph Mattiello d33053273f Merge remote-tracking branch 'libretro/release/v3.8.1' into m/native
# Conflicts:
#	app/CMakeLists.txt
#	app/README.md
#	app/tests/settings_reset_test.cpp
2026-10-13 22:17:06 -04:00
Joseph Mattiello 707f1eab57 Merge remote-tracking branch 'libretro/release/v3.8.1' into feat/app-native-os
# Conflicts:
#	app/CMakeLists.txt
#	app/src/achievements_ui.cpp
#	app/src/app.cpp
#	app/src/app.h
#	app/src/settings_reset_ui.cpp
#	app/src/ui.cpp
#	app/tests/settings_reset_test.cpp
2026-10-13 22:02:50 -04:00
Joseph MattielloandClaude Sonnet 5.5 a5a8049b3d fix(app): keep the RetroAchievements login token in the OS credential store
The token used to sit in plain text in config.ini. It now goes to the macOS
Keychain, the Windows Credential Manager or the Linux Secret Service (libsecret,
loaded with dlopen so there is no build or run dependency). A token already in
config.ini is written to the store, read back, then removed from the file
(atomic rewrite) at start-up in the first copy of the app; Sign out erases it
from both. Secondary copies (#902) only read the store and write it on an
explicit sign-in/out. Where no store answers the token stays in config.ini as
before, with a log note and a Settings hint. --ra-mock and headless runs use an
in-memory backend and never reach the real store.

Refs #836

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-13 21:55:40 -04:00
Joseph MattielloandClaude Opus 5.5 20af25086a docs(site): Discord Rich Presence ships in 3.8.1
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 21:42:22 -04:00
Joseph MattielloandClaude Sonnet 5.5 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]>
2026-10-13 21:41:06 -04:00
Joseph Mattiello 0f4883b770 Merge remote-tracking branch 'libretro/release/v3.8.1' into feat/app-ra-images-dashboard 2026-10-13 20:45:47 -04:00
Joseph MattielloandClaude Sonnet 5.5 3699e83895 docs(app): RetroAchievements pictures and dashboard; test fixes
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-13 20:33:48 -04:00
Joseph MattielloandClaude Sonnet 5.5 fa327302ca docs(app): platform integration - README, packaging file-type table, release notes; reset covers tray and keep-awake
Settings > Reset treats the two new Desktop settings as Storage keys, and the tray follows a reset.

Refs #833

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-13 20:22:05 -04:00
Joseph MattielloandClaude Sonnet 5.5 dc75f06d10 feat(app): library favorites, recently played and a save states tab
Star a game on its tile (or right-click > Add to Favorites); the games that boot
are kept under Recently played (last 20). Each list shows as its own tab, as a
strip at the top of the Cartridges and Jaguar CD tabs, both, or hidden. A new
Save states tab lists every game with states and the screenshot saved with each;
clicking one boots the game and loads it (or just loads it into the same game
already running). The tile context menu gains Play, Load state, favorites,
Remove from Recently Played, Show in Finder / Explorer and Copy path.

Favorites and recents live in library/user.tsv, written as atomic operations
(re-read, apply one change, rename) so several running copies cannot clobber
each other. App state file names now come from one helper shared with the tab.

Refs #838

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-13 19:53:50 -04:00
Joseph MattielloandClaude Sonnet 5.5 fd90bc5b8a docs: Jaguar CD artwork mirror on the site (README, app page, release notes)
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-13 17:28:29 -04:00
Joseph MattielloandClaude Sonnet 5.5 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]>
2026-10-13 17:28:23 -04:00
Joe Mattiello 9bef2fbc61 Merge pull request #901 from libretro/fix/app-cd-art
feat(app): Jaguar CD covers, title cards, .chd/.cdi identification
2026-10-13 17:20:29 -04:00
Joe Mattiello 7d43cea255 Merge pull request #902 from libretro/feat/app-multi-instance
feat(app): run several instances at once
2026-10-13 17:20:16 -04:00
Joseph MattielloandClaude Sonnet 5.5 d7bfa84f52 feat(app): run several instances at once (New Window, per-instance saves, controller pinning)
File > New window (Ctrl/Cmd+N) starts another copy: `open -n` on a macOS bundle,
the executable elsewhere ($APPIMAGE on AppImage). Copies take a number from an OS
file lock in the data folder (instance-N.lock); the first behaves exactly as before.

Instance 2+ never rewrites the shared config.ini (own config-N.ini holds only window
size and controller choice), keeps game saves in saves/instance-N/ (first run reads
the shared save), uses imgui-N.ini, and hands the netplay address to the core through
VJ_NETLINK_HOST instead of the one shared vj_netlink.txt. Atomic writes now use a
per-process temp name. Settings > Input pins a window to one controller (or keyboard
only) and keeps reading it while another window has focus.

Refs #833
Refs #888

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-13 16:17:12 -04:00
Joseph MattielloandClaude Sonnet 5.5 c7a1a4edcc feat(app): identify .chd and .cdi discs; match non-Redump rips by name
The library skipped .chd and .cdi ("nothing cheap to hash") and name-matched
only on an exact file stem, so most disc rips showed no title.

- .chd: CRC32 of track 1 rebuilt the way the core reads it (2352 of each
  2448-byte frame, audio put back in .bin byte order, virtual pregap left
  out, tracks padded to 4 frames); CHDs the core refuses (several audio
  tracks, no CHSE) are not hashed.
- .cdi: best-effort track 1 CRC from the descriptor layout.
- A disc whose checksum is not in the dat falls back to a normalized name
  match of the file name, then the folder name (case, punctuation, region and
  Track/Disc tags ignored; exact title first, then a unique longer title,
  never a guess between two games).
- Tiles say how a disc was identified in the tooltip.
- Scan cache v2: old caches re-hash only disc images that had no checksum.

Verified against synthetic uncompressed CHD/CDI images; real Redump CHDs are
still to be checked.

Refs #838

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-13 15:41:44 -04:00
Joseph MattielloandClaude Sonnet 5.5 de96413fea feat(app): title cards and custom covers for library tiles
libretro-thumbnails has no Atari - Jaguar CD repository, so no disc could ever
get art. Tiles with no art now draw a card with the game title and a CD / Cart
badge, and cover art can be supplied locally:

- right-click a tile > Set cover image... (PNG) / Remove cover image
- drop a PNG on a tile while the library is showing
- <data>/covers/<Title>.png (dat name, then the shown name), picked up on Rescan
- precedence: tile cover > covers folder > downloaded > card

Unidentified discs get a cleaned title (trailing "(Track N)" and doubled
extensions dropped; "(Disc N)" kept) and now have art slots keyed by dat name,
checksum or path; only identified titles ever reach the network. The Jaguar CD
tab says why discs have no art.

Refs #838

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-13 15:27:31 -04:00
Joseph MattielloandClaude Sonnet 5.5 56fb08af22 docs(app): list the Windows ARM64 and Flatpak packages; install.ps1 picks ARM64
Refs #839

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-13 15:13:18 -04:00
Joe Mattiello 7e2b514767 Merge pull request #896 from libretro/docs/app-readme-site
docs(site): app page with screenshots; README app section and install options
2026-10-13 14:33:06 -04:00