Commit Graph
220 Commits
Author SHA1 Message Date
Mihawk 2512f39519 Game Forwarder: the WebUI's own page for home screen tiles
A "Game Forwarder" entry in the WebUI's sidebar (webui/forwarders.js, its section in
webui/index.html): the tiles with their picture, name, game, core and state (on the
home screen, appears / changes / leaves once RetroArch closes), a New tile editor, Edit
and Delete. ShadowMountPlus's state heads the page; without it, nothing can be made.

The editor: the system first, each behind its icon as on the Games page (a listbox
like the language picker's, by mouse or keyboard), then one of its games by name; the
name on the home screen and the core, prefilled with the game's; "Close RetroArch when
the game ends"; and the picture, drawn here in a 512x512 square and sent as a PNG:
box art, 3D box, logo, cartridge or disc always offered (greyed out when the game has
no such file yet, with a pointer to Download Media), screenshot, title screen and fan
art when it has them, RetroArch's icon; a cutout fits the square, a picture fills it
(both can be changed), and the system's icon can sit in the bottom right corner on a
dark plate. Saving says "Saving..." while ShadowMountPlus answers.

A tile opens a game in the content folder (a path inside it) or on a USB or extended
storage drive (its own path), as the library lists them (src/ps5_forwarders.cpp).
36 new texts, in every language of the WebUI (tools/webui-strings.py --check).

Evidence:
- tests/test_webui_forwarders.py test_page_in_chromium (tests/webui_forwarders_browser.cjs,
  Chromium against the native server and the ShadowMountPlus stand-in): the system
  picker (icon, listbox, Escape, choice), the game, name and core prefilled, the
  pictures offered and greyed out, a logo fitted and a cover filling the square, the
  system icon's plate in the corner, a tile created (its icon a 512x512 PNG), edited
  (picture kept, corner and fill options off), deleted; no page error; nothing wider
  than a 390 px phone - OK. The route tests take the drive paths' rule (a "/mnt/usb0/.."
  path refused) - 4 tests OK.
- console (base PS5, ShadowMountPlus 1.7-beta5fix1), the page in a browser: the tile
  list read from the console; a SNES game's tile made with its 3D box fitted
  (PPSA99170, "appears on the home screen once RetroArch closes"); a second, waiting
  tile deleted from the page at once; 33 systems in the picker, each with its icon.
- gates: format, unit (210 tests), build, integration (210 tests), evidence - PASS.
2026-10-18 07:40:22 -04:00
Mihawk 8a31a1f2b2 Game Forwarder tiles: the WebUI's routes and ShadowMountPlus, made, edited and removed
src/ps5_forwarders.{h,cpp}: a tile is /data/homebrew/<id> (PPSA99170 to PPSA99269, an
id no folder has and ShadowMountPlus knows nowhere) with the tile app and payload of
step 1, libc.prx, a param.json for its id and name, an icon and forward.txt (the
arguments, which also mark the folder as a tile: nothing else is listed, changed or
removed). ShadowMountPlus's HTTP API (127.0.0.1:10101, its docs/api.md 1.7) puts it on
the home screen: manual/add at once, installed when no game runs.

Routes (src/webui_ps5.cpp): GET/POST /api/forwarders (the list with ShadowMountPlus's
state, the cores, each tile's state; create), POST /api/forwarders/edit?id=,
PUT/GET /api/forwarders/icon?id= (a 512x512 PNG, up to 2 MiB), POST
/api/forwarders/delete?id=. The library JSON carries each game's core, to prefill it.

ShadowMountPlus refuses to uninstall while a game runs, and RetroArch is one whenever
the WebUI is up: a removal of an installed tile takes it out of the manual list at
once (so no scan installs it again) and is finished by the WebUI's daemon once the
title has closed (daemon/webui_daemon.cpp -> finish_pending): uninstall, then delete
the folder. A new name or icon on an installed tile is re-registered there the same
way (uninstall, back in the manual list, scan), and every tile's app and payload are
brought up to the build's.

Found on the console and handled: ShadowMountPlus 1.7-beta5fix1 can drop a source from
its manual list as it installs it (its next scan reads app.db before the install shows
there); the tile stays installed, and a re-registration adds it back first. Its game
list can take longer than 5 s after RetroArch starts: 20 s now, and a list that does not
arrive says why instead of showing every tile as waiting.

Evidence:
- host: tests/test_webui_forwarders.py against a stand-in for ShadowMountPlus's API:
  create (first free id past a plain folder and a title ShadowMountPlus knows), the
  files and param.json, validation (name, content path, "..", absolute path, newline,
  core), edit, icon (size and format refused, stored, served), removal while busy
  (pending, out of the manual list) finished by finish_pending, at once when not
  installed, re-registration after the source was dropped, binaries refreshed,
  ShadowMountPlus absent - 3 tests OK.
- console (base PS5 13.40, ShadowMountPlus 1.7-beta5fix1), through the WebUI from the PC:
  GET /api/forwarders: ShadowMountPlus answers, the test tile PPSA99170 installed;
  POST create -> PPSA99171 (eboot.bin, launch.elf, libc.prx, icon0.png, param.json
  UP9000-PPSA99171_00-FORWARDER0099171, forward.txt with five arguments), in the
  manual list; RetroArch closed -> ShadowMountPlus "installed game PPSA99171";
  the tile opened -> tile.log "110832 sent to 127.0.0.1:9021 (errno 0)", launch.log
  five arguments, RetroArch "forwarder: /app0/content/snes/<game> with
  /app0/cores/snes9x_libretro.so, closing after it"; rename and icon -> updating,
  delete PPSA99170 -> pending (manual list empty); RetroArch closed -> daemon
  "forwarder PPSA99170: removed", "forwarder PPSA99171: installed again with its new
  name or icon"; /user/appmeta/PPSA99171 titleName "Forwarder renamed" and the new
  icon (first pixel 20 60 d0), /user/appmeta/PPSA99170 and /data/homebrew/PPSA99170
  gone, app.db 33 -> 31 -> 32 entries; the daemon also replaced PPSA99170's older
  launch.elf with this build's.
- gates: format, unit (209 tests), build, integration (209 tests), evidence - PASS.
2026-10-18 00:56:35 -04:00
Mihawk a0ea678ee2 Game Forwarder tiles: the tile app and its launch payload, built into the title
A home screen tile is a title of its own (PPSA99170 to PPSA99269) that starts one
game in RetroArch. Titles cannot launch titles (sceSystemServiceLaunchApp returns
0x8094000F from one, sceLncUtilLaunchApp is a null import), so the tile hands a
payload to the console's ELF loader on 127.0.0.1:9021 and exits:

- forwarder/tile.c: the tile's eboot.bin. Reads its title id from
  /app0/sce_sys/param.json, stamps it into /app0/launch.elf's slot, sends it to
  the loader, logs /app0/tile.log, and leaves through LoadExec("exit") (a return
  from main ends in a SIGSYS error notice).
- forwarder/launch.c: the payload. Reads /data/homebrew/<tile>/forward.txt (one
  argument a line: --rom, --core, --exit-after-game, src/frontend_mode_ps5.cpp),
  waits for the tile to leave, and starts PPSA99169 with those arguments through
  sceLncUtilLaunchApp (sceSystemServiceLaunchApp as the fallback).
- tools/build-forwarder.sh builds both; tools/build-title.sh puts them in
  dist/PPSA99169/forwarder/ and counts them in the title's identity; the notices
  cover both files.

Evidence:
- host: python3 -m unittest tests.test_forwarder (the tile stamps and sends the
  payload byte for byte, refuses a lowercase id, a missing or a doubled slot; the
  payload passes the five arguments, falls back to SystemService, and an
  unstamped payload starts nothing) - OK.
- console (base PS5, 13.40, ShadowMountPlus 1.7-beta5fix1), the test tile
  PPSA99170 registered through ShadowMountPlus's manual list:
  tile.log "tile PPSA99170: launch payload 110832 bytes, 110832 sent to
  127.0.0.1:9021 (errno 0)"; launch.log five arguments, sceLncUtilLaunchApp
  00002018; RetroArch's trace "forwarder: /app0/content/nes/<game> with
  /app0/cores/fceumm_libretro.so, closing after it"; the tile exited cleanly
  (no error notice), back to the home screen after the game.
- gates: format, unit (206 tests), build (tile 11,123 B, payload 110,832 B),
  integration (206 tests; a first run timed out starting two scraper test
  servers under load, both passed alone and on the rerun), evidence - PASS.
2026-10-18 00:23:06 -04:00
Martin Pham 20dd7fe082 Home screen forwarders start a game with --rom and --core
From mihawk-99/PS5_RetroArch#34 (Martin Pham). A forwarder, a small app
with a home screen tile of its own, starts the title with --rom <file>
[--core <core>] [--exit-after-game] (each also as --x=<value>). On a
launch that names no mode, src/frontend_mode_ps5.cpp takes it as game
mode takes a frontend's request: the content (absolute, or inside
/app0/content/ and never above it) and the core are checked by
ps5_game_check, RetroArch starts with -L, and afterwards the title
opens as from the home screen, or closes with --exit-after-game.
Without --core the playlists choose, else the one core whose info file
lists the extension (Genesis Plus GX for the Mega Drive extensions both
Sega cores list) or, for a folder, the one core that opens folders. A
refusal goes to trace.txt and the launch goes on as from the home
screen; a handover's --ps5-mode wins over --rom.

Changed from the pull request: its README examples and test fixtures no
longer name games and use the ES-DE folder names content/ has now; one
header comment is moved to its declaration. Its line-ending rewrites of
the PowerShell scripts are dropped (they are gone, 8f14452), and its
GitHub workflow is parked in parked/github-build-workflow, with what
would finish it at the top of build.yml: it cannot run here, and it
does not fetch PS5_MihawkGen, which the frontend compiles now.

To prove it on a console, whose control payload launches without
arguments: the relaunch test takes its restarts' arguments from the
lines after the arm line, and tools/run-title.sh --relaunch-arg writes
them.

Evidence: tests/frontend_mode_ps5_test.cpp (parsing, paths, cores by
playlist, extension and folder, refusals, after the game);
tests/relaunch_ps5_test.cpp (arguments carried; a control character or
a ninth argument arms nothing). Gates: format, unit (204), build
(staged cores reused), integration, evidence PASS. Console: with this
series' deploy.
2026-10-17 21:42:17 -04:00
Jorge Feitosa 6f03aa224a Microphone input, optional melonDS DS core and its touchpad stylus (0134, 0135)
From mihawk-99/PS5_RetroArch#53 and #52 (Jorge Feitosa), renumbered
(0131 to 0133 were taken), taken together: #52 fixes the read loop #53
turns on.

- src/microphone_ps5.cpp (0134): a native AudioIn microphone driver,
  the default one, for the first controller's user. It captures only
  while a core has its microphone on, sends silence under the DualSense
  mute, and keeps PCM only in a bounded queue in memory. RetroArch is
  configured with the microphone; the title links a host stub for
  libSceAudioIn's imports.
- 0135: a microphone read that gets no frames returns silence instead of
  looping forever (a paused core, an empty nonblocking device, a failed
  capture).
- tools/build-melondsds.sh: melonDS DS at pinned revisions (git objects
  by SHA), software renderer and a native-memory JIT, staged only with
  PS5_WITH_MELONDSDS=1; DeSmuME stays the DS default. The DualSense
  touchpad drives its stylus as it drives DeSmuME's.

Changed from the pull requests: the README and the 0134 entry describe
what happens, not an opt-in the code never had. RetroArch's microphone is
on by default, as upstream; melonDS DS listens only while its microphone
button is held (its default), and Settings > Audio > Microphone turns it
off for every core. tests/test_microphone_read.py applies the port's
edits to vendor/retroarch itself rather than reading build/ra-conf, which
a tree configured before 0135 would fail. The SwanStation and melonDS DS
title changes are merged into one licence-file rule.

Evidence: tests/test_microphone_ps5.py (the driver against a simulated
AudioIn: lifecycle, mute, input failure); tests/test_microphone_read.py
(it times out with upstream's loop); tests/test_touchpad_stylus.py
(melonDS DS's relative stylus); tools/build-melondsds.sh, core ABI PASS
(25 exports, kernel_web, sha256 b03d4c93...). A title built with both
optional cores: 61 notice parts, check-notices covers every shipped
executable, check-manifest PASS, 78 melonDS DS options in the catalog.
Gates (default build): format, unit (204), build (staged cores reused),
integration, evidence PASS. Console: with this series' deploy.
2026-10-17 21:25:33 -04:00
Jorge Feitosa c1ed6e5a2a Optional SwanStation core (PS5_WITH_SWANSTATION=1)
From mihawk-99/PS5_RetroArch#54 (Jorge Feitosa). tools/build-swanstation.sh
builds libretro/swanstation at a pinned revision (archive SHA-256 checked)
as a native shared core: Vulkan, the x64 recompiler on title executable
memory (ps5_exec_allocate) with LUT fastmem instead of the mmap path.
The platform changes are adapted from AngelXex's SwanStationPS5 1.3.4,
credited in the patch, the README and the notices. A title build stages it
only with PS5_WITH_SWANSTATION=1, with its licence file beside the info
file; Beetle PSX HW stays the PlayStation default, and the WebUI catalog
lists its options when it is staged.

Changed from the pull request: tooling/swanstation/README.md no longer
names the game it was tested with.

Evidence: tools/build-swanstation.sh, core ABI PASS (25 exports,
kernel_web, sha256 dfb1521f...); tests/test_optional_core_metadata.py
(catalog with and without the core). A title built with
PS5_WITH_SWANSTATION=1: 60 notice parts, check-notices covers every
shipped executable, check-manifest PASS, 113 SwanStation options in the
catalog. Gates (default build): format, unit (201), build (staged cores
reused), integration, evidence PASS. Console: with this series' deploy.
2026-10-17 21:06:25 -04:00
Jorge Feitosa a8bda031f0 FLAC for the frontend's CHD reader, so achievements hash CD FLAC discs (0133)
From mihawk-99/PS5_RetroArch#50 (Jorge Feitosa), renumbered: 0131 and
0132 were taken. chdman lists CD FLAC among a CD's codecs by default,
and libchdr refuses a CHD that declares a codec it was built without, so
the frontend could not open such a disc to hash it for RetroAchievements
even where the core read it with its own copy of libchdr. RetroArch is
now configured with builtin FLAC; build-title.sh leaves out the one FLAC
define whose value is a string, which tools/build.sh cannot take as a
name and the title's sources do not use.

The CHD reader's FLAC codec decodes with dr_flac (deps/dr, public domain
or MIT-0). libFLAC is compiled into libretroarch.a, but nothing calls
it: the linked title holds no FLAC__ symbol and 51 drflac ones, so the
notices are unchanged.

Evidence:
- tests/test_chd_flac.py: the configure flags enable builtin FLAC; the
  vendored reader, compiled as Makefile.common compiles it with builtin
  FLAC, opens tests/chd/tone-cdfl.chd (synthetic: a second of a 440 Hz
  tone and silence, chdman 0.289 -c cdfl, 3,783 bytes) and decodes its
  75 frames to the generated PCM byte for byte; without FLAC it fails.
- Host, with a CHD chdman made from one of the owner's PlayStation
  backups (cdlz, cdzl, cdfl; 2,338 of 31,002 hunks CD FLAC): the reader
  from build/ra-conf answers "unsupported format" without FLAC, and with
  it opens the disc and reads all 31,002 hunks with their CRCs.
- Console, before this change (build 22b5d122, offline, a test user):
  Beetle PSX HW loads that CHD, and RetroArch's hash answers "Could not
  open track" for each of nine candidate consoles, then "hash generation
  failed". After: with this series' deploy.
Gates: format, unit (199), build (staged cores reused), integration,
evidence PASS.
2026-10-17 20:52:55 -04:00
MihawkandMarco Tundo 72ed08b7b8 Frame generation off presents each game frame once (0132)
The two parts of mihawk-99/PS5_RetroArch#62 (Marco Tundo) that 0131 did
not already cover. Frame generation's plan writes the presents per game
frame into video_shader_subframes, which retroarch.cfg keeps. Once frame
generation was turned off, in its menus or in ES-DE's, the 2 it left
there held every game frame for two refreshes: half speed on a 60 Hz
output. retroarch.c now sets one present per game frame whenever frame
generation is off, and logs it when it replaces a larger number.
Turning frame generation on or off in the menus reinitializes the video
output, as a Target Framerate change already did, so the plan applies
at once (src/ps5_framegen_menu.c).

A Shader Sub-Frames value set by hand no longer survives a core load
while frame generation is off; sub-frame shaders have not been tested
on this console.

Evidence: tests/test_framegen_plan.py runs the plan as the script
inserts it. Two new cases start from a 2 left in retroarch.cfg with
frame generation off, on a 60 Hz output and on a VRR one; both fail
with 0132's edit left out. Gates: format PASS; unit PASS (197 tests);
build PASS; integration PASS; evidence PASS (131 captures). The build
gate ran with PS5_REUSE_STAGED_CORES naming the 33 shipped cores: core
stamps hash absolute paths, so in a worktree every core looks changed,
and the staged cores are main's. Console run: with this series' deploy.

Co-authored-by: Marco Tundo <marco@bymar.co>
2026-10-17 20:37:25 -04:00
Mihawk 8f14452e5d Linux only: the PowerShell scripts from the boilerplate go
The eight tools/*.ps1 scripts came in with 4df5c68 from
ps5-native-app-boilerplate and nothing in the build, the gates or the
docs runs them: the project builds and deploys from Linux only.
tools/prepare-assets.sh only wrapped prepare-assets.ps1, so it goes
too. .gitattributes loses the CRLF rule for *.ps1 and tools/lint.sh
no longer lists .ps1 among the files whose headers it checks.

Evidence: git grep finds no reference to any removed script;
tools/verify.sh format PASS; tools/verify.sh unit PASS (283 s,
klog/unit-gate.log).
2026-10-17 19:57:40 -04:00
Mihawk 6b70c9b130 Content folders are named as ES-DE names its systems; older folders follow
Testers kept reporting folders EmulationStation did not detect. The title
made a folder in content/ for each system named as RetroArch's databases
are ("Sony - PlayStation 2"); it now names each as ES-DE names the system
("ps2", "snes", "genesis", the 50 platform ids, all of them ES-DE system
names checked against ES-DE 3.5.0's own systems file), so content/ is
ES-DE's own layout, <ROM folder>/<system>.

- ps5_library_make_system_folders makes content/ first when it is missing:
  a copy of the title without its empty folders had none at all, because
  the frontend made content/ only later and the step gave up.
- ps5_library_rename_system_folders, at each start before any frontend:
  a folder an earlier build made under RetroArch's name is renamed to the
  ES-DE one, or merged into it when none of its entries' names is taken
  there (else left as it is, still its system's). The paths in RetroArch's
  playlists (and their scan folders) and the per-folder overrides, core
  options, shader presets and remaps under config/ follow; a core whose
  folder bears the content folder's own name (MAME, ScummVM) keeps its
  files, which are that core's. Folders the user named (PS1) stay as they
  are and are still recognised.
- The WebUI's Add Game puts a game in the system's ES-DE folder.
- README: content/<system>/ with ES-DE's names (content/Saturn/ was out of
  date: only system/Saturn is made).

Evidence: tests/ps5_library_test.c (system folders by ES-DE name, a missing
content folder made, a rename, a merge, a clash left in place, a folder
whose name only begins the same, playlists, per-folder settings, a core
folder named as the content folder): python3 -m unittest
tests.test_ps5_library OK. On the console (evidence/esde-folder-names,
with the commits after this one): a home launch renamed three seeded
folders (a rename and two merges), the playlist and options file followed,
and every ES-DE system path is /app0/content/<name>. tools/verify.sh: all
five gates PASS.
2026-10-17 19:35:49 -04:00
Mihawk 43216d5d5a build: the WebUI's CA bundle no longer needs ES-DE's checkout first
build-title.sh copied Mozilla's CA bundle from .deps/es-de, which only
tools/build-esde.sh clones, and it does so after this step and only for a
build with frontends. With .deps emptied, neither the development build
(the build gate) nor the all-target build could pass: both stopped at the
missing bundle. The bundle is now fetched by itself from ES-DE at the commit
build-esde.sh pins (one pin, read from that script), checked against its
sha256 (f66dff1b..., the same file ES-DE's checkout holds), and kept in
.deps/downloads.

Evidence: the pinned URL returns sha256 f66dff1bdf8f9606...; the
all-target build (PS5_FRONTENDS='es-de picker') then ran from a fresh
.deps through ES-DE, the picker and the notices to a verified package
(dist/PS5_RetroArch.zip, python3 tools/check-notices.py dist/PPSA99169:
the licences cover every shipped executable).
2026-10-17 18:21:04 -04:00
Mihawk 5c4256b202 Frame generation plans on the flips the output really shows (0131)
The 59.94 Hz mode takes 120 flips a second on a VRR display and 60 on an
output without 120 Hz. 0121 assumed 119.88 Hz for every 59.94 Hz mode, so
on a 60 Hz output each picture was presented twice and games ran at half
speed with broken audio (a tester's v2.0.0-beta-2 logs: "display 119.880
Hz ... each shown 2x", 58.8 presents/s, the core at 29.6 calls/s).

Patch 0131. khr_display_ctx.c times flips once per process, on the first
swapchain, where the mode is below 100 Hz: the median of nine acquire
intervals once every image is queued, 1 s timeouts, and on a failure a
warning and the mode is believed. vulkan_common.c keeps the rate. The plan
in retroarch.c takes 119.88 only above 100 Hz, and a Target Framerate
above the display's rate becomes the display's (120 on a 60 Hz output is
60, and a 30 fps game still gets a frame made between its frames).

Evidence: evidence/framegen-flip-rate (python3 tools/evidence.py compare
evidence: 128 replayed, 0 failed). tests/test_framegen_plan.py composes
the plan from EDITS as the script applies it and runs nine cases; with
0131's edits left out it fails on the 60 Hz case. The eboot.bin built from
this tree (identity 1f81446a..., sha256 d3dc2a31...) ran on the tester's
60 Hz output, and the owner reports it worked perfectly; no log of that run
was received. Not run yet: the owner's base PS5 with its VRR display (the
console did not answer).

format PASS; evidence PASS; unit 192 of 197 OK. The 5 errors are tests
that read .deps/{ppsspp,azahar,desmume}-src, deleted with .deps on
2026-10-15 along with every core source the build gate's catalog step
reads.
2026-10-17 17:02:49 -04:00
Mihawk 7d45412b4b Frame generation from public PS5_MihawkGen, pinned; the RetroArch shim in this repository
The generator (framegen/src, framegen/include) and its trained network now
come from PS5_MihawkGen (github.com/mihawk-99/PS5_MihawkGen, public),
pinned at c57232a; a release build refuses any other revision. Its sources
and the d5 network are byte-identical to what the title built before (the
network sha256 31ecc7d6..., the four generator sources and the compiled
shaders; framegen.h and nn_final.comp differ only in a comment). The
RetroArch driver shim (vulkan_framegen.cpp/.h, from PS5_FrameGen 9490b0d)
moves to tooling/framegen, so all of the frame generation code in
eboot.bin is published. tooling/notices: a mihawkgen component with its
source address.

format PASS; unit (196 tests) OK.
2026-10-14 20:48:51 -04:00
Mihawk 517673894c No game titles in the repository; the v2.0.0-beta-2 release note
Every game title in tracked text (test fixtures, comments, patch notes,
console evidence, the v1.0.0-beta.1 note's alt text) is now a made-up
stand-in that keeps the original's shape: its punctuation (apostrophes,
"&", " - "), region tags and, where a test depends on it, its sort order
and how a title relates to its alternate name. Evidence folders and the
Flycast pad script named after games are renamed. Only the committed
handoff/PPSA99002/eboot.bin still holds a title, inside the binary.

releases/v2.0.0-beta-2.md: PS5SX2, frame generation, RetroAchievements,
NetPlay, the WebUI in 16 languages, the library and frontend changes,
the systems tested on a Base PS5, upgrading from v1.0.0-beta.1, source
and credits. It opens with the anti-piracy banner and points, names no
game and shows no game picture: three WebUI screenshots in
assets/releases/v2.0.0-beta-2/.

Gates: format, unit (196 tests), build, evidence (127 replayed, 0
failed), check-manifest and check-notices: PASS.
2026-10-14 20:35:04 -04:00
Mihawk ccb9da7fab PS5SX2: the fork's work is on its main branch 2026-10-14 19:53:03 -04:00
Mihawk 0bca2863e6 Gates: test stubs for frame generation; comment wrapping; TLS and password patches renumbered 0129/0130
- tests/input_ps5_test.cpp: ps5_fg_record_request stub (the pad script's
  RECORD calls PS5_FrameGen's recorder, not linked in the host test).
- tests/test_webui_qr.py: stubs for the frame generation entries 0120 binds
  beside the WebUI's (ps5_fg_menu_is_label/_is_folder, action_ok_ps5_fg,
  action_ok_push_default).
- src/main.cpp, src/ps5_framegen_menu.c: tools/lint-format.sh --write
  (comments re-wrapped to the line limit; no code changed).
- The frame generation code already names patches 0122-0128, which
  patches/series does not list; 47879d1's TLS and password patches took
  0122/0123 and are now 0129 (TLS verification) and 0130 (password).

lint-format PASS; tests test_input_ps5, test_webui_qr, test_frontend,
test_webui_secrets: OK.
2026-10-14 19:27:23 -04:00
Mihawk 47879d118e HTTPS verifies certificates; the RetroAchievements password is not kept; names are never translated
- 0122: HTTPS uses MBEDTLS_SSL_VERIFY_REQUIRED (upstream verified optionally
  and ignored the result), so nobody on the network path can stand in for
  RetroAchievements. Its chain (GTS Root R4, cross-signed by GlobalSign Root
  CA) verifies against the bundled 2017 roots.
- 0123 and src/ps5_cheevos.c: a sign-in that returns a token removes
  cheevos_password from /app0/config/webui.cfg (with the empty token the
  WebUI puts beside a new password) and from retroarch.cfg, which keeps its
  token. The WebUI clears the old token when a new password or account is
  saved, so the new account signs in with its password.
- WebUI: game, file, system and source names carry data-no-translate, and a
  value inside a translated sentence is translated only on an exact match:
  "Shadow of the Colossus" no longer reads as "{0} of {1}" ("Shadow sur the
  Colossus" in French).
- LRPS2 leftovers: the 34 orphan option texts in each catalog, the stale
  wording in ps5sx2_libretro.info and build-ps5sx2.sh. Patch count 255 -> 257.

Console (192.168.50.237, klog/retroarch-191705.log): DNS, "TLS connected",
mihawk99 logged in, game 2992 identified with verification required; after
the run webui.cfg holds no cheevos_password and retroarch.cfg an empty one
with its token kept.
Tests: test_webui_secrets (token cleared; helper removes the password, keeps
the token in retroarch.cfg), test_webui_language (a file named Shadow of the
Colossus stays as it is in French), test_frontend: OK. lint-shell PASS;
lint-format fails only in src/main.cpp and src/ps5_framegen_menu.c, not
touched here.
2026-10-14 19:19:14 -04:00
Mihawk 8289d7ee40 PS5SX2: pin the core at dba9ef4 (blocking-core protocol, dithering option) 2026-10-13 16:04:57 -04:00
Mihawk 5b2dc7f42c FrameGen: blocking-core protocol, floor opt-in, mvsplat in the build, recorder width hook
apply-port-patches 0122-0128: private env commands 0x25001 (the core blocks inside retro_run) and 0x25002 (emulated vsyncs a frame
covers, period in microseconds) size the generator's group from what the core says, never from a measured gap; the floor is opt-in
(PS5_FRAMEGEN_FLOOR=1); core run/cpu times in the log. The menu and its config follow. build-retroarch.sh compiles fg_splat.cpp.
main.cpp reads framegen-record-width.txt for the recorder. input_ps5.cpp: reflowed comments and calls.
2026-10-13 08:50:58 -04:00
Mihawk 6937678fdc PS2: PS5SX2 replaces LRPS2 (tools/build-ps5sx2.sh); LRPS2 removed from the build
The PS2 core is now PS5SX2 (Swordpdf's PCSX2 for the PS5) from the fork
../Libretro_PS5SX2, branch libretro, pinned at 308e8dc. build-ps5sx2.sh
fetches and pins its dependencies (rapidyaml 0cd25a1, glslang 275822a built
for the console, shaderc v2026.2 headers), builds the core with the fork's
ps5/libretro/Makefile.libretro, ABI-checks it and stages PCSX2's game
database in system/ps5sx2/resources. PS5SX2_DEV=1 builds the fork as it
stands. BIOS and memory cards stay in system/pcsx2/ (shared).

LRPS2 is gone: build-lrps2.sh and tooling/lrps2, its notices component,
WebUI catalog and settings-guide entries, and the title's core list.
build-title.sh empties dist/{cores,info} before staging, so a core taken
out of the list does not linger. The balanced 4K default (0102) now sets
ps5sx2_upscale to 4x; the core offers up to 8x (6x is 4K).

Also: patch 0118's tests (the block count, the XMB icon names test).

Console evidence (192.168.50.237, RADV; tools/run-title.sh --game=ps5sx2):
- Shadow of the Colossus (USA) boots and plays (klog/game-122302.png).
- tests/pad/ps5sx2-states-4k.txt: frames of 3072x2688 at 6x; a 46,562,360
  byte state saved and loaded from the Quick Menu (klog/retroarch-124621.log).
- RetroAchievements: SET_SUPPORT_ACHIEVEMENTS, account logged in, disc
  hashed, the core's memory map published (klog/retroarch-125231.log).
- pcsx2_libretro.{so,info} deleted from the console's cores/ and info/.

Gates: format PASS; unit and integration fail only in
test_input_ps5.test_joypad_bindings and test_webui_qr (the uncommitted
frame-generation and input work in src/, not part of this commit); notices
PASS (33 cores); manifest PASS; evidence 127 replayed, 0 failed. The title
was built with the other cores reused (PS5_REUSE_STAGED_CORES).
2026-10-12 13:05:52 -04:00
Mihawk 453049da68 Pad script RECORD: the present-stream recorder; run-title.sh brings the recordings back 2026-10-11 22:19:13 -04:00
Mihawk 4f3945f111 Statistics overlay names Base Rate and FrameGen; hardcore mode off by default (seed and compiled default) 2026-10-11 17:11:47 -04:00
Mihawk 32efd9b173 FPS line: Base and FrameGen rates when frames are generated; the counter shows the core's own rate otherwise 2026-10-11 17:06:15 -04:00
Mihawk ed396f39ea FPS counter: the core's own new frames a second, the presents a second only while frames are generated; the load-state trace 2026-10-11 16:52:01 -04:00
Mihawk 50a3d09ee7 Governor level, calls and floor in the driver; floor and honest overlay in the menu code; the early presents default fixed 2026-10-11 14:10:44 -04:00
Mihawk 72a3bac0b3 Target Framerate (60/120), the timeline of pictures, any-cadence generation, present sequence log, honest overlay 2026-10-11 14:03:38 -04:00
Mihawk da4cf320b9 Frame generation: real frames of pixel-art content come from the held copy; present spacing in the status log 2026-10-11 10:56:21 -04:00
Mihawk 3b59e0d59f Remove the small-change skip: every sprite moves at the output rate 2026-10-11 07:50:31 -04:00
Mihawk e707d3e30f Frame generation: frames where only a small part of the picture changed are shown as the core made them 2026-10-11 07:35:35 -04:00
Mihawk 328b7e7457 The on-screen frame rate counts presented frames (shader subframes) 2026-10-11 07:13:21 -04:00
Mihawk 472975d5e0 Frame generation: the cadence check also reads the clock (a core that does not hand over unchanged frames); lighter status display 2026-10-11 02:14:46 -04:00
Mihawk f02c762ad7 Frame generation: only a steady cadence is interpolated 2026-10-11 01:04:21 -04:00
Mihawk 490a9da74c Frame generation: Output Rate Auto (0121), pacing on the real 119.88 Hz, per-second pace log 2026-10-11 00:38:36 -04:00
Mihawk 0b581dd6b8 Frame generation menus: XMB leftmost tab, RGUI first entry, ES-DE settings entry (0120)
Options (on/off, output rate, method, cadence, working size, status on screen) are shared
through /app0/config/framegen.cfg, which the title appends when it starts RetroArch.
2026-10-10 23:27:48 -04:00
Mihawk c85dd735b5 Frame generation: PS5_FrameGen's network shows the frames between content frames (0119)
The Vulkan driver's shader subframes present the generated frame at j/N
between the previous and the current content frame (video_framegen, with
video_shader_subframes 2 to 4). The hooks are anchor edits; the generator and
its shim are built from ../PS5_FrameGen and the trained network is staged at
assets/framegen/network.fgw.
2026-10-10 21:06:03 -04:00
Mihawk fc1df57bfe XMB: playlist tabs show their system's hardware (issue 35)
tools/xmb-system-icons.py makes, at build time, one tab icon per RetroArch database the
title knows (src/ps5_library.c's platform table: 48), from the WebUI's system artwork
(webui/assets/systems, Linear ES-DE, MIT): each picture cropped to its hardware (the logo
and year under it removed; XMB prints the name), centred, 256 x 256 PNG, named
<database>.png in assets/xmb/monochrome/png, where XMB looks for a playlist's icon.
Game rows keep XMB's own content icon (no <database>-content.png). A playlist with
another name keeps the default icon.

Notices: the generated icons are their own part (MIT); retroarch-assets now names its
files exactly, so neither claims the other's (fnmatch's * crosses "/").

Evidence: python3 -m unittest tests.test_xmb_assets (one icon per database, 256 x 256
RGBA, the caption cropped off, no -content icons, each icon owned by one licence); the
build printed "[xmb] 48 system icons" and tools/check-notices.py passed. Contact sheet
checked by eye. Console screenshot not taken: the title refused to start on this
console ("startup refused: elevation or installation could not be verified").
Gates: bash tools/verify.sh -> verify: PASS (format unit build integration evidence)
2026-10-10 14:27:18 -04:00
Mihawk bead80725c Start the bundled Lapy service when RetroArch launches
Package the qualified title-specific resident service and start it through
local elfldr before workers when no service is ready. Wait for preflight,
verify SDK elevation and UID 0, and reuse the service on subsequent launches.
Keep installed paths and offline fallback; no Payload Manager boot hook.

Console evidence: evidence/lapy-autostart/capture.json and expectation.json.
Reproduce with evidence/lapy-autostart/README.txt, then
python3 tools/evidence.py compare evidence/.

Verified on the base PS5: cold service startup, two authenticated achievement
sessions, reuse of one service across three requests, and NetPlay relay join
with 3837/3361 input packets and 52 matching state checks. Normal title exits;
root counts 73/72 before each request. Resident service remains running.
Installed binary hashes and unchanged Payload Manager configuration verified.

Validation: 194 unit and 194 integration tests; format; all-target build with
PS5_FRONTENDS='es-de picker' and the existing pinned build/picker-template;
manifest/notices/source bundle checks; 127 evidence captures replayed.
No secrets, saves or raw console captures included. No push.
2026-10-10 11:26:09 -04:00
Mihawk 56ecbf367f Verify RetroAchievements and NetPlay after cooperative elevation
Qualify the console build with authenticated achievement-set loading, a fresh
badge download, FCEUmm LAN hosting and relay joining. The relay session exchanges
scripted controls and passes 55 direct emulation-state CRC comparisons. Normal
quit returns the elevation service's root counts to baseline.

Expose DNS/TLS failures and add a debug-only trace at the upstream state CRC
comparison. This adds one port hook, so the intentional patch-count assertion
changes from 253 to 254. Keep the requested Online Updater removal and repeatable
RetroArch runner arguments. The verified build uses the standard SDK socket API.

Online startup still requires the qualified external Lapy service; no autoload
is installed. Document the force-close teardown timeout and untested lobby/NAT
scope. Credentials, settings, saves and raw console captures are not committed.

Validation: PS5_FRONTENDS='es-de picker' with the pinned picker template;
tools/verify.sh format unit build integration evidence passed (193 unit,
193 integration). After moving only the debug trace to the received-CRC path,
24 focused frontend/patch tests and format/build passed again, followed by the
final console runs. Final format/evidence, manifest and notices checks passed.

Evidence: evidence/online-features/capture.json and expectation.json.
Replay: python3 tools/evidence.py compare evidence/ (126 captures, zero failures).
Console reproduction: evidence/online-features/README.txt and console_netplay.py.
2026-10-10 10:53:45 -04:00
Mihawk b9260ae31c Keep existing installation paths usable after optional elevation
Request cooperative SDK elevation before RetroArch workers only when the
qualified external daemon is ready. Verify UID and executable identity, map
legacy filesystem paths, and retain shell LoadExec names. Picker and ES-DE
keep their normal sandbox startup. Missing daemon remains a normal launch.

Verification: tools/verify.sh format unit build integration evidence, run
with the pinned picker template. Format passed; 193 unit and 193 integration
tests passed; all shipped targets built; 123 evidence captures replayed.
Generated picker bootstrap repairs and earlier failed trials are recorded.

Console: console_test.py final-return-ra, final-esde and final-offline;
executables read-back verified; current settings preserved; test controls
removed. Badge PNG downloaded and decoded. Hosting reached player 2 once;
joining and account authentication remain unresolved. No WAN/lobby claim.
Artifact: evidence/installed-paths/{capture,expectation}.json and README.txt.
Reproduce comparison: bash tools/verify.sh evidence.

Pre-existing networking/updater/runner edits remain outside this commit;
the capture identifies the tested working-tree context.
2026-10-10 10:04:27 -04:00
Mihawk 5e1b956a37 HTTPS: seed RetroArch's mbedTLS from arc4random_buf, not /dev/urandom
RetroArch's mbedTLS 2.6 registers /dev/urandom as its platform entropy source
unconditionally, and one failing source fails the random generator's seed, so every
TLS connection (RetroAchievements, HTTPS downloads) would fail if a title cannot open
it, which nothing here has shown it can. --wrap=mbedtls_platform_entropy_poll gives it
the title's arc4random_buf instead (the platform layer's, which the WebUI already
draws its session tokens from). No vendor change.

Evidence: evidence/network-build now records the registered source:
"<__wrap_mbedtls_platform_entropy_poll> -> arc4random_buf (= ps5_arc4random_buf)".
Gates: bash tools/verify.sh -> verify: PASS (format unit build integration evidence)
2026-10-10 01:50:45 -04:00
Mihawk 44143aba19 RetroArch with networking: netplay, RetroAchievements and HTTPS
configure now enables networking, netplay LAN discovery, RetroAchievements (rcheevos)
and SSL through RetroArch's own mbedTLS (deps/mbedtls, its bundled CA certificates):
nothing new is linked from outside the tree. tools/build-retroarch.sh passes
-DHAVE_BUILTINMBEDTLS, which libretro-common's TLS socket needs and only upstream's
console makefiles pass by hand.

Name lookups: the SDK links getaddrinfo to a resolver that refuses every lookup (the
real one is in a module titles do not load) and no module exports getnameinfo.
src/net_shims.c replaces libretro-common's getaddrinfo_retro, freeaddrinfo_retro and
getnameinfo_retro (--wrap; wrapping getaddrinfo itself still bound the SDK libc's
refuser) and resolves names with the console's own DNS (libSceNet's
sceNetResolverStartNtoa); getnameinfo is numeric. Every RetroArch caller (netplay, the
lobby, HTTP, NAT traversal) reaches it; only mbedTLS's unused mbedtls_net_connect/bind
keep libc's.

Evidence: evidence/network-build (from build/llvm-pie.elf and build/ra/libretroarch.a:
the configure switches, 28 rcheevos and 60 mbedTLS objects, the 9 resolver callers,
the sceNetResolver imports, no null imports); python3 -m unittest tests.test_net_shims.
Console behaviour (sign-in, netplay) is not yet verified: no launcher was available.
Gates: bash tools/verify.sh -> verify: PASS (format unit build integration evidence)
2026-10-10 01:16:31 -04:00
Mihawk bf9b897313 ES-DE: six more themes to choose from (Colorful, PlayStation-X, Art Book Next, ...)
EmulationStation now ships, beside Alekfull NX (still the default) and Linear:
Colorful (Simplified), Colorful (Revisited), PlayStation-X, Art Book Next, Retrofix
(Revisited) and Artflix (Revisited), all from ES-DE's own theme list. Each is pinned
by commit in frontends/es-de/themes.list, fetched by tools/build-esde.sh, copied by
frontends/es-de/stage.sh without artwork sources, and credited in the notices
(CC BY-NC-SA 2.0 or 4.0, non-commercial; Artflix states no licence of its own and
follows its origin, Alekfull's ARTFLIX). A user picks one in ES-DE's UI Settings.

Not included: Next-pixel and Not-so-epic exist only as legacy Recalbox/Batocera themes,
a format ES-DE 3 no longer loads; no ES-DE version of either was found (ES-DE's list,
GitHub, GitLab).

The title folder's es-de/themes is 847 MB; the release ZIP grows to 1.53 GB.
Evidence: python3 -m unittest tests.test_esde_themes (pins, notices, fetched commits);
the build printed "es-de: resources 17M, 8 themes in 847M" and the ZIP lists the eight
theme.xml files.
Gates: bash tools/verify.sh -> verify: PASS (format unit build integration evidence)
2026-10-10 01:16:31 -04:00
Mihawk 45f3718fdb WebUI: the page in 16 languages, chosen in the header or Settings, kept on the console
webui/i18n.js shows each English text of the page from webui/i18n/<code>.json:
English, Spanish, French, German, Italian, Portuguese (Brazil), Russian, Japanese,
Korean, Chinese (Simplified and Traditional), Vietnamese, Turkish, Polish, Dutch and
Indonesian, 776 texts each (tools/webui-strings.py lists them and checks every
catalog). Pickers with flags (lipis/flag-icons, MIT; vn, tr, pl, nl, id added): a
compact one in the header (the flag alone on a phone) and every language as a tile in
the Settings footer. The choice is POST /api/preferences, kept in
config/webui-preferences.cfg and reported by /api/status, so every device follows it
within a status check (10 s). Not covered: right-to-left languages; option texts the
cores provide stay as written.

Evidence: python3 -m unittest tests.test_webui_language: catalogs complete; the
preference refuses unknown codes and wrong tokens; Chromium finds no English text left
on any page in French, follows a choice made in another browser, works by keyboard,
and fits a 390 px phone with no sideways scroll (screenshots in
build/tmp/webui-language).
Gates: bash tools/verify.sh -> verify: PASS (format unit build integration evidence)
2026-10-10 00:47:48 -04:00
Mihawk 213a4321f7 WebUI: guided Settings for the 18 newer cores
tools/generate-core-metadata.py now extracts the option tables of a5200, the
Beetle NeoPop/PCE/PC-FX/VB/WonderSwan cores, DOSBox-pure, Flycast, Handy,
NeoCD, Opera, PicoDrive, PokeMini, ProSystem, PUAE, ScummVM, Stella and
Virtual Jaguar: table names are detected, v1 tables are dumped
(dump-options.cpp), DOSBox's namespace tables, NeoCD's runtime builder and
Stella's legacy variables are handled. The 15 existing catalogs are
byte-identical. build-title.sh fails when a shipped core (rpcs3 aside) has no
catalog.

Evidence: build/webui-core-metadata/index.cfg lists all 33 cores; the WebUI
in a browser lists 33 profiles and Beetle PCE shows Video, Audio, Input,
Emulation Hacks, PC Engine CD and General.
Gates: bash tools/verify.sh -> verify: PASS (format unit build integration evidence)
2026-10-09 23:14:22 -04:00
Mihawk 6fa72d46e7 Content: a folder for every system the installed cores run
content/ had only the folders people made (and a bare "Saturn"), so a fresh install
gave no hint where games go (issue 33). ps5_library_make_system_folders makes
content/<system> for each system the installed cores' databases name, with RetroArch's
names ("Nintendo - Nintendo Entertainment System"), which every frontend's library
reads as that system. A system with a folder already, under any name ("PS1",
"Genesis"), gets none: nothing is renamed or moved. A database no platform stands for
(Satellaview, Sufami Turbo, PICO, CD32, CDTV) gets none; variants go to their system
(Wii (Digital) -> Nintendo - Wii).

- src/main.cpp runs it at each title start, before the frontend dispatch (a home-screen
  launch goes to the picker from there; RetroArch's own init is too late), so a core
  added later gets its folder.
- tools/system-folders.c runs the same code on the staged title in build-title.sh:
  dist/PPSA99169/content holds the 46 folders of the 33 cores; video-assets.py keeps
  empty folders in the release ZIP.
- The bare content/Saturn the title made is dropped (Sega - Saturn replaces it).

Evidence: evidence/issue-33-system-folders (console: 45 folders -> 52 after a home-screen
restart, the 7 missing systems added 0777, the owner's kept; dist and the ZIP: 46);
tests/ps5_library_test.c (10 made, an existing folder honoured, unknown databases and
cores without .so skipped, a second run makes none). tools/verify.sh: PASS (format unit
build integration evidence).
2026-10-09 22:29:13 -04:00
Mihawk 55e6878f42 Apply galaxy artwork to README banners and screenshot frames
Use the approved banner, four subtle section accents and self-contained SVG frames preserving original screenshot bytes. Keep all README notices, headings, system icons and renderer rows.

Verification: python3 tools/make-readme-frames.py; source and XML checks; browser loads 74 images without overflow; bash tools/verify.sh format evidence passes with 118 captures. Evidence: evidence/readme-galaxy. No new dependencies. Release-note changes excluded.
2026-10-08 13:26:36 -04:00
Mihawk 04cd031dc0 Add DualSense touchpad stylus control to DS and 3DS cores
Adapt Proton contact tracking for relative hover and physical press/drag/release.
Keep touchpad Select in menus and other cores. Add resolution-scaled mouse
motion and clipped stylus cursors to DeSmuME and Azahar (Vulkan/software),
with reproducible core patches, build provenance and licence attribution.

Verified: format; 182 host/integration tests; all shipped native targets;
core ABI, manifest and licence checks; 103 evidence captures replayed.
Deployed build 6da657c4 to the base PS5; stored SHA-256 of eboot.bin and both
cores matches the stage. Physical controller/TV acceptance remains pending.

Reproduce:
PS5_FRONTENDS='es-de picker' PS5_VULKAN_TEMPLATE="$PWD/build/picker-template" tools/verify.sh
python3 -m unittest tests.test_input_ps5 tests.test_touchpad_stylus

Evidence: evidence/touchpad-stylus/capture.json and expectation.json.
Raw logs: klog/touchpad-stylus/ (ignored).
2026-10-07 19:10:53 -04:00
Mihawk 3de11ab36e Fix RetroArch WebUI QR action registration
Bind WebUI in the common action initializer instead of the custom keyboard
branch. Preserve patch markers so existing configured trees update too.
Production callback/dispatch regression fails before the fix and passes
after it; picker and ES-DE binaries remain byte-identical.

Resolve the integration fixture race exposed by this run: browser covers
now use valid PNG data and assertions require decoded images.

Validation: PS5_FRONTENDS='es-de picker' PS5_VULKAN_TEMPLATE="$PWD/build/picker-template" tools/verify.sh
Format, unit (177 tests), and build passed. After correcting the fixture,
tools/verify.sh format integration evidence passed (177 tests).
Evidence: evidence/webui-qr-actions/capture.json and expectation.json;
reproduce with python3 tools/evidence.py compare evidence/

Deployed build 0bdc8dd8: 6 changed files verified; exact stored-byte eboot
digest matches. Owner confirmed picker/ES-DE; XMB/RGUI live recheck pending.
2026-10-07 18:33:46 -04:00
Mihawk bf859b0d77 Add runtime WebUI QR access to every frontend
Picker Triangle toggles and Circle dismisses; XMB gets a WebUI tab left of
Settings; RGUI shows WebUI first; ES-DE replaces Scraper with WebUI.
Share runtime IPv4 discovery and an unchanged MIT Nayuki QR encoder.

Verify: PS5_FRONTENDS='es-de picker' PS5_VULKAN_TEMPLATE="$PWD/build/picker-template" tools/verify.sh
All five gates pass; 176 tests in each pass. Independent QR decoding,
address changes/offline recovery, picker input and menu routing tested.
Evidence: evidence/webui-qr/capture.json and expectation.json; replay with
python3 tools/evidence.py compare evidence/

Deployed build 700259e0: 9 changed files verified, 13614 retained. Exact
stored-byte digests match all three frontend binaries. On-console scans
and controller checks remain pending; no title launch performed.
2026-10-07 17:59:40 -04:00
Mihawk cdc2b0716c curl for the PS5: mbedTLS built thread-safe (the WebUI daemon crashed)
The daemon died at start on the console (SIGSEGV, fault address 0) and never came up:
the backtrace (klog, symbolized against build/webui-daemon/ps5-retroarch-webui.elf)
runs from a scraper worker's download through curl_easy_perform into
mbedtls_ssl_write_client_hello -> mbedtls_ctr_drbg_random_with_add ->
mbedtls_entropy_func -> mbedtls_sha512_update. curl 8.22 shares one CTR-DRBG (prediction
resistance on) among all its connections and relies on mbedTLS's own locks for it; our
mbedTLS 3.6.7 was built without them. With 32 workers (a034b6e) starting handshakes
together the shared generator was torn; the job resumed by itself at every start, so
every start crashed.

tools/build-curl.sh now sets MBEDTLS_THREADING_C and MBEDTLS_THREADING_PTHREAD
(scripts/config.py) before building mbedTLS. Why a flag in a shared script: curl's
mbedTLS backend requires it whenever handles run on several threads; the ES-DE build
that links this curl gains the same locks.

Evidence: libmbedcrypto.a's ctr_drbg.o now calls mbedtls_mutex_lock (pthread mutexes in
threading.o); the daemon's mbedtls_ctr_drbg_random calls the lock before and after its
work (objdump). tools/verify.sh: PASS (format unit build integration evidence). The
console run follows the deploy.
2026-10-07 16:04:38 -04:00