mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-09 17:30:53 -10:00
dependabot/github_actions/develop/actions/setup-python-7
527
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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 | ||
|
|
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 |
||
|
|
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> |
||
|
|
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]> |
||
|
|
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 |
||
|
|
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]> |
||
|
|
3c683169ef | ci(core): share compile-only release builds and preserve CI artifacts | ||
|
|
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 | ||
|
|
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 | ||
|
|
338e2b6527 |
fix(blitter): follow destination lanes for computed Z with DSTA2
(cherry picked from commit
|
||
|
|
e6fa96e1d0 | feat(cheats): convert community source freezes with revision guards | ||
|
|
f7249c4d01 | fix(blitter): follow destination lanes for computed Z with DSTA2 | ||
|
|
3cfe437271 |
fix(risc): persist and wrap the MMULT matrix address counter (#1048)
* fix(risc): persist and wrap the MMULT matrix address counter * test(risc): keep MMULT harness under the test ABI guard |
||
|
|
8287d41676 |
fix(tom): expose live PIT counters and hardware register readback (#1047)
* fix(tom): expose live PIT counters and hardware register readback * test(tom): refresh Jagniccc baseline for live PIT readback * test(tom): keep readback harness under the test ABI guard |
||
|
|
62118c0669 | docs: validate combined v3.10.0 emulation fixes | ||
|
|
f5db240244 |
fix(risc): persist and wrap the MMULT matrix address counter
(cherry picked from commit
|
||
|
|
5a2af07e8b |
fix(tom): expose live PIT counters and hardware register readback
(cherry picked from commit
|
||
|
|
fcace2d235 | fix(app): preserve playlist discs and per-disc state resume | ||
|
|
e6094a60bd | docs: validate v3.10.0 and regenerate boot matrices | ||
|
|
7e84cf227f | chore: prepare v3.10.0 app feature release | ||
|
|
0085c4485d |
feat(app): emulation thread phase 2 — achievements, netplay and busy calls (#967) (#1031)
* docs(app): emulation thread phase 2 plan (#967) Achievements and netplay back on with the emulation thread (core-event stream, rc_client_do_frame on the emulation thread, netplay as a UI-thread object fed by the stream), the cheat batch as a regular command, the 250 ms busy spinner, and the Phase 1 open items (MinGW waits, limits after a failed start, the vsync-off nap). Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): EmuSnapshot in its own file, with the core-event stream (#967) CoreEventQueue carries log lines, vj_events and option writes from the emulation thread to the UI thread in order; log lines are bounded, events and option writes never dropped. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): core log lines, events and owner option writes reach the UI thread through the stream (#967) CoreHost::onUiEvent gets every log line and vj_event on the UI thread (pinSnapshot), with an epoch that rides the command queue; option writes made on the emulation thread inside a call() are applied to the UI's mirror and the config there too, never on the emulation thread. A content switch drops queued lines and events. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): netplay works with the emulation thread on (#967) Netplay stays a UI-thread object: the core's link lines and events reach it through CoreHost::onUiEvent, filtered by an epoch bumped at every start/stop/bounce, and the endpoint bounce completes after the core has read "disabled" for 2 emulated frames (3 threaded), counted with frameCount(). Threaded twins of netplay_pair (both sides, voice modem, a mixed threaded/inline pair) and of netplay_no_partner, which must exit 1, never 3. Co-Authored-By: Claude Opus 5.5 <[email protected]> * refactor(app): the cheat list reaches the core as one regular command (#967) CheatManager::apply() builds one CheatBatch (reset, then every set) and hands it to commit, which the app posts to the emulation thread; the three sinks and the UI-thread batch collector are gone. Co-Authored-By: Claude Opus 5.5 <[email protected]> * refactor(app): RetroAchievements safe on two threads (#967) rc_client events are copied into AchEvents and applied in pump(); core RAM, the memory map and the cheat resets are reached on the emulation thread (call/post); read_memory off it answers address checks from the map without touching RAM; the rich-presence line is computed with the frame and published; the mock server is locked. Inline runs behave as before. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): high-resolution short waits for the emulation thread on Windows (#967) The MinGW runner paced threaded fast-forward at one frame per ~15.6 ms (63.4 vs 96.6 frames/s); develop's newest completed MinGW runs (x64 and ARM64) pass emu_thread_unit, but ctest hides a passing test's output so the timer note is not visible there, and no MinGW run on develop has yet carried the 1 ms precise-wait check. preciseSleepNs uses a high-resolution waitable timer, and every app job now prints emu_thread_unit's timer report. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test(app): the precise-sleep check uses the shortest wake (#967) A starved host still has some on-time wakes, while timers that are coarse by platform (every wake ~15.6 ms on MinGW) have none, so the check asserts on the shortest of 20 preciseSleepNs(1 ms) waits; the note prints mean and min. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): RetroAchievements works with the emulation thread on (#967) rc_client_do_frame (and with it rc_client_idle) runs after each frame on the emulation thread; the UI pump posts the idle while paused. A threaded headless RA run steps one frame at a time in the inline order, so every ra_* test has a _threaded twin that lands its steps on the same frames. Only the debugger tools and the GDB stub stay off. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): precise_sleep.h includes a lean windows.h (#967) emu_commands.h reaches ~24 MinGW translation units through app.h; WIN32_LEAN_AND_MEAN keeps legacy winsock and macro collisions out of them (the waitable-timer and Sleep calls need nothing outside it). A WaitForSingleObject that does not return WAIT_OBJECT_0 now falls back to Sleep(1), so a failed wait cannot make waitUntil spin. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): quiesce the emulation thread before RetroAchievements shuts down (#967) App::shutdown destroyed the rc_client while a free-running emulation thread could still be inside rc_client_do_frame or a posted emuIdle: it now unloads and stops the thread first, then ach.shutdown() (still before video.shutdown(), images.stop() needs the GL context). AchHttp::ensureWorker runs inside request()'s locked block, and shutdown() moves the worker out under the lock and joins outside it, so request() is safe from the UI and the emulation thread (new two-thread case in ach_http_test). onContentLoaded and onReset resync framesSeen_ so frames that ran before the load are not counted as the new game's. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): a call() that takes over 250 ms shows a busy spinner (#967) EmuThread::call() runs a wait hook on the waiting thread from 250 ms on; the app's hook pumps OS events and swaps a spinner drawn with scissored clears -- no ImGui frame, no call(), no read of a field only a call() writes (CoreHost counts those while the UI waits). Headless, it logs once per call. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): a failed emulation-thread start lifts the feature limits; vsync off naps to the next refresh (#967) Phase 1 final-review minors 2 and 3. Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(app): emulation thread phase 2 -- achievements, netplay, the busy spinner (#967) Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): drop the previous game's achievement trackers and indicators after a content switch (#967) Events carry the generation they were queued in; onContentLoaded and onContentClosed bump it, and drainEvents drops the stale tracker, challenge, progress, attempt started/failed and reset events (no HIDE would ever come for them). A real unlock, completions, scoreboards and the connection state are kept. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): drive rc_client's mutating calls from the emulation thread only (#967) App::reset runs ach.onReset() inside the emulation-thread call (rc_client_reset raises events); applyHardcore, setEnabled(false), logout and the pause check (rc_client_can_pause advances the pause-decay state) go through coreCall, so none of them runs while rc_client_do_frame does. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): the vsync-off present loop runs on a display-period deadline grid and handles input while it naps (#967) The nap was anchored at the swap end, so the period was refresh + build + swap (~54-57 Hz on a 60 Hz display) and no input was published during it. LateLatch::advanceGridNs / gridNapNs keep a deadline grid; App::presentNap waits for events as latchWait does and hands them to the emulation thread at once. (SDL 3.4 has no SDL_WaitEventTimeoutNS.) Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): the emulation-thread setting hint names only what is still switched off (#967) RetroAchievements and netplay work with the thread on; the Tools (debugger) menu and the GDB stub do not. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test(app): pin that a reload drops the previous game's queued core log lines (#967) The block had no onUiEvent, so it only proved option survival. It now queues a log line inside the same call as the reload, and asserts it is not delivered while the option write still is. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): final-review minors -- ci report step, inline pin, display mode, RAII guards, stale docs (#967) ci: the timer-report step is informational (continue-on-error). CoreHost::pinSnapshot applies queued option events when the UI thread is the owner (thread stopped / inline). The display period is re-read on SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED. busyFrame's UI-waiting flag and call()'s wait-hook flag are RAII. netplay.h and cheats.h comments, docs/agent/testing.md (TSan recipe) and the ach_event_unit label are brought up to date. The off-thread read_memory answer notes the rcheevos v12.5 assumption. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): serialize achievement replies and synchronize scope capture (#967) Run HTTP and mock rc_client replies on the core owner; guard scope visibility and its sample ring. Add fail-first ownership/concurrency regressions to the sanitizer suite and prevent Windows GL-loader macros from breaking the busy spinner. * fix(app): synchronize achievement UI client access (#967) --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
998dee625c | docs: make AGENTS.md the shared Codex and Claude guide (#1032) | ||
|
|
b0dc50f871 |
feat(app): controller navigation of the desktop library, with per-family and custom button profiles (#1021) (#1030)
* docs(app): controller navigation of the desktop library, design (#1021) Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(app): controller navigation of the desktop library, plan (#1021) Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): NavInput per-pad sources, supplied intents and long-press Accept (#1021) Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): controller-family menu-button profiles with a per-model custom layer (#1021) Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): a menu-button family change clears the model's overrides; ASCII minus label (#1021) Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): DesktopNav focus model for the desktop library (#1021) Pure focus over the library's regions (Favorites strip, Recents strip, grid, or the States list), reusing tv::LinearFocus/GridFocus/FocusMemory/jumpInitial. Maps pad intents to Activate/OpenMenu/ToggleFavorite/TabPrev/TabNext/ FocusSearch/Back commands, follows the focused item by key when lists change, remembers focus per tab and region, and raises a one-shot scroll flag. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): TV mode reads each pad's menu-button profile (#1021) The shell asks the host what a pad's button or trigger means (the pad's controller-family profile plus the model's custom layer) instead of the fixed Xbox table; hints name the profile's binding. Search and AcceptLong are the desktop library's, so a button mapped to one is neither fed to the TV screens nor consumed. A removed pad drops only its own holds. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): route pads to the desktop library when it owns the input (#1021) DesktopPad (tv::NavInput with long-press Accept + DesktopNav + pending commands) and deskPadActive/HandleEvent/Frame per design section 2: TV off, no running game (or a paused one with the library window open), home library or window showing, no capture or other desktop window. Pad buttons and triggers go through the pad's NavProfile, the left stick through padAxis, the right stick Y is kept for scrolling; a mapped press is consumed. Start, unmapped buttons and the keyboard fall through. Every edge of active / library window / tab / pad popup resets the nav and the queue. Script: expect desk-pad and desk-pending. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): zero the desktop nav's right-stick scroll on ownership edges and focus loss (#1021) A right stick let go while the library did not own the pad left scrollY set, so the grid would scroll on once it did. Zero it on every edge of active / library window / tab / pad popup and on window focus loss. Script: new 'axis NAME VALUE' verb and 'expect desk-scroll', plus a Settings open/close line for desk-pad (Ctrl+,). Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): pad focus, ring and actions in the desktop library (#1021) The library publishes its Favorites / Recently played strips, grid and State shots to the desktop nav each frame, draws a focus ring (theme text colour, soft shadow) on the focused tile while the pad drives, scrolls it into view only when it is not fully visible, scrolls with the right stick, and executes the queued commands: launch (arming the release guard), the tile menu and BIOS popup anchored at the tile, favorite, tab switch, search focus and Back. A pad press in the search box leaves it. The status line shows the last-used pad's button hints. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): pad scroll only for the pad, stick scroll hands focus on, shared button labels (#1021) Bring-into-view scrolling acts only while the pad drives (the mouse's view no longer snaps on a scan, search or tab click). When the right stick scrolls the focused grid tile out of view, focus moves to the nearest fully visible tile of its column (pure deskNearestVisibleIndex, DesktopNav::focusIndex without a scroll flag). The face-label logic moves into padButtonLabel (pad_profile) and is used by TV hints and the desktop hint line. The hint rides the status line (no line comes and goes) and says only what works on the States tab. A stale queued tile command is dropped, the redundant second command pass goes, and the design doc's search-box rule says any pad press leaves it. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): drive library popups with the pad through ImGui's gamepad nav (#1021) Pad-opened popups (tile menu, BIOS popup, delete modal) get ImGui's own gamepad navigation fed with our intents (directions, Accept -> GamepadFaceDown, Back -> GamepadFaceRight), so the Switch confirm/back swap holds. The SDL3 backend is in Manual gamepad mode; HasGamepad is set between its NewFrame and ImGui's, only while the popup is open. Accept acts on press inside popups. tv_ui_smoke guards that gamepad nav does not make ImGui want the keyboard. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): edit a controller model's menu buttons in the controls screen (#1021) Settings > Controls gets a Menu buttons section for one connected controller model: a layout combo (Auto shows the detected family), a Set / Default row per menu intent and Reset menu buttons. With every gamepad selected it shows the last-used pad's family table read-only. InputManager::beginNavCapture reuses the bind capture, writes no game binding, refuses the pad's TV-menu buttons, D-pad and sticks, and reports Swapped / Reserved. The headless tv script gains capture nav:INTENT and a {padguid} placeholder in expect cfg. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): menu-button Default swaps the partner back; swap note ignores hold flag (#1021) Default on a menu intent now goes through setNavBinding with the family's button, so a swapped partner gets its old button back and no two intents share one. The Swapped note compares kind/code/dir like setNavBinding. The combo is labelled Family. Script: navdefault INTENT. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): pad nav review fixes: visible popup cursor, Play via the pad path, no pad under foreign popups (#1021) - A popup the pad opened shows its focused first row at once (ConfigNavCursorVisibleAlways while its nav is on), since Accept acts on press. - The tile menu's Play row goes through activateEntry, so a pad user gets no mouse-only resume prompt and the confirm press is guarded. - The library does not own the pad while an ImGui popup the pad did not open is up (right-click or menu-bar menu, Lists popup, combo, modal). - Remove the unreachable pad hook on the delete-state modal. - Menu-button capture also refuses the pad's controls-overlay (C_HELP) button; resetNavBinding bounds-checks the intent; picking the family a pad already has no longer wipes its menu-button changes (items that do say so). - DeskLists are built and published only while the pad drives the library, and moved into the nav; Pad::nav defaults to the Xbox map; tv_host.h comment says SDL_gamepad.h is for enums only. - Docs: README and TV-mode design updates. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
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]> |
||
|
|
33446a784c |
Feature/neuro sdk (#1001)
* feat(app): Neuro API client, so an AI can play as Player 1 Settings > Neuro (or --neuro ws://HOST:PORT) connects the app to a Neuro game API server (https://github.com/VedalAI/neuro-sdk): Neuro-sama, or any backend that speaks the API (Randy, Tony, Gary), plays through registered actions. - Controller actions: press_buttons, button_sequence, hold_buttons, release_all; every schema-valid input fits the 30 s queue. - Game profiles (built-in Tempest 2000, or <data>/neuro/*.json): named button macros, an "about" text sent as context, and RAM watches in the state text. - Forces the next move whenever the last input has played out (never while paused); results are sent on validation, as the API asks. - WebSocket client on a worker thread (no TLS, no new dependencies), with reconnect; MSG_NOSIGNAL / SO_NOSIGPIPE so a dropped server cannot kill the app. - Headless --neuro runs in real time. - Tests: neuro_unit and neuro_loopback (a WebSocket server on a free port). Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs: Neuro API guide (actions, profile format, testing with Randy) Co-Authored-By: Claude Opus 5.5 <[email protected]> * refactor(app): Neuro API client in app/src/neuro/, behind VJ_APP_NEURO The neuro_* sources move to app/src/neuro/. The CMake option VJ_APP_NEURO (ON by default) builds them, defines VJ_HAVE_NEURO and adds the neuro_* tests; with it OFF the app has no Settings > Neuro tab and no --neuro. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): NOMINMAX before winsock2.h in neuro_ws.cpp (MSVC std::min) windows.h's min/max macros broke std::min on MSVC; discord_ipc.cpp guards the same way. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
0bbe016f90 |
feat(app): Ctrl/Cmd+Shift+T toggles TV mode; the TV-menu action is labelled as the TV-mode toggle (#1020) (#1024)
A fixed (not rebindable) Ctrl+Shift+T / Cmd+Shift+T toggles TV mode with or without a game running (the game keeps running, the in-game menu stays shut). It is wired into App::handleHotkey, the ImGui View menu and the native View > TV Mode item, and is ignored while a text field has the keyboard or a binding is being captured. The C_TVMENU action is relabelled "TV menu / TV mode" and the cheat sheet lists the shortcut. The headless TV script's `key` verb takes ctrl+shift+ modifiers; tv_menu_headless and platform_test cover the shortcut. Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
7d72ded8c9 |
docs(app): emulation thread phase 2 plan (#967) (#1022)
Achievements and netplay back on with the emulation thread (core-event stream, rc_client_do_frame on the emulation thread, netplay as a UI-thread object fed by the stream), the cheat batch as a regular command, the 250 ms busy spinner, and the Phase 1 open items (MinGW waits, limits after a failed start, the vsync-off nap). Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
d97d25a311 |
fix(app): follow a disc swap in the content identity; keep the .srm on the opened disc (#1010) (#1011)
* fix(app): follow a disc swap in the content identity; keep the .srm on the opened disc After File > Disc / Add disc image... booted another disc, contentPath()/contentName() and everything keyed on them still named the first disc. CoreHost now keeps two identities: - contentPath()/contentName(): the disc in the tray. It moves only after an insert that worked and left the tray closed (followMountedDisc). The core moves its image index before it tries the insert and keeps it when the insert fails, so the index alone is not enough. Off the owner thread these answer from the snapshot, since discSetEject/discSetIndex may run as queued commands. - loadedPath()/loadedName(): what was opened. The .srm (and the second-copy fallback) is named after it, and Restart reopens it. The core keeps SAVE_RAM (the EEPROM pair and the Memory Track) across an insert and never reloads it, so re-keying the file would write disc A's Memory Track into B's .srm. App::followDiscSwap (main loop, addDisc, the headless check) catches up on a swap: - save-state slots and thumbnails follow the tray (statePath reads contentName): the core refuses a state taken on a different disc (savestate v14 disc identity); - achievements use rc_client_begin_identify_and_change_media, so the session and hardcore survive a disc of the same game; an unidentified first disc gets a fresh identify instead; - cheats re-key (the core re-reads the per-title row on insert, titledb_probe_disc); - a queued state load and the resume offer for the previous disc are dropped. The window title and Discord read contentPath every frame, so they follow without changes. Recents, the library's recently played and the per-game control overrides stay with the opened disc: reopening must start from its .srm. --disc-swap now checks after each step that the content and the state slot name the disc in the tray while loadedPath and saveRamPath stay on the opened disc, and saves a state on the second disc. The three disc-swap tests get their own --data-dir so that state lands in the build tree. Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit b96ce11c4a7499fddf001038beab660b05fa1664) * docs(app): the content identity a disc swap moves is read from the snapshot Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit c7e7dfc772750dffd25dfe9221c92faa6a980651) --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
cd39de7eef |
fix(app): register the loaded CD with disk control so the disc menu works (#1003) (#1006)
* fix(app): register the loaded CD with the core's disk control so the disc menu works (#1003) The core starts its disk-control image list empty even after it booted a CD (libretro.c: "nothing to insert (image list is empty)") and has no .m3u loader, so the app's Open/Close Tray item, File > Disc and Cmd::DiscTray / Cmd::DiscSelect were unreachable for every title. - CoreHost::loadContent appends the CD it loaded (cue/cdi/chd/iso only: the core registers disk control for a cartridge too, and closing the tray would try to mount the cartridge as a disc), and only while the core's list is empty. - CoreHost::discAppend: RetroArch's "Disk Image Append", owner-only. - File > Add disc image... (Cmd::DiscAdd, ImGui and native menus): a file picker; the image joins the list and is selected, so a closed tray swaps at once. Inside EmuThread::call like the other disc commands (an insert can re-stage the CD BIOS, #967). - Headless --disc-swap IMAGE drives addDisc, Open tray, File > Disc and Close tray, reading the disc list the menus read (the pinned snapshot when threaded). smoke_disc_swap (inline) and smoke_disc_swap_threaded (--emu-thread on, label emu_thread) swap synth_jagcd.chd -> synth_jagcd_multi.chd -> synth_jagcd.chd; with the registration removed both fail with "the loaded disc is not in the disc list (0 image(s))". No core change. Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit 0033152e1f9835b148e41dacffce78202a64dc92) * test(app): disc swap threaded in real-BIOS boot mode, where each insert re-stages the CD BIOS (#1003) Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit 98599c6c905e945c5bd2c4421053526e531f3f96) * fix(app): disc labels keep only the file name on Windows too (#1003) The core's disk_get_image_label splits the path on '/' only, so on Windows File > Disc listed whole 'D:\...\Disc.chd' paths, and smoke_disc_swap failed on the MinGW runner. The label now loses everything up to the last '/' or '\\' app-side; the core is unchanged. Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit 08b340f41dc0f30a8a74bc2ea7ed9107f2acdf3b) --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
1b18d49117 |
fix(app): emulation thread presents as evenly as the single-thread loop (#967) (#1018)
* test(app): VJ_PERF_TRACE frame/take timing trace + evenness model (#967) VJ_PERF_TRACE=<prefix> with --perf-frames writes <prefix>-emu.csv (each emulated frame: due on the pacing grid, start, end, trim, audio queue, serial) and <prefix>-ui.csv (each UI iteration: take time, serial shown, swap begin/return, the late latch's wake/target/budget), all on SDL_GetTicksNS, so frame ends and take points can be placed against the vblank. VJ_PERF_CSV counts frames run between takes, not frames shown. pacing_clock_test --model prints the fake-clock model: the +/-0.5% trim moves the grid's phase ~1 ms per minute even with 5 ms device-pull jitter, while repeats+skips grow ~32/min per ms of frame-end spread. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit 462c14627faf7940f222f87f1947070e590041b0) * fix(app): late latch: cap late-start exemptions, one long build no longer sets the budget (#967) Two items from the Phase 1 review: - a wait that overran its wake time is excused from the miss rate only kMaxLateExempt (8) times in a row; a wait that always overruns is the latch's own cost and must be able to release it; - the budget is the second-slowest of the last 60 builds: one long build (a window opening, a shader compile) misses its vblank whatever the budget says, and as the max it also gave up the wait, or moved the take point several ms earlier, for the next second. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit bdc878609859e4f719d1377cb0d4afb882c47343) * fix(app): mode A steers the emulation grid's phase off the UI's take point (#967) The threaded loop repeated and skipped frames 60-160 times a minute where the 60.054-vs-60 Hz beat owes 3-4 skips. A frame ends at its due time plus its wake-up lateness and cost, so while the slowly drifting grid carries a take point through that spread, whether each frame makes the take is decided by its own cost: spread / 15 us frames in a row come out as random repeat + skip pairs. The single-thread loop never sees this: it runs the frame for each vblank inside that iteration. PacingClock now remembers the last 120 frame ends against their due times, and when the next frame's window (p2..p98, plus 0.75 ms) would contain a take point, steps the grid once across it in the direction the beat already moves it. Each crossing is then the one skip (or repeat) it has to be. Only the phase moves, by at most the window once per beat; the rate stays the trimmed audio rate (pacing_clock_test: 3 ppm over 150 s against a 200 ppm sound card, no underrun). The UI publishes its take time and the display period while the latch has the vblank grid locked (with or without the wait); --pace-steer off disables it for A/B. Model (pacing_clock_test --model): 4 ms cost spread, 60 Hz, take 3 ms before the vblank: 133 repeats + 136 skips per minute unsteered, 0 + 3 steered (4 steps); 120 Hz: 577 frames held 1 or 3 refreshes -> 7. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit a01504788087a0d3160242f0ee13a89699517b94) * docs(app): mode A phase steering in the emulation-thread design (#967) Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit 133ee01a2f4b51f85f83473a060455808465289c) * fix(app): steering holds the end window across cost switches (#967) Measured on the Studio (yarc, VJ_PERF_TRACE): the emulation thread wakes 0.5-3 ms late and the frame cost switches with the content between ~7.5 and ~2.9 ms for seconds at a time. Two faults showed in the first steered run (12 repeats + 35 skips, 29 steps): - the 'not before this frame can end' guard used the window's p98, so after a switch to cheap frames every earlier step was refused while the ends dithered on the take point; the guard is now only 'not before the frame starting now' (a step that lands early just starts late); - the 120-frame window forgot the slow mode within 2 s, so the switch back landed the ends on the take point; the window's edges are now held and shrink back at 0.2 ms/s. pacing_clock_test models both (wake lateness + a switching cost, six take offsets): 18-38 repeats+skips per minute before, 3-5 after. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit 90643d1e44af0b4de36c95576a28618f3c23849d) * fix(app): steering follows the trimmed drift, at most one step per 30 frames (#967) Studio, Cybermorph, steering v2: 20 repeats + 38 skips and 98 steps. Every step went earlier (the untrimmed 60.054-on-60 Hz direction) and queued its few ms of audio; 55 ms in a minute held the trim at +0.17%, past the beat's 0.09%, so the grid was drifting the other way and each step put the window back in front of the take point. The direction now comes from the trimmed period, which makes the steps self-correcting (a late step drains what an early one queued). A floor of 30 frames between steps keeps any other mistake from becoming a display lock. pacing_clock_test: a grid slower than the panel (a 0.15% slow sound card) went from 5224 steps and 0 repeats (a lock) to 4 repeats and 5 steps in 120 s; a 9 ms window: 6-7 events in 120 s. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit 395e255eeb49c69171ccf03a64ebc9bae71b8ddf) * docs(app): VJ_PERF_TRACE summary script, --pace-steer in the README (#967) Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit 1cbe57b59b135a0a280d14ca4545742e24897c6f) * fix(app): steer only where the grid creeps along the take grid (#967) At ratios off a whole number (50.08 Hz PAL on 60 Hz, 60.05 on 75 or 144 Hz) successive frames land ~3 ms apart on the take grid, so some frame's end window always holds a take point: that is 5:6-style judder, not a slow crossing. pacing_clock_test showed 99-119 steps a minute there and the rate pulled off the audio clock. Steering now needs the untrimmed creep to be under 0.2 ms a frame (60.05 Hz on 60/120/59.94 Hz: 15-32 us): 0 steps, no underrun, rate kept at all three ratios. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit 9adae8e4d64c897a6834f4d6e98f4eed6f048a94) * test(app): perf_trace_summary.py computes event -> present exactly from the trace (#967) ~11,000 virtual events per run instead of 120 injected keys, whose run averages moved by up to 3.4 ms between identical runs. Refs #967 Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit 469cf23cbabc1e63b5cce10b52d06d1608dd7873) --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
df0f6d271b |
feat(app): emulation on its own thread, phase 1 (#967) (#1000)
* feat(app): FrameExchange triple buffer for the emulation thread (#967) Frame moves to frame.h so CoreHost, FrameExchange and VideoOut share it. Content epochs keep a previous game's frame from ever being shown. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): EmuCommands, the emulation thread's ordered command queue (#967) Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): PacingClock mode A, the audio clock as master with an even frame grid (#967) A wall-clock grid at the core's period, trimmed at most 0.5% by the smoothed SDL queue level: true Jaguar rate on the sound card's crystal without the 10.7 ms device-pull jitter a raw queue trigger would add. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): PacingClock refill rebases the grid so it converges (#967) During refill frames start at up to 2x the core rate but the grid still advanced a full period per frame, leaving it ahead of the clock by the extra audio just queued. The loop then slept that long, drained the queue below the low-water mark and re-armed refill: bursts and ~66 ms stalls forever, with underruns. Rebase the grid on each early start while refilling, and make testRefillFromEmpty check evenness, rate and underruns after the refill. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): EmuThread, the single owner thread of the core (#967) post() at frame boundaries in order, call() that runs inline on the thread itself and after a stop, callFor() and a join timeout for a core stuck in retro_run, advance() for headless and tests, inline mode for everything else. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): InputSnapshot, the newest input for the emulation thread's poll (#967) States are newest-wins; pointer motion is summed until a frame takes it and is delivered exactly once. InputManager::poll can run on the UI thread with the keypad overlay ticking per emulated frame. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): EmuThread: pending call returns on abandon, exact advance, rate-based pacing test (#967) call() polls for abandonment instead of blocking forever on a queued task; advance(n) counts only budgeted frames so a frame already in flight cannot satisfy it; the pacing test checks rates over measured windows. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): AudioOut safe across the emulation and UI threads; mode-A update (#967) update(false) counts underruns without nudging the playback ratio: in mode A the pacer follows the audio clock and the two controllers must not fight. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): CoreHost owner thread, EmuSnapshot and queued writes (#967) Inline nothing changes. With an owner, the UI reads options, AV info and discs from a pinned EmuSnapshot, option values through a UI-side mirror (the config is still written on the UI thread), and its writes reach the core at the next frame boundary. Any other core call off the owner is refused, counted and logged once. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test(app): the real core inline vs on the emulation thread, frame for frame (#967) Also pins a load while frames run (no stale frame, empty buffer inside the load call, rising serials) and the owner rule. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): CoreHost publishes the snapshot before a load call returns (#967) A load or unload made through EmuThread::call() returned before the batch ended, so the UI could pin the previous option table, AV info or disc list. finishLoad, stopSession, a failed startSession and the disc eject / index commands now publish themselves. With no post set while threaded, queued writers are counted and dropped instead of recursing into themselves. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): Settings > Interface > Emulation thread and --emu-thread (#967) Off by default in every build for now (nightlies default it on in phase 3, once nothing is switched off while threaded). Decided once per run; headless runs stay inline unless --emu-thread on. Nothing starts a thread yet. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): VJ_PERF_CSV input-latency and underrun columns, --perf-input (#967) Latency is the oldest input event's SDL timestamp to the core's poll; the first three columns are unchanged. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): content and state actions through EmuThread::call; headless --emu-thread on (#967) Load, boot BIOS, unload, reset, save and load state, SRAM flushes and the shutdown unload run where the core lives; inline they run in place as before. The save-state thumbnail is the frame the state was taken on. UI checks ask "halted?" instead of CoreHost::inFrame(). The headless smoke tests pass with --emu-thread on with the inline hashes. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): switch off what is not thread-safe yet while the emulation thread is on (#967) RetroAchievements, netplay, the Tools windows and the GDB stub option are off for a threaded run (phases 2 and 3 bring them over). Cheat changes from the Cheats window reach the core as one command, never half a list. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test(app): windowed emulation-thread check for the Mac Studio (#967) Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): the interactive loop with the emulation on its own thread (#967) The UI thread polls and publishes input, takes the newest frame, drains messages and counters and presents; it never waits for the emulation. The 5 s SRAM flush is posted. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): VJ_PERF_CSV event-to-present column (#967) The swap that first shows a frame which polled an input event, timed from the event: inline from the iteration's oldest polled event, threaded through the InputSnapshot publish number each frame carries. --perf-input now injects its key from an SDL timer at a swept delay, so events land evenly over the display period in both loops. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): late latch for the threaded loop (#967) With the emulation thread and vsync on, the UI thread waits until just before the next vblank (the slowest recent UI build plus a pad that grows on a missed vblank), publishing input as each event arrives, then takes the newest frame. It tracks the vblank grid from swaps that block and stays off while it cannot. The fake-clock model: event -> present 25.3 ms for 5 ms core frames (inline 25.0, unlatched 46.4); 34.2 ms for 14 ms frames, which only mode B can fix. --late-latch off for A/B measurement. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): late latch backs off on persistent misses, hysteresis (#967) The latch now releases (with a 300-swap cool-down) when more than a quarter of its last 60 waits missed the vblank they aimed at, so a build whose GPU work lands late cannot keep every frame a vblank behind. Once locked it lets go on jitter only at 2 ms (locks below 1 ms), and a wait that itself overran its wake time no longer counts as a miss. The locked/released log line and the perf note show misses, releases and the pad. Co-Authored-By: Claude Opus 5.5 <[email protected]> * ci(app): ThreadSanitizer job over the emulation-thread tests (#967) VJ_APP_TSAN instruments the app, the core, SDL and the tests; the job runs ctest -L emu_thread and fails on any race report. tsan_canary proves the sanitizer is really on. Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(app): the Emulation thread setting (#967) Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): route UI disc commands through call() so staged_ is never written off the UI wait (#967) A disc insert can re-stage the CD BIOS in the core (stage_cd_bios via open_disc_and_resolve_boot), which writes CoreHost::staged_ from the VJ_EVT_BIOS_STAGED/REJECTED event. Posted from the UI, that write ran on the emulation thread while the BIOS panel read stagedBios() every frame. The File menu tray/disc items and desktop.cpp toggleDiscTray/selectDisc now run inside EmuThread::call (in place when inline), like reset. core_host.h's threading comment and the plan's boundary table say so. Latent today: the app never registers disc images with the core. Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(app): thread-test fail patterns, README frame-skip wording, two comments (#967) - README: the drift alone skips a frame about every 18 s, but frames finishing near the take point also repeat/skip (~1-2.5/s measured at 60 Hz); the display-locked mode planned later addresses it, hence off by default. - emu_thread_flag_rejects, emu_thread_limits, netplay_refused_threaded and gdb_locked_threaded only had a pass regex (ctest then ignores the exit code): they now also fail on "ThreadSanitizer|owner violation". - late_latch.h: kLockAfter counts tracked observations; single outliers do not reset it. emu_thread.h: a call queued before an abandon may still run later on the detached thread, so captures must be by value. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test(app): emu-thread tests on CI runners: Windows timer, MSVC capture, libdbus TSan (#967) - emu_thread_unit raises the Windows timer resolution to 1 ms (timeBeginPeriod, winmm), as SDL_Init does in the app; at the default ~15.6 ms, fast-forward could not beat normal speed (63 vs 97 frames/s on the Windows runners). - Its "keeps running" check is a deadline poll instead of a fixed-window quarter-rate floor, which a starved macOS runner missed (6 of 36 frames). - emu_commands_test: kEach is static constexpr, so MSVC does not need it captured (C3493). - tsan.supp: suppress libdbus's own lock-order-inversion between SDL_Init and SDL_Quit on the main thread (system library, not instrumented); it was the only report in the Linux TSan job, on every threaded app run. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): precise emulation-thread waits on MinGW (#967) MinGW's timed condition wait (winpthreads) rounds short timeouts up to the scheduler tick (~10-16 ms) even with the 1 ms timer resolution SDL sets, so threaded fast-forward ran below normal speed on the MinGW Windows runner (67.5 vs 98.2 frames/s; MSVC was fine) -- and the shipped Windows packages are MinGW builds. EmuCommands::waitUntil now waits on the condition only while more than 20 ms remain, then polls in 1 ms sleeps until the deadline or a post()/notify(). Other platforms keep the plain condition wait. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test(app): emu_thread_unit reports the host's timer precision for the fast-forward check (#967) The MinGW runner still showed fast-forward at one frame per ~15.6 ms (64 vs 98 frames/s) after the polling wait, which points at the platform's timers rather than EmuThread. The test now measures sleep_for(1 ms) and the steady_clock step and prints them with the rates. The fast-forward ratio is still asserted whenever it holds or the host can pace 2.5 ms frames; it is reported instead only when it misses on a host whose sleeps exceed 6 ms or whose clock steps exceed 1 ms (or that cannot even sustain normal speed). Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
e49d5649a9 |
feat(app): TV shell with Home, Library and Game details (TV mode phase 2) (#999)
* docs(app): flesh out TV mode phase 2 plan against the merged phase 1 code (#991) Screen stack beside the in-game menu, TV-owned GameRef/Art/StateRef host types, geometry from mockups A and B, test bodies for Home/Library/Details, Auto acting as Manual until phase 4, and the spec changes phase 2 needs approved. Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): TV mode state machine and input ownership (#991) Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): TV mode fullscreen hold is state, leaving fullscreen turns TV off (#991) Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): TV shell, Manual TV mode, --tv and the View menu entry (#991) Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): TV shell leaves bind capture and text fields their input; clickable section tabs (#991) Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): TV Home screen with shelves and hero panel (#991) Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): TV Library screen with rail, cover grid and letter jump (#991) Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): TV pad trigger glyphs, held Backspace stops at an empty filter (#991) Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): TV Game details and the TV-mode toggle in the in-game menu (#991) Co-Authored-By: Claude Opus 5.5 <[email protected]> * feat(app): TV script verbs for windowed screenshots (wait library, shot, quit) (#991) Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): TV hint bar band and device-true hints; Home hides a shelf that cannot fit (#991) Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): TV mode final-review fixes: fresh launches, focus follows the game, input ownership (#991) - A launch from a TV screen starts the game fresh: the resume prompt needs a mouse (X and Game details already offer the auto-save). - ui.tv_mode is read, never written, until the setting has its UI (phase 4), so the Auto default can still reach users. - Focus on Home shelves and Library lists follows the game's key, so coming back from a game lands on it after Recent reorders. - A focused desktop text field keeps the keyboard (no forced WantCaptureKeyboard); OS key-repeat of nav keys is dropped (NavInput repeats directions itself); an open desktop window takes the pad away from the TV screens under it. - Empty Library grid shows only the hints that work; Home says "Scanning your library..." during the first scan; Ctrl/Cmd+L and File > Game library open the desktop library window while TV mode is on. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |