mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-10 03:21:27 -10:00
dependabot/github_actions/develop/actions/setup-python-7
2460
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2e676d167a |
ci: Bump actions/setup-python from 5 to 7
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 7. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v7) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> |
||
|
|
d604c4ade6 |
add codex xcrun cmd
Signed-off-by: Joseph Mattiello <[email protected]> |
||
|
|
fd06d26f75 |
feat(app): add raw memory bitmap viewer and width guessing (#1095)
* feat(app): add raw memory bitmap viewer and width guessing * fix(app): preserve synthetic OP previews across content switches |
||
|
|
ee4952328d | feat(cheats): convert read-only integer lookup tables (#1097) | ||
|
|
1fb655012e |
fix(tom): derive HC readback from emulated time so AvP marines attack (#1094)
HC ($F00004) free-runs 0..HP once per half-line on hardware (JTRM), so its low bits at a CPU read are effectively random. Alien vs Predator uses them as its marines' fire roll -- `move.w $F00004,d0 / and #7 / cmp #2` at 68K $015ADA -- while its Predators roll the game's software RNG. The core returned a per-read counter reset to 0 / (HP+1)/2 every half-line, so a once-per-AI-tick read always saw $0000 or $05A6: low bits 0 or 6, never 2. Marines walked up to an Alien player and never fired (30 of 30 rolls measured from the corridor savestate). The readback is now the elapsed fraction of the current half-line: time to the next half-line event at slice start, less how far the 68K has run into the slice (JaguarSliceElapsedUs, stall and clock scale included), scaled to HP+1 ticks. Byte reads of $F00004/5 use it too. tomRam8[HC] is unchanged (the OP's second-half-line test reads bit 10), and the read-phase counter is gone; nothing new is serialized. Measured after: the same marine rolls all eight HC&7 values (2 on 12 of 111), crouches, fires, and the Alien's health drops. Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
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 |
||
|
|
98212bf95d |
fix(app): don't block in the GL swap when the display was asleep at launch (#1081) (#1089)
On macOS SDL emulates vsync with a CVDisplayLink: SDL_GL_SwapWindow waits on a condition variable that the link's callback signals each refresh. The link is made from the active displays when the GL context is created. With the display asleep there are none, the link never ticks (not even after the display wakes) and the first swap blocks forever, the emulation with it when it runs inline. A display that falls asleep after launch does not block: the link survives. SwapGuard (pure) decides whether the swap interval may be 1: never for a run whose context was made with the display asleep, and (defensively) not while it sleeps later. App::guardSwap() asks the platform layer (CGDisplayIsAsleep / an empty CGGetActiveDisplayList) every 250 ms and before the swap's timestamps, and every UI swap goes through it. Interval 0 is the existing vsync-off path, so the emulation keeps its own clock while nothing can be seen. Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
fe699d5d3f |
fix(app): release a gdb-held halt on Detach/Reset/Open/Close and the Phase 3 halt follow-ups (#1090)
The app never answers the halt pump with DETACH any more (#1069). Inside GDBHalt() a dropped host let a gdb client's still-armed Z0 park the core again with nobody to pump: threaded, the mirror read "not halted" and every frame-lane call waited for gdb; inline, the window froze. Detach, Open, Close, Reset, a disc change and the hardcore flip all reached that. One rule for leaving a halt (docs/app-debug-tools.md): every release is a CONTINUE with the host kept, decided by one lane post on the emulation thread with the core's real state. Detach disarms and drops the app's breakpoint list and defers the host drop to DebugCore::frameEnded(), outside retro_run(); a new halt before then cancels the drop (that halt needs the host) and an attach() in that window supersedes it. Reset, Open, Close and the disc commands (App::releaseForCall / callReleased) disarm the enabled breakpoints for the release and re-arm them before the first frame after the command, so the list survives a Reset (#1081 M1). The give-up predicate of the call that follows fires only on a halt newer than the released one (DebugCore::releaseSerial), which also covers a Detach made while a step is in flight (M5). Also from the Phase 3 review (#1081): the SRAM-autosave and achievements idle posts are coalesced to one in flight so a long halt leaves the frame lane bounded (M2; rc_client_idle still does not run while halted, on purpose), halt_reason() is read before haltMu_ (M4), the same-pc fold is documented in the pump (M3), nothing is refused while a Detach completes (M6), and systemRam() carries its owner-thread note (M7). Tests: debug_e2e_threaded sections 13 (threaded: Detach then Reset while a gdb client holds a Z0 halt in the hot loop; an app breakpoint survives a Reset) and 14 (the same Detach inline) fail on the old code and pass now; debug_session_unit pins the rule against the mock (frame-end drop, the cancel on a new halt, release + re-arm, the release serial, Detach then Break). Part of issue 967. Co-authored-by: Claude Opus 5.5 <[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]> |
||
|
|
4fdca5e8a4 |
Merge pull request #1086 from libretro/chore/backmerge-master-1083
Merge master into develop after the v3.10.0 installer hotfix (#1083) |
||
|
|
c38b7cb0a6 |
Merge master into develop after the v3.10.0 installer hotfix (#1083)
The installer fix and tests already reached develop via #1065. This brings over the completed v3.10.0 qualification record from master and keeps develop's installer.yml, which drops the develop push trigger as part of the CI trim (#1074). Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
aa5b73bd2b |
ci: use RUNNER_TEMP for scratch files the self-hosted Studio runner would keep (#1084)
The Studio runner keeps /tmp between jobs, so regression-macos-arm64's
miniretro clone failed on the next run ('destination path /tmp/miniretro-src
already exists', exit 128; #1082). Clone into a fresh RUNNER_TEMP dir, and
move the EEPROM lifecycle test's scratch files there too.
Co-authored-by: Claude Opus 5.5 <[email protected]>
|
||
|
|
4c9e0fdd42 |
Merge pull request #1083 from libretro/hotfix/v3.10.0-installer
fix(installer): backport qualified portable data preservation to v3.10.0 |
||
|
|
1dec1cb2fd |
feat(app): nightly builds default the emulation thread On, with the phase 3 gate (#967 3d) (#1082)
* test(app): no_threaded_limits keeps every feature on while the emulation thread runs (#967 G1a)
Fails when a threaded-only gate, its user-facing text or its limit test comes back, when the GDB stub is locked for any
reason but hardcore, when a file starts asking "am I threaded?" beyond its allowlisted count, or when tsan.supp grows.
Verified red on the pre-3a tree (
|
||
|
|
a8ed6b5332 | docs: record completed v3.10.0 package qualification | ||
|
|
f460ab44e1 | test(installer): invoke Windows RetroArch directly for qualification (#1065) | ||
|
|
329a96edc8 |
fix(installer): preserve portable data and qualify published package updates (#1059)
* fix(installer): retain portable data and qualify published packages * docs: record published Mac and clean Linux qualification evidence * test(packaging): add clean Intel macOS qualification lane * fix(ci): use distro audio packages and authenticated qualification metadata * test(packaging): qualify downloaded Windows setup upgrade and uninstall * test(packaging): require real upgrades, core loading and immutable candidate evidence * test(installer): run Linux core qualification with a virtual GL display * test(installer): resolve release tags for qualification provenance |
||
|
|
617fe83e15 |
test(app): fonts_system_unit reports a slow host instead of failing (#1078) (#1080)
* test(app): fonts_system_unit reports a slow host instead of failing (#1078) The real-machine check waited a fixed 30 s for the full system font scan and failed `list != nullptr` when it was not done. GitHub's hosted Windows runners take 32-53 s for that scan (about 2 s elsewhere), so it failed unrelated PRs (#1068, #1074). Every correctness assertion (names, weight/italic/fixed pitch, collections, dedupe, sort, directory selection) already runs on synthesised fonts, so the machine scan is only a smoke check of the worker-thread plumbing. If it has not finished within the bound it now prints "slow host" and passes; FONTS_STRICT=1 keeps the hard check with a 120 s bound. No production change: the scan runs on a worker thread, has no time cap, and the font picker draws whatever it has meanwhile. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test(app): fonts_system_unit's CTest timeout covers the strict 120 s scan (#1078) Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
85cd3f1642 |
feat(app): debugger halt and GDB stub with the emulation thread on (#967 phase 3b) (#1068)
* feat(app): bounded halt pump on DebugCore; the UI mirrors the halt DebugCore owns the halt: pump() serves the debug lane, waits at most kPumpWait (4 ms) on a debug-lane-only wait and returns VJ_DBG_RESUME_NONE to GDBHalt's own loop, which keeps the socket service and the halt timeout. The UI writes its verdict tagged with the halt serial it saw, so a verdict for a dead halt never resumes the next one; frameEnded() closes a halt the core left on gdb's say-so. DebugSession mirrors HaltState once per UI frame (syncHalt) and fires onHaltUi / onResumeUi on serial edges; attach/detach/breakNow are ordered through the lane and a pending detach. Inline keeps the nested UI frame. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): a STEP the core never re-halts must not leave the UI mirror halted pump() returns STEP without bumping the halt serial, so the bar stays up while the core steps. If the frame ends before the core re-halts (the stepped instruction ended it, or the target had stopped), frameEnded() found nothing halted and the mirror stayed halted for good; inline, stepEmulation() is gated on halted(), so the frame that would re-halt never ran and F9 / Detach wrote verdicts nothing took. DebugCore now remembers a STEP in flight (cleared by the next halt entry or setIface) and frameEnded() closes it like a foreign resume. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): verdicts and detach only against the core's live halt After a STEP verdict the halt serial is unchanged but the core is running until it re-halts, so DebugSession::detach() offered a DETACH for a serial the re-halt then dropped, left detachPending_ set (attach / Break refused) and, with hardcore turning on in that window, left the core attached. resume / step / detachAndResume now require the core's halt to be live (parked, same serial); detach and detachAndResume fall back to the lane detach otherwise, and that post also releases the pump if it runs inside a live halt. haltedFrame() syncs the mirror first so the first nested frame meets the current halt; the inline resume hook keeps headless audio unpaused as before. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): the emulation thread pumps the halt; quitting ends it Threaded, the pump's wait is App::haltWait (serve the debug lane for at most 4 ms, refill the views when a request is pending); frameEnded() runs after every retro_run so a halt gdb ended is closed on our side (inline: from syncDebug outside a frame). beginQuit() sets quitting_, wakes the pump and waits at most 200 ms for the detach the pump answers with, before the auto-save, the SRAM flush and the 3 s unload; closing the window inside an inline halt takes the same path. haltedFrame runs only inline. The halt bar says "HALTED by gdb" for a halt the app did not ask for; a halt at the CPU window's own temporary breakpoint (run to cursor, step over) is ours (DebugSession::ownsBreakAt). Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): calls and frame advances give up when a halt begins; content changes detach first EmuThread::call and advance take a give-up predicate: a call queued behind a frame that halts returns instead of parking the UI behind the busy spinner; advance registers its budget on the debug lane and waits with the same predicate. Every UI-thread call passes one (App::emuCall). Threaded, open / close / reset / disc detach the debugger first and then proceed; save / load state and boot refuse with a toast; frame-lane posts wait for Continue and the halt bar and the Core options tab say so. Inline keeps refusing (the call would run inside retro_run). RetroAchievements replies ride the debug lane, so none is ever cancelled. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): a given-up disc follow-up retries; close reports a re-halt; deterministic give-up and re-drain tests followDiscSwap advances seenDiscSwaps_ only when its call ran, so a halt no longer loses the RA / cheats follow-up. closeContent says so when its detach predicate gave up. The netplay headless step stops re-budgeting a frame whose advance gave up. testCallGiveUpLosesToStart covers the compare-and-swap-lost branch of call(), and testAdvanceBudgetDoesNotOvertakeFramePost covers the loop's second frame-lane drain; the 300-iteration smoke loop is gone. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): the halt and the GDB stub work on the emulation thread; a silent host behind every halt; hardcore locks the stub The phase 1 limits are gone: Break / Step / breakpoints work threaded and virtualjaguar_gdb_stub is a plain option again. Whenever the stub is enabled the app attaches its host on the emulation thread inside the load (before the first frame, so a gdb_wait boot halt is seen) and again after any frame it is missing, so every gdb halt flows through the app's pump: the halt bar shows it, calls give up, quitting detaches. RetroAchievements hardcore locks the stub to disabled at load, and a hardcore flip with the stub open reloads the game so the lock reaches the core (phase 3 plan, decisions 2 and 3). Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): B4 review: pre-frame silent host, gdb halts survive headless inline, Detach clears breakpoints - emuRunOneFrame re-attaches the silent gdb host before the frame, so a gdb halt right after a Detach is hosted - headless inline haltedFrame releases only app halts and quit; gdb's halt keeps the core's socket loop serving - Detach drops the session breakpoint list with the core's table - Settings > Emulation thread hint reflects that the halt and stub work threaded - locked-option warning is generic; a hardcore restart waits out an inline halt Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * test(app): the halt and the GDB stub end to end on the emulation thread debug_e2e_threaded drives break / step / continue / detach / hardcore / quit from a UI thread that never blocks over 50 ms, pins the option post that applies after Continue, the save state that gives up when a halt begins, a posted lane detach run inside a live halt, the 3 s join, and a gdb_wait boot halt with no client (Detach, quit). gdb_threaded_halt finds a headless run halted at boot from a gdb client, reads memory, continues, and quits the app mid-halt, threaded and inline, plus an inline hardcore variant: SIGINT / SIGTERM end a headless run cleanly (installed after SDL_Init, headless only). The headless inline loop now closes a gdb halt after each frame (frameEnded + syncHalt), without which the hardcore reload retry stays blocked. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): B5 review: the cancelled save is proven absent after frames run; free ports; signal handler resets debug_e2e_threaded waits for frames after the halt ends before asserting the cancelled save left no file (it is held while halted, so the old check was vacuous) and saves to a different file; the gdb_wait sections probe a free port; the test also fails on ThreadSanitizer. The signal handler restores SIG_DFL after storing the flag (a second signal kills a wedged run) and g_signalQuit is asserted lock-free. gdb_threaded_halt.py's watchdog kills live children; a missing Python3 is reported at configure time. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(app): the halt and the GDB stub on the emulation thread Halt model, Detach, hardcore lock and reload, gdb_wait, headless signal quit, testing; README, testing.md, design section 6 (2026-10-22 as-shipped notes), plan 3b task list. Includes the MSVC fix: debug_e2e_threaded includes <ws2tcpip.h> for socklen_t. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): quitting while a gdb client holds a halt no longer hangs The pump answered a quit with DETACH, which drops the app's host inside the core. A gdb client's breakpoint that is still armed (a hot loop) then fires again at once, with the socket open and no host: the core parks in GDBHalt() for the client, and the quit's flush, autosave and unload wait on it forever. The pump now answers CONTINUE and the host stays attached (unloading drops it). Every wait in the quit epilogue is bounded (flush 1 s, autosave 1.5 s, skipped while the core is still halted); the unload keeps its 3 s backstop. Also: re-attach the silent gdb host before each frame (inline stepEmulation, headless inline loops), comment/doc fixes, EmuThread::stop precondition, and the three gdb tests' PASS regexes lose their ^ anchor. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): a hardcore restart that gives up stays pending and retries Achievements::pump cleared resetPending_ before the restart and ignored its result. Threaded, the restart can give up when the core halts again (a gdb client with a breakpoint still armed): hardcore was then active without a clean restart and the open GDB stub bypassed the hardcore lock. The restart now stays pending until it takes. reset() returns whether it ran. A give-up with the game still loaded (and, for the reload, the stub still open) retries at the next pump; a restart that failed to load the game, or closed the stub, is dropped. The cheat reset is posted once and the restart is counted once (--ra-expect-reset), the give-up is logged once. Tests: VJ_TEST_RESTART_GIVEUPS=N makes the first N restarts report a give-up (a headless run cannot stage the re-halt); gdb_hardcore_flip_giveup_* (reload path) and ra_hardcore_reset_giveup_* (reset path), inline and threaded. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): a signalled threaded --neuro headless run still prints the Neuro report The signal-quit block returns before the TIMING/report section; report first (it only touches UI-thread state) so a SIGTERM'd --neuro run behaves like a finished one. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * test(app): gdb_* tests take their port from the OS at run time (#967) The six tests with hard-coded ports 23457-23463 (gdb_enabled, gdb_enabled_threaded, gdb_hardcore_flip_*, gdb_hardcore_flip_giveup_*) all failed on the hosted Windows ARM64 runner with 'failed to open port': every one of the distinct ports was refused, which points at a reserved/excluded range, not a collision (the Python gdb tests, which already pick a free port, passed there). tests/with_free_port.py binds 127.0.0.1:0, reads the port back, substitutes {PORT} in the command and, with --config-ini, writes the saved stub setting for the hardcore tests. The assertions and ctest regexes are unchanged. Registered only with Python3 (STATUS message otherwise). RESOURCE_LOCK vj_gdb_port on every test that binds a stub port closes the bind-0 TOCTOU window. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): start Winsock before the core loads so the GDB stub can bind on Windows (#967) Every gdb_* test that opens the stub failed on hosted Windows x64 with "[GDB] stub enabled but failed to open port N", on fixed and on OS-chosen free ports alike. The core's GDBSockOpen (src/debug/gdbsock.c) calls socket() without WSAStartup, because its libretro host (RetroArch) starts Winsock. The app never did on that path (neuro and netplay only start it for themselves), so socket() failed with WSANOTINITIALISED and the stub stayed disabled. The feature was therefore broken for users on Windows too, not only in tests. platform::initNetworking() now runs WSAStartup once, right after SDL_Init and before any core load, headless runs included. A no-op on macOS and Linux. Winsock is reference counted, so neuro's and netplay's own WSAStartup stay harmless. Also corrects the comments that blamed reserved port ranges. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * test(app): with_free_port.py hands the app explicit stdout/stderr (#967) The Packages / Windows x64 job builds the GUI-subsystem exe (VJ_APP_WIN32_GUI). There, gdb_enabled, gdb_enabled_threaded and the gdb_hardcore_flip* tests missed their PASS regexes. Popen(cmd) left the child's standard handles to inheritance, so Windows started the GUI-subsystem app with no stdout. console_attach.cpp then attached the runner's console, and the output never reached ctest's pipe. The wrapper now passes stdin=DEVNULL and stdout/stderr explicitly, so the output stays on the pipe. Tests that ctest launches directly already get redirected handles and were unaffected. Refs #1075 Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: dry <dry@x> |
||
|
|
10642e5ade |
fix(app): TV Settings follow-ups from the Phase 5 final review (#1077)
- The Shader row now steps on a press only. A held Right/Left repeats every
80 ms, and each step parses and compiles a preset and saves the config
(a broken preset toasts for 6 s per step); the repeats are ignored for
that row, with a smoke test that a held Right applies exactly one step.
- The library panel's "BIOS & boot..." button goes through
App::openBiosSettings() like the menu item, so it follows TV mode.
- F8 behind hardcore's run-on menu toasts "The menu has the controller"
(nothing is paused there); a menu-held pause keeps "Close the menu to
resume".
- A slider's "(custom)" label keeps the unit the grid labels use
("55.5 % (custom)", not "55.5 (custom)").
- App::testDenyPause is private; only the script runner (a member) sets it.
Co-authored-by: Claude Opus 5.5 <[email protected]>
|
||
|
|
e7009c440a |
ci: trim develop-push runs, debounce nightly, route macOS to Studio runner (#1074)
* ci: stop re-running PR-covered workflows on develop pushes; debounce nightly; route macOS to Studio runner Drop develop from push: in c-cpp, app, regression-test, acid-test, installer, jaguar-demos, microbench, jaguar-toolchain and cheat-import (PRs already ran them); ignore develop in pull-request.yml. release.yml's nightly waits 10 min in its guard job so a merge burst builds once. macOS rows of c-cpp/app/ regression and the unsigned iOS/tvOS nightly rows use the self-hosted Studio runner on trusted events only, with a VJ_STUDIO_RUNNER=off kill switch. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(spm): find blitter_simd objects independent of SwiftPM build layout Xcode 26 SwiftPM keeps objects under .build/out/Intermediates.noindex/... instead of <triple>/debug/virtualjaguar.build, which broke the vtable check on the Studio runner. Search by object name and fail loudly if one is missing. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: dry <dry@x> Co-authored-by: Claude Sonnet 5.5 <[email protected]> |
||
|
|
7e2f5967ee |
feat(app): TV mode auto-switching, first-use hint and build default (TV mode phase 4) (#1067)
* docs(app): revise TV mode plan Phase 4 against develop (#991) Phase 4 was written before Phases 1-3 and desktop pad navigation (#1030) landed. Re-point the tasks at the current code: the controller trigger gates on deskPadActive and consumes the switching press, package defaults are set in app-package.yml (not release.yml), the first-use hint text comes from the pad's actual TV-menu binding. Adds Phase 4 approvals A-G and a placeholder for the user's layout fixes. Refs #991 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): TV mode switches on in fullscreen and on a controller press (#991) Wires the Auto setting. Until now Auto acted as Manual (effectiveSetting), so the fullscreen and controller triggers TvMode already implements were never fed. Why each piece is shaped as it is: - App tells TvMode about fullscreen edges only (tvFullscreenEdge against tvNoteFsSeen_): TvMode re-applies a repeated onFullscreen(true), and macOS sends enter events on space and display changes, which would turn TV mode back on after a manual off. - A mapped pad button (a menu intent: D-pad, face buttons, shoulders, Start) pressed while the desktop library owns the pad and no game is loaded switches TV mode on and is consumed, so it neither moves desktop focus nor acts on the TV screen it reveals. Guide/Share/touchpad/paddles/sticks are not triggers: they carry app actions or drift. The call sits before the TV shell so presses the shell consumes still give a controller-started TV mode a fresh mouse budget (otherwise mouse jitter would sum up and drop TV mode while the pad is in use). - Mouse travel and a printable key the TV UI did not use hand a controller-started TV mode back; F-keys, Esc and Ctrl/Cmd/Alt shortcuts never count (F4 and Ctrl+Shift+T are toggles themselves). - effectiveSetting() is gone; a config with no ui.tv_mode takes defaultSetting(VJ_TV_DEFAULT_SETTING) (Auto), never written back, so the build default can move. setTvSetting/setTvAuto are the one way to change the settings; the script gains `fullscreen on|off` and `expect tv-setting`. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): TV mode in Settings > Interface, the Auto rows in TV Settings, VJ_TV_DEFAULT (#991) Settings > Interface gets a TV mode section (Off / Manual / Auto, plus the two Auto checkboxes while Auto is chosen) and TV Settings gets the matching Auto: in fullscreen / Auto: when a controller is used rows, so the Phase 4 triggers can be switched without editing the config. Both routes go through App::setTvSetting / setTvAuto and save afterwards; the setters themselves never save. The default a fresh config starts from now comes from CMake: VJ_TV_DEFAULT (auto | manual | off, validated) defines VJ_TV_DEFAULT_SETTING for the virtualjaguar target, so a nightly can start on Auto while a release package starts on Manual. A stored ui.tv_mode still wins, and is only written when the user changes it. The headless script also pins approval C at App level (Esc, Enter and a Ctrl shortcut never end a controller-started TV mode) and walks the new rows, shifting the script's later lines to make room. Co-Authored-By: Claude Opus 5.5 <[email protected]> * ci(app): release packages default TV mode to Manual, nightlies to Auto (#991) The TV mode default now comes from CMake's VJ_TV_DEFAULT. app-package.yml sets it per build from release_tag: vX.Y.Z tags and prerelease start a fresh config on Manual, nightly, prNNN and dev builds stay on Auto. The three native configures pass it as -DVJ_TV_DEFAULT, and the Flatpak, which is built straight from its manifest, gets the same value through one sed on config-opts before flatpak-builder runs (the committed manifest keeps CMake's default). Co-Authored-By: Claude Opus 5.5 <[email protected]> * test(app): make the TV Settings Auto-rows walk independent of earlier tests (#991) The headless block that walks to "Auto: in fullscreen" relied on the shell still remembering Settings > Interface with focus on More settings..., left by a test far above it. It now presses Back twice (Home from either level of the Settings screen), opens a fresh Settings visit and clamps its way to the row: Up x4 / Down x3 on the groups, Up x8 / Down x4 in the list. It also checks that the Auto checkbox change, which saves the config, still leaves ui.tv_mode absent, and the CMake comment now says app-package.yml always passes VJ_TV_DEFAULT. Co-Authored-By: Claude Opus 5.5 <[email protected]> * ci(app): fail the Flatpak build when the TV default sed did not apply (#991) Check the manifest for the exact config-opts line after the sed instead of printing it, so a changed manifest cannot silently ship the wrong default. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test(app): anchor the Auto-rows walk with expect tv-focus (#991) The previous walk assumed a fresh Settings visit starts on the groups. It does not: the shell's memory is never cleared, so the Base layer resumes the level (list or groups) and row an earlier test left, and TV mode comes back on at Home, so the Back presses before the first visit did nothing. The stray Right after the Up/Down presses only passed because the later Up x8 re-anchored it. Add `expect tv-focus=LABEL` (Screen::focusLabel(), Shell::focusLabel(); Settings names the group on the groups and the row in its list) and rewrite the walk so every step is asserted before the next one acts: open Settings, one Back, LB x3 to Home, RB x3 back (now on the groups either way), Up x4 reads Picture, Down x3 Interface, Right enters the list, Up x8 reads Show FPS, Down x4 reads Auto: in fullscreen, and only then Left / Right change a value. Also fix the Settings screen's comment: the Base layer resumes the remembered level, only the menu slot always starts on the groups. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): one-time TV mode hint on the first controller press in the library (#991) Under Manual nothing tells a new player how to reach TV mode, and the button differs per pad (Share hold, Guide hold, a paddle) and per OS. The first pad press the desktop library takes now shows a 6 s toast naming this pad's own TV-menu button and the keyboard's (tv::firstUseHint; the fixed Ctrl/Cmd+Shift+T stands in until two are known). The key ui.tv_hint_shown is saved before the toast and a per-run flag backs it, so it cannot show twice; Auto (whose switching press returns before the hint site) and Off never show it. A Settings reset of Interface clears the key, so it may show once more. The pad text uses the short names (Share, Guide) rather than Binding::display()'s "Share / Capture". The TV script gains expect toast=TEXT, toast-count=N and hintreset, and the Settings > Interface hint now says nightly and local builds start on Auto. Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(app): TV mode section in the README (#991) Document what TV mode is, the three settings and where they live, the Auto rules, the build default (-DVJ_TV_DEFAULT: Auto for local and nightly builds, Manual for releases), the first-use hint, --tv, the 500 ms hold and the Guide / keypad-chord limitation. The existing key-table rows link to it. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): hint names the pad's own button; mouse verb sends real motion; Home copy for a library with games (#991) Review of Task 18 and the Studio windowed check found four things. The first-use hint named "Share" on every pad, but SDL's MISC1 is the DualSense mic and the Switch Pro Capture button, and Guide is PS / Home on those. tvMenuButtonName(family, binding) names it per family (the virtual pad keeps "Share (hold)"). Settings > Reset group on Interface now also clears the per-run flag, so the hint may show again as the README says. The windowed mouse check left TV mode on because SDL reports a warped pointer with xrel = yrel = 0, so the 32 px budget never accumulated (the product path was fine: the events reached TvMode::onMouseMove with zero travel). `mouse` now also pushes a MOUSE_MOTION carrying the travel, in headless runs too, and a headless block pins the handleEvent wiring. Under Auto a new player with 20 games landed on Home saying the library was empty. With games but no shelves Home now says "Nothing to continue yet. Press RB for your Library."; the old text stays for a truly empty one. The Off "no hint" check now proves the press reached the desktop nav. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): Home empty state without a per-frame library copy; no doubled "(hold)"; README Off wording (#991) TV Home with no shelves is the landing state of a new Auto user, and it asked the host for List::All every frame to find out whether any games exist, which builds a GameRef (several strings) per library entry. A Host::hasGames() answers it with no copy and an early exit. tvMenuButtonName appended " (hold)" to navButtonName(), whose fallback Binding::display() already adds it, so a rebound hold on a paddle read "Paddle P1 (hold) (hold)". It now names a non-hold copy and suffixes once. README: Off never turns TV mode on, but with a game running the TV-menu button still opens the in-game menu (spec 8). Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(app): TV mode plan Task 16a closed, no layout fixes needed (#991) The owner tested phases 1-3 on a TV and reported no layout fixes, so the last open task of the plan is done and phase 4 completes #991. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): Settings opens the TV Settings screen while TV mode is on (#991) Every Settings entry (Ctrl/Cmd+Comma, the menu bar, the native macOS menu / tray and Cmd::Settings) set showSettings, the desktop window, even in TV mode, so a player in a game could not reach settings without the plain UI. They now go through App::openSettings(): TV mode on with no game switches the Base layer to the Settings section; with a game it opens the in-game menu (same pause / hardcore gate as the TV-menu action) with the Settings screen in its slot, so B returns to the menu root. TV mode off is unchanged. Ctrl/Cmd+Comma still closes an open desktop window (More settings...); in TV mode a second press leaves the TV screen as it is. The BIOS-tab entries stay desktop (the TV Settings has no BIOS tab). Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): the TV menu opens without pausing when hardcore refuses; BIOS entries use the TV hand-off (#991) Settings must stay reachable mid-game, so when Achievements::allowPause refuses the in-game menu (the F4 action and the Settings route alike) opens anyway, like the netplay rule: the menu owns the pads, the game runs on, and the menu shows a 'Hardcore: the game keeps running' banner. menuResume restores the pad ownership as after a netplay menu. The refusal toast is dropped from this path (nothing refuses any more). The BIOS-tab entries (menu bar, native Cmd::BiosSettings) now go through App::openBiosSettings: with TV mode on it is the pad-driven More-settings hand-off (the menu first with a game), else the desktop window as before. Test seams: the 'command NAME' and 'hardcore-pause deny|allow' script verbs and the menu-owns-input expect. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test(app): TV Settings routes through the native commands, the Save page, hardcore and BIOS (#991) Native-menu path via the new 'command' verb (a synthesized ctrl+, is ignored in a real window), the menu root and Save page routes, the hardcore-refused menu (runs on, owns the pads, hands them back), command bios in TV mode, and smoke tests for Shell::openSettings and the hardcore banner. Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(app): TV mode plan Phase 5, every setting and the Library in TV mode (#991) The owner found TV mode stops at the game: Settings and the Library open the desktop windows mid-game, and TV Settings lists a handful of rows with the core options hidden until a game has run. Phase 5 reads the core's option definitions at startup so every core option and every choice-like app setting has a TV row, and routes the Library through TV screens too (an in-game Library row; a launch asks first). Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): core options readable at startup; TV Settings catalogue of every core option and app choice (#991) The owner could not see the game core's settings in TV mode: the option table only reached the app through CoreHost::startSession, so until a game had run the TV Settings hid the three curated core rows and said "Available once a game has run". CoreHost::probeOptions() runs retro_set_environment once at startup, before the emulation thread exists, answering only the option commands (everything else is refused), then the core's own display pass, so defs, categories and visibility are filled exactly as a load fills them. The core is then taken back to a fresh process's state with retro_deinit, the call every reload makes before its retro_set_environment: the display pass leaves its show_* latches at "hidden", and without the reset the first game's own display pass finds no change and every gated row (netlink host, voice chat, mouse and rotary tuning, CD-only keys...) stays visible. core_options_probe checks that the first load after a probe has the same visibility as the second. The TV Settings model is now a catalogue: SettingRow::group is a string key plus a label, coreOptionRows lists every visible core option by the core's categories (overridden = read-only, Host-kind typed keys read-only, "(custom)" for a value off the list, a restart note), sliderRow turns the desktop sliders into stepped lists with a "(custom)" entry for a finer value, and AppHost lists every choice, toggle and slider of the desktop tabs (Picture, Sound, Interface, Library, Achievements, Discord, Neuro) before the core's groups. The curated core rows, the speed note and the fixed four-group enum are gone. tv_settings.cpp only follows the new group type; the rail's scroll and the long-list focus are the next task. tv_menu_headless walks the longer lists with Down x20 (clamping) and a new test-only `settings-group KEY` verb that sets the shell's rail memory, so no count of rail items depends on the build. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): a "(custom)" setting value sits between its neighbours, so Left and Right step the right way (#991) A slider value off the list (the audio buffer's own default of 64 ms, an off-grid volume, dead zone, tile size) was appended last, so Left jumped to the end of the list and Right did nothing. sliderRow now inserts "<value> (custom)" at its sorted position and stepValue maps indices past it back to the grid: 64 ms sits between 60 and 70. A typed numeric core option whose presets are in numeric order (a clock scale, a port) gets the same through coreCustomPos, which setSetting uses to map the stepped index back to the option's values; text, addresses and unordered presets keep the entry last. Also says in a comment why probeOptions drops the display callback: gating options do not show or hide dependent rows until the first game loads. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): TV Settings scrolls its rail, pages its list, and shows every group (#991) With the core's categories joining the app's tabs there are ~17 groups, and the rail and the in-game groups level drew every one at a fixed pitch, so the tail ran off the screen and the focus could land there. Both now scroll a whole item at a time (LinearFocus::first eased by the Phase 3 tween, snapped on the first draw), keep the focused item in view, show "more" arrows with room reserved for them, and draw no item that is wholly outside the window. The list scrolls the same way, so no row is cut mid-text at rest. LT / RT (PagePrev / PageNext) jump a screenful on the list, the rail and the in-menu groups level, and stop at the ends; a "Page" hint appears when the list scrolls. A held direction already repeats on the nav's timer, so Shell::repeatIntent() (NavInput::lastWasRepeat) lets the screen tell a repeat from a press where it matters: a held Left stops at a row's first choice instead of leaving for the rail, and a Right held from the rail enters the list without then stepping its first row. Test support: expect tv-group=KEY (Screen::focusGroup), an opt-in text position probe in the widgets, and a headless walk that steps the core's internal resolution with no game loaded, checks opt.* is saved and that the next game reads it. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): Library is a TV screen while TV mode is on; in-game Library row with a launch confirm (#991) While TV mode is on every Library entry point (Ctrl/Cmd+L, File > Game library, the native Cmd::Library) goes through App::openLibrary(): the Library section with no game, the in-game menu with the Library in its slot with one (the hardcore pause gate and its toast apply, as for Settings). The menu gains a "Library" row after Load state; the slot screen is wide() so the rail and cover grid draw over the dimmed game. Typing reaches the slot; Details and the letter picker (Base stack only) are not offered there. Launching from the slot asks first (Page::ConfirmLaunch, "Close <running> and start <title>?", No / Yes, focus on No); Home, Library and Details launch through Shell::requestLaunch, which starts at once when no game runs. Yes goes through the existing library launch (the app auto-saves and closes the menu), then launched() arms the release guard. Script expects: menu-page=..., and tv-focus reads the in-game menu while it is open. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): TV Library review fixes: no desktop library over TV mode at startup or on its on-edge; launch confirm rows match Reset/Close (#991) Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): TV Settings and placeholder tiles wrap long text instead of cutting it Studio shots showed long setting labels cut with "..." (two Per-Title Enhancement rows read the same; notes lost their ends) and Library placeholder titles split mid-word or running off the tile. - Settings in the in-game menu's slot takes the whole panel (wide(), as the Library does) and names itself in a caption line; the label gets the row width less a value column sized by the widest choice, and wraps at words to two lines (a note too); every row of a group is as tall as the tallest so whole-row scrolling still holds. - widgets::wrapLines / textLines: word wrap with a last-line "..." cut at a word; the text probe still sees the whole text once. - Placeholder tiles wrap at words, drop to the 24 u floor before splitting a word, and sit at the foot with the "..." only on the last line that fits; the menu's Library uses tighter columns so "Cybermorph" fits whole, and waits to draw a row until it fits above the panel's foot. - Scroll arrows sit clear of the focus ring (arrow room 26 u); the groups level has its caption so its up arrow stays inside the list area. - The rail and the groups level list "Page" for LT / RT, joining only the glyphs that exist. - Core options already prefer desc_categorized (coreRow); the Per-Title rows have none in libretro_core_options.h, so wrapping is what tells them apart. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): F8 does not pause the game behind the hardcore run-on menu While the menu runs on over a hardcore game (its banner says the game keeps running) a pause-decay countdown can end and F8 then paused the game the player cannot see, to be resumed as the menu closes. menuRunsOn_ is treated like menuHeldPause_: F8 is refused with the same toast. tv_menu_headless covers it (allow the pause while the menu is open, then F8) and `command bios` with no game (the window opens alone, B closes it). Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(app): the launch confirm lists Yes above No Phase 5 item 8 said "rows No / Yes", and the test description said Down for Yes; the rows are Yes, No with the focus on No, so Yes is Up. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
6ce76c1cf8 |
feat(app): Neuro on the emulation thread (#967 phase 3c) (#1066)
* feat(app): AgentPlayer and the snapshot's agent channel (#967) The Neuro agent's input becomes a program the emulation thread replays: InputSnapshot gains an agent channel (AgentStep / AgentProgram / AgentProgress; publishAgent, takeAgent, reportAgent, agentProgress) behind its own mutex and a gen atomic, so the per-event lock is untouched. InputQueue gains version, lineage, firstFrame, trim and program, and AgentPlayer is the pure emulation-thread replica (load, frame, progress), driven by pollAgent. Unit-level only: the app is wired in the next task. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): Neuro presses through the input snapshot (#967) The UI-side Agent now publishes the input it accepted as an AgentProgram (Agent::publishTo) and absorbs the replica's progress (Agent::absorb), which trims its queue and drives busy(), pressedNow() and framesPlayed(). The app's input poll runs the AgentPlayer replica one step per emulated frame, inline and threaded alike (pollAgent); Agent::frame() and InputManager::agentControls are gone (InputManager::addAgentControls keeps the inline hashes unmoved). The #1025 pins are ported to the replica (testAgentGating through a Replica helper, loopback with a frame-numbered record) and extended (testAgentProgram: busy covers older progress, release_all and hardcore clear the replica within one poll, Neuro off, append mid-play does not replay). New neuro_core_e2e runs the replica against the real core (inline, lock-step threaded, free-running at 600 Hz, disabled) and neuro_app_inline runs the headless app against a fake Neuro server. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): Neuro reads RAM watches through a DebugViews request (#967) Neuro no longer reads core RAM through the retro_get_memory_* externs. The profile's watches are one DebugViews request, a Memory request over the covering range or a Dram request when the span is wider than 64 KiB, and the emulation thread's copier fills it after each frame. neuro::WatchFeed (neuro_watch.h) owns the request on the UI thread: it gives a changed range a fresh id so the old range's buffer is never read as the new one, reads the buffer at most every 0.25 s, and adopts it only when it is ok, current and answers exactly the request asked. Anything else leaves the state text at "?". GameState::ram / ramSize become watchBytes / watchBase, watchValue gains a base argument, and Agent::watchRange gives the covering range. Headless inline runs move the refresh serial (syncDebug) and run the copier after each frame, and the end-of-run report prints the state line (neuro_watch_inline pins a cheat byte read through the Dram path). Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): Neuro works with the emulation thread on (#967) The gate that turned Neuro off while the emulation thread is on is gone: settingsFrom no longer disables it, neuroInit does not log "off for this run", and Settings > Neuro drops the hint. The program/replica design from the earlier 3c commits (AgentProgram through the input snapshot, AgentPlayer in the core's poll, watches as a DebugViews request) is what makes this safe. The threaded headless --neuro run ticks Neuro in lock step: one frame at a time, in real time, like the inline loop, and neuroHeadlessReport prints in both modes. neuro_off_threaded is replaced by its twins: neuro_on_threaded, neuro_watch_threaded and neuro_loopback_threaded (the loopback run with the poll on its own thread; its frame-numbered press record equals the inline run's). Docs: neuro.md, app/README.md, docs/agent/testing.md. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): Neuro final-review fixes: one lock-step headless loop, watch staleness (#967) - App::runHeadless: the threaded mock-mode and --neuro loops are one lock-step loop in the inline loop's order, so the 3b rebase meets two loops, not three. - WatchFeed::dropIfStale: neuroTick drops a previous game's bytes before it lends them to the state text, not one tick later. - WatchFeed cadence: a rejected buffer is not re-read every UI frame; the clock resets on release, a key change and a dropped epoch. - neuroHeadlessReport forces its read (comment now matches the code). - docs/neuro.md: the 2 MiB copy runs while Neuro is on, about 10 times a second. - Tests: dropIfStale and rejected-buffer cadence (testWatchFeed), a non-zero low byte for the Dram index, absorb on a stale lineage (testAgentProgram); the testAgentGating comment moves back above its function. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
80be53f43e |
fix(app): CI: wait-hook test waits for its hooks; a failed database download is a warning (#967) (#1070)
emu_thread_unit's "a debugCall behind a slow frame runs the wait hook like call()" held the frame for a fixed 450 ms and expected three hooks (kBusyAfter, then ~every 16 ms). GitHub's hosted macOS VM oversleeps enough that only two fit, which failed develop's App macOS arm64 and Packages / macOS universal jobs. The frame is now held until the hook has run three times, with a 5 s bound, so the coverage is kept. The ThreadSanitizer job's configure failed because file(DOWNLOAD ... EXPECTED_HASH) turns a failed download of 'Atari - Jaguar.dat' (an HTTP error from raw.githubusercontent.com) into "cannot compute hash on failed download", a configure error, instead of the warning the code already prints. The download now runs without EXPECTED_HASH and the SHA-256 is checked by hand: a failed download or a mismatch removes the file and warns, as intended. Refs #967 Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
163f3aff17 |
fix(info): savestate_features = "deterministic" (#1064)
RetroArch reads savestate_features as a name ("basic", "serialized",
"deterministic"; core_info.c). "3" matched none of them and only got the
deterministic level because it is RetroArch's default. libretro-super
already ships "deterministic" for this core.
Co-authored-by: Claude Opus 5.5 <[email protected]>
|
||
|
|
b66a6282ee | test(installer): invoke Windows RetroArch directly for qualification (#1065) | ||
|
|
00d205caa4 |
Merge master (v3.10.0) back into develop
Co-Authored-By: Claude Opus 5.5 <[email protected]> # Conflicts: # .github/workflows/cheat-import.yml # Makefile |
||
|
|
3e5afec7ba |
test(app): latch_waker_unit reports lateness where the host cannot pace (#1062) (#1063)
On GitHub's hosted macOS VM the reference SDL_DelayPrecise is itself 8.3 ms late at p90. The waker's 12 ms then failed the "at most 2 ms later than the reference" check. That broke the develop nightly's macOS universal package: the native arm64 slice's smoke tests fail. The x86_64 (Rosetta) slice runs only the shader tests. The test now judges lateness only when the reference's own p90 is at most 5 ms. Above that, the host's timers are too coarse for a 2 ms comparison to mean anything, so it prints a "note:" with the measurements instead, as emu_thread_unit does for coarse MinGW timers. Every wait must still deliver its wake event and never wake early. The disarm, re-arm, event-first and stop checks are unchanged. Closes #1062 Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
cf036d7432 |
fix(installer): preserve portable data and qualify published package updates (#1059)
* fix(installer): retain portable data and qualify published packages * docs: record published Mac and clean Linux qualification evidence * test(packaging): add clean Intel macOS qualification lane * fix(ci): use distro audio packages and authenticated qualification metadata * test(packaging): qualify downloaded Windows setup upgrade and uninstall * test(packaging): require real upgrades, core loading and immutable candidate evidence * test(installer): run Linux core qualification with a virtual GL display * test(installer): resolve release tags for qualification provenance |
||
|
|
8cf078c01e |
feat(app): TV Settings, Overlays, Achievements and More settings (TV mode phase 3) (#1060)
* docs(app): revise TV mode plan Phase 3 against develop (#991) Phase 3 was written before Phases 1-2, the emulation thread (#1000, #1031) and desktop pad navigation (#1030) merged. Re-point every task at the current code: More settings reuses desktop_nav_popup's ImGui nav feed, achievement data reaches tv/ as TV value types, in-game menu rows live in Shell::buildRoot. Adds the Phase 3 approval list (A-G) and Phase 4 staleness notes. Refs #991 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): TV Settings screen with the curated rows (#991) Settings is the third TV section (Home, Library, Settings): a rail of groups (Picture, Speed, Sound, Interface) beside the focused group's rows. Left / Right step a row, Accept steps it forward and wraps, the last row opens the desktop Settings window. Every row is an existing app setting or core option and goes through the call its desktop control makes, then saveConfig(); a row the build or session does not have (no shader runtime, no core options before the first game, a locked option) is absent, and one a session override pins is read-only with the reason under its value. The row rules are a pure model (tv_settings_model) unit-tested without App or ImGui; AppHost only gathers the inputs. Screens now get Back first (Screen::back) so Settings' list can step up to its rail without leaving the section. The shader preset list and apply are exposed from shaders_ui.cpp, and the aspect table moves to aspect_choices.h so the model and Settings > Video share it. Choosing TV mode Manual <-> Auto keeps TV mode on (configure() restarts it from off); Off turns it off. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): TV Settings clicks follow the drawn chevrons (#991) A click on a row's value stepped by quarters of the row, but the value is right-aligned: on a short value ("On", "50 %") the left chevron sits past the three-quarter mark, so clicking it stepped forward (Volume went up). The row now records where its value is drawn and a click left of the value's middle steps back, right of it forward. A focused row grows past the unscaled card, so its right chevron was also outside the hover rectangle and unreachable; the hover test is widened by what the row grows. The smoke test clicks the real chevrons of "50 %". Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): TV Achievements screen and the Achievements and Settings sections (#991) TV mode had no way to see RetroAchievements without leaving for the desktop UI. Sections are now Home, Library, Achievements, Settings (LB/RB). The Achievements screen shows the tracked game's achievements and leaderboard trackers, or with no game the RA Jaguar list, where A launches a game the library has. Y on Home opens it, and Game details gains an Achievements row that opens it on that game. The status line the desktop list builds moved into ra::achStatusLine so both lists draw the same text. Launching from the list opens the game fresh (no resume offer, which needs a mouse), as every other TV launch does. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): show achievement descriptions in TV mode (#991) The desktop achievements window shows what an achievement asks of the player under its title; the TV rows showed only the title and the status line, so a locked achievement said "Locked Progress: 5/10" and never what to do. AchItem carries the description and the row draws it as a muted line between the title and the status (trackers have none). A row takes up to two muted lines inside its height, at the 24 floor of the type scale. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): TV Overlays screen and Achievements, Overlays and Settings in the in-game menu (#991) The in-game menu is a short list of actions; the spec puts Achievements, Overlays and Settings in it, between Load state and Controls. Each opens the same tv::Screen the Base layer uses, in one slot beside the menu's page model: the panel's list area becomes the screen's, the screen gets every intent, and Back asks the screen first (Settings steps from its rows up to its groups) and then clears the slot onto the row it came from. The menu keeps holding the pause while the slot is set, and a root rebuild waits for the slot to clear. Embedded screens draw no rail and no safe margin (the menu's own inset instead) and their way out reads "Menu". Settings gains a first level for it: the groups as rows. Achievements in the menu cannot start another game from the Jaguar list. OverlaysScreen lists Show FPS, the keypad and controls overlays, and every debug tool with a toggle, with a tool's reason wrapped under its label when the debug windows are off. Accept toggles in place; the keypad and controls overlay are mouse-driven windows over the game, so they close the menu. The headless script walks Down x5 to Settings, changes Internal resolution and checks, after the menu is closed, that the core reads 2x (new core-option expect); that test rules the shader runtime out so the Picture group has the same rows on every machine. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): More settings from TV mode with ImGui pad navigation (#991) "More settings..." used to set showSettings and nothing else, so on the Base layer the shell stepped aside but the window had no pad navigation, and inside the in-game menu (which keeps the input regardless) Accept opened the window while the menu still took every pad press. tv_more_settings.{h,cpp} is the App-side glue. While the window it opened is up (and TV mode is on or the menu is open) it takes the pads before the shell, feeds our own NavInput to deskFeedPopupIntents, and turns on ImGui gamepad and keyboard navigation (the keyboard through the new deskSetKeyboardNav, so WantCaptureKeyboard gates F4 and Ctrl/Cmd+Shift+T as a text box does). B or Esc closes the window only when no popup is open and no item is active; otherwise B goes on to ImGui as Cancel and Esc is ImGui's, so a combo or a text box is left first. The menu stays open underneath, holding the pause, and has the pad back when the window closes. Esc is handled in the glue as well as in handleHotkey: keyboard navigation makes WantCaptureKeyboard true, which gates handleHotkey, and with the menu open the shell would otherwise take the arrows, Enter and Esc too. drawSettings, while live, is its own ImGui window ("###vjtvmore", NoSavedSettings) centred at 80 % of the viewport with TV-sized text (tv::bodyFontScale), so imgui.ini and the desktop window's size are never touched. Shell::handleEvent now clears padDriven_ on a mouse move even when a desktop window has the input, so the pointer is back in the window. saveConfig's stale comment about ui.tv_mode is corrected. Tests: tv_menu_headless (Base layer hand-off, Esc and Ctrl+Comma, and the in-menu hand-off with the menu and its pause intact), tv_ui_smoke (nav flags, pointer, menu keeps the slot), tv_focus_test (bodyFontScale). Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): More settings only live over the menu or the Base layer; pointer return; Speed note (#991) Review fixes for the TV "More settings..." hand-off: - tvMoreSettingsLive is now "menu open, or TV mode on with no game running". With a game running the pads are polled, not read from the events the glue takes, so a window driven by the pad would have been played through when a Share hold closed the menu under it (or a ROM was dropped on it). The window then continues as the plain desktop Settings window and the flag clears. A script case holds Share with the window open over the menu. - The pointer's return after pad navigation is seen in drawBase (the mouse moved), not in Shell::handleEvent, which App::handleEvent never calls while a desktop window has the input. The dead branch is gone and the smoke test goes through the draw path. - The Speed group is there before a game has run, with the read-only line "Available once a game has run" (approved change G), on the Base screen and in the menu's groups level; a note row has the full row width for its text. - The handleHotkey Esc rule is gone: the glue consumes Esc before it can be reached. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): More settings focus, hints, demotion and TV-sized title; tv_mode Off clears the remembered state (#991) - "Reset all settings..." carries NoNavDefaultFocus, so pad / keyboard navigation in the More settings window no longer starts on it (the first A opened the reset modal). - The Base layer draws no hints while a desktop window has the pads: the screen's A Open / LB RB Sections / B Groups did nothing there. - When the hand-off ends (the menu closed under the window by a Share hold, or a game dropped on it) with a game running, the window is closed too: a pad player could not close a plain desktop window and the pads are the game's again. Script cases for both. - The window's font is pushed before Begin so the title bar and its close box are TV-sized, and popped after End. - Choosing Off in the TV-mode row also clears ui.tv_manual_on, or a later Manual switched TV on at once. - The Overlays list leaves the room under its rows to the "more" arrow when it scrolls (the half-cut last row ran into it). - tv_more_settings.h: the "live" comment matches the definition. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): keep More settings pad focus off the Reset button; reserve the Overlays arrow band; no menu hints under the window (#991) - While the TV More settings window is live the "Reset all settings..." button is NoNav (the mouse still reaches it): NoNavDefaultFocus alone left it the only default-focus candidate when the first tab is empty (no game, no core options), so two stray presses opened the reset modal. ImGui's tabs never take default focus, so the selected tab asks for it (SetItemDefaultFocus, live only): the pad starts on a tab and D-pad Right goes to the next one. - The Overlays list reserves the 24u arrow band in the scroll target and the hit tests as well as the clip, so the focused last visible row is not cut off. - The in-game menu draws no hints while a desktop window ("More settings") has the pads. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
5d0afd4b37 |
feat(app): Tools windows work with the emulation thread on (#967 phase 3a) (#1061)
* feat(app): a debug lane in the emulation thread's command queue (#967) EmuCommands gets Lane::Debug beside the frame lane; the loop drains both at each frame boundary. EmuThread::debugCall / debugPost are safe inside retro_run() once the halt pump serves the lane (phase 3b). Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): DebugViews, per-window requests the emulation thread fills (#967) A request table with a double buffer per request: the UI sets and reads, the emulation thread (inline: the same thread) fills after each frame. A changed request or a content switch invalidates its buffer; no lock is held across a core call. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): DebugCore owns the debug interface; DebugSession marshals to it (#967) DebugCore wraps every vj_debug_iface entry point (emulation thread only); DebugSession routes each action through coreCall/corePost, which run inline until App wires them to the debug lane. setHaltAvailable gates attach, Break and breakpoints so a threaded run can keep the windows and refuse the halt. Inline behaviour is unchanged. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): Memory viewer and Palette read published view buffers (#967) The Memory grid, its selection footer and the Palette ask the view copier for their bytes and draw the published buffers; a content switch (DebugViews epoch) resets them. SearchJob::step and the CRY table each take one callCore. Adds the shared counting test fixture (debug_ui_fixture.h) and the debug_no_direct_iface static guard that the other tool windows reuse. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): resized Memory window adopts its wider buffer; guard catches core() calls (#967) fetchWindow compared only serial and start address when deciding a buffer was already adopted, so a paused window that grew (bytes-per-row change at the region top) kept the shorter window. It now compares size too. CheckNoDirectIface also rejects the session's DebugCore wrappers (s.core().peek( and friends). The smoke test pins the resize case and asserts the Palette really adopted a changed CLUT byte. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): CPU debugger reads Regs and Disasm view buffers (#967) Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): CPU walks drop stale answers; rows re-adopt on every answer (#967) Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): Object list reads the OpSnapshot request; step-a-frame via advance(1) (#967) Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): Object list step bumps the serial after the frame; release posts the clear (#967) Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): Blitter log and Textures through view buffers and core calls (#967) Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): Cheat scanner on the Dram request; Audio scope available with the thread on (#967) The scan pass is one DebugSession::snapshotDram (callCore: the 2 MiB peek runs on the emulation side); the live column reads a published Dram request and copies the buffer only when a newer fill exists. The raw memoryRegion path is gone. The scope gets a framesSeen test hook and a threaded-audio gate test. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): Cheat scanner shows -- until a value was read; new rows carry the scan's own values (#967) Per-row known flag: cleared on a new result list, release and an epoch change; edit prefill and Add as cheat refuse a row with no value. doScan derives the rows from the RAM image it just read, not the older published buffer. Tests: search before the first answer, stale held buffer, collapsed window, unreadable core. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): the Tools windows work with the emulation thread on (#967) The App fills the debug views after each emulated frame (and on the UI thread inline), wires DebugSession to the debug lane, hands the core's interface over again and narrows the thread gate to "halt not available": Break, Step, breakpoints and the GDB stub stay off until phase 3b. --show-window opens a tool threaded. DebugSession::iface()/setThreadBlocked() are gone. debug_views_threaded runs the real core on the emulation thread with the session wired as App wires it. Final-review fixes folded in: - "Step a frame" refuses while the debugger is attached in the threaded case too, because advance() has no give-up. - The scanner's live column waits on its own buffer (DebugViews::serialOf) instead of on any pending view. - The -emu.csv perf trace gains a trailing fill_ns column that times the debug-view copier. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(app): correct the guard's getter list and the Step-a-frame comments (#967) CheckNoDirectIface.cmake named haltSerial() as a legal core() getter. DebugCore has no haltSerial() yet; it arrives in phase 3b. The op_view.h and op_ui.cpp comments now say that Advance also refuses while the session is attached, because emu.advance() has no give-up. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): a replaced core closes the tools like an unload (#967) Threaded openContent() on a running game publishes null and the new debug interface inside one blocking emu.call(), so the UI's syncCore() saw only the final non-null interface: closeAllTools() was skipped and the cheat scanner kept the old game's candidates, which it could then compare or write against the new game's RAM. syncCore() now closes the tools whenever it replaces a core it had (generation moved, had a core), which is what inline does when it sees null then the new interface. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): every breakpoint control greys out while the halt is unavailable (#967) With the emulation thread on, only the toolbar's Break button was gated: the margin click, the context menu's Set breakpoint and "Add at cursor" still called toggleBreakpoint(), which always refuses without the halt machine and reported the misleading "table is full / not attached" error. All three now share one predicate (canAddBp): the menu item and button grey out with haltDisabledReason() as the tooltip, the margin hover says why, and toggleBp() itself answers with that reason without touching the core. Removing a breakpoint stays allowed. The Break tooltip uses the same reason text. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(app): Tools-window actions wait for the frame in progress (#967) README: with the emulation thread on, an action that reaches the core (a Tools-window edit, a load, a state save) waits for the current frame to end, including link play's bounded peer wait. It does not run immediately. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
9c5656e271 |
perf(dsp): build the untimed DSP loop without GDB code when nothing is armed (#1057)
* perf(dsp): build the untimed loop without GDB code when nothing is armed DSPExecUntimed() is now compiled twice from dsp_exec_untimed.h: a plain instance with no breakpoint test and no idle-skip gate, and an armed instance used only while gdbArmedDSP is set. DSPExec() picks one per slice, alongside the existing timed/untimed split. The armed instance keeps the breakpoint test where it was, after the IRQ dispatch and the PC-escape bail, so a breakpoint on an ISR's first instruction still fires. Arming is sampled once per slice, as before. Preprocessed, the plain loop is develop's loop with the GDB lines removed and nothing else changed. Refs #1054 Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(debug): record the #1054 DSP loop specialisation and its layout caveat The parked specialisation now ships, so the design doc's "parked" and "~2% unexplained" text no longer describes the code. Records the A/B/B/A numbers. It also records that code layout moves this loop by about +/-1%: compiling the check out makes real games slower, and the port beats the ceiling on identical C. The claim stays narrow: yarc gains, games are neutral. It also notes that the DSP_HOOK control arm will now read about 0. Refs #1054 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(dsp): keep the local-RAM opcode fetch inside dsp_ram_8 The six fetch sites (both exec loops and the four delay-slot fetches) read dsp_ram_8[off] and [off + 1] whenever dsp_pc was below base + 0x2000. So an odd PC at base + 0x1FFF read one byte past the array. cppcheck flagged it (arrayIndexOutOfBoundsCond) once the code moved into a changed file. The bound is now base + 0x2000 - 1, the same form gpu.c already uses. That one address now goes through DSPReadWord(), which aligns. Every other PC is byte-identical. clang-tidy now skips src/jerry/dsp_exec_untimed.h the way it skips the *_simd_* fragments: on its own it is only undeclared dsp.c statics. Its code is still linted through dsp.c via HeaderFilterRegex. Refs #1054 Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
2abb032ab5 |
fix(app): end the late latch's event wait on time on macOS (#1026) (#1058)
* test(app): stall probe for late-latch waits (#1026) Dev-only, VJ_PERF_TRACE: brackets the late latch's blocking calls and, for each call that returns over 3 ms late, records the UI thread's CPU time, the main run loop's activity transitions and sampled scheduler state and stacks into <prefix>-stall.txt. The UI trace gains wait_cpu_ns. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): end the late latch's event wait on time on macOS (#1026) The late latch's SDL_WaitEventTimeout often returned milliseconds past its deadline, which started the build late and missed the vblank. Two causes, both measured on the Studio with the stall probe: - A launch from a terminal (and every ssh-run benchmark) starts the main thread at the default QoS, not user-interactive as LaunchServices does. Under CPU load the UI thread was runnable but not running. - SDL's timeout is the Cocoa run loop's own timeout, which Core Foundation fires from a dispatch worker at qos_class_main(), the main thread's QoS at launch. Raising the thread does not move that worker, and under load the UI thread slept in the run loop 3-20 ms past its deadline (run state waiting, ~0.03 ms CPU). The UI thread now runs at user-interactive QoS however it was launched. Its timed event waits (latch and threaded nap) end on a wake event that LatchWaker pushes from its own user-interactive thread. SDL's timeout stays as a 5 ms backstop. A main-run-loop CFRunLoopTimer was tried and fired ~3 ms late on every wait from a terminal launch, so it is not used. --latch-wake plain keeps the old wait for A/B runs. perf_trace_summary.py now also reports UI vblank misses and waits that end over 3 ms late. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): address #1026 review (waker test bounds, probe stack walk) - latch_waker_unit: every wake within 10 ms, all but 2 of 20 within 3 ms, so a loaded shared runner does not flake; it runs the waker on every platform (start(true)) so the Linux ThreadSanitizer job covers it. The app still starts it on macOS only. - Stall probe: bound the frame-pointer walk by the sampled SP (only the stack above it is mapped) and release the thread port. - App::shutdown() also stops the waker and the probe before SDL_Quit. - Comments/docs: re-arm granularity, early return on a stale wake, and --latch-wake plain keeps the QoS raise. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): wake on time on hosted runners and in the waker's last ms (#1026) latch_waker_unit failed on GitHub's hosted macOS VM, where every 1 ms sleep oversleeps 3-8 ms. Its absolute 3 ms bound was testing the host's timers, not the waker. Each wait is now paired with a reference SDL_DelayPrecise to the same deadline, on a thread at the waker's QoS; the waker may be at most 2 ms later at p90 and 10 ms at worst. That comparison exposed a real 0.5 ms lateness on the Studio. The waker handed SDL_DelayPrecise under 2 ms, too short for it to learn the host's oversleep, and a sub-ms macOS sleep oversleeps ~0.5 ms. The waker now slices until 3-4 ms remain, as the latch's own precise tail does: 0.002 ms late quiet and under 16 hogs. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
b63d745a1d |
docs(app): emulation thread phase 3 plan (#967) (#1056)
* docs(app): emulation thread phase 3 plan and design amendments (#967) Phase 3 brings back everything still switched off while the emulation thread runs, then flips the nightly default. The plan splits the work into four PRs: - 3a: debug views on the emulation thread (a debug command lane, DebugViews requests, DebugCore). - 3b: the debugger halt and the GDB stub (a bounded halt pump, a verdict atomic, call() give-up, a silent gdb host). - 3c: Neuro on the input snapshot (an AgentPlayer replica that runs one step per emulated frame; RAM watches through DebugViews). - 3d: VJ_BUILD_CHANNEL, so nightlies default the setting On. A G1 gate runs before 3d and makes "nothing switched off" a test. The design doc gets dated amendments to sections 5, 6, 8 and 10: - The pump's wait is bounded, because the core's GDBHalt loop owns the socket and the halt timeout. - haltedFrame stays for inline mode until phase 5. - While halted, commands are held, refused or detached-first. - gdb gets a silent host and is locked under hardcore. - The channel flag. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(app): phase 3 plan: headless-only signal handlers; command cp (#967) Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(app): phase 3 plan: fix the negative self-test command (#967) Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
e7db53678b |
Merge pull request #1042 from libretro/release/v3.10.0
release: v3.10.0 — desktop features and emulation fixesv3.10.0 |
||
|
|
304867bd48 | fix(ci): scope checkout ownership trust for core artifact staging | ||
|
|
3c683169ef | ci(core): share compile-only release builds and preserve CI artifacts | ||
|
|
2e46ca4492 | ci(app): share exact-head release packages with matching PRs | ||
|
|
b8164d81b9 | ci: share source validation runner and ensure Ruby manifest checks | ||
|
|
444ee32ead | fix(app): reject truncated Neuro profile addresses and CRCs | ||
|
|
2165519e97 | ci: allow ref-scoped temporary runners for lightweight checks | ||
|
|
f6e2e7b784 | docs(release): refresh final-core cartridge and CD boot matrices | ||
|
|
b5e4009021 | docs(release): correct GT blitter fix in v3.10.0 changelog | ||
|
|
045d2f88cd | docs(release): describe community cheat source conversion | ||
|
|
8ab7448d78 |
test(cheats): exercise pinned community sources in CI
(cherry picked from commit
|
||
|
|
98e1327cf6 |
feat(cheats): convert community source freezes with revision guards
(cherry picked from commit
|
||
|
|
abc9054ec2 |
Merge pull request #1052 from libretro/codex/community-cheat-import
feat(cheats): convert community source freezes with ROM revision guards |
||
|
|
378c15afc9 | docs: record DSTA2 GT blitter correction in v3.10.0 notes |