Build pinned mGBA with this PS5 SDK, official metadata and both GB/GBA engines.
Support validated C initializer arrays, native directory/time imports and an
explicit config path. Map large title/core allocations so GBA archives load.
Correct mGBA callback colours and cached frame ownership, crop padded core
textures during presentation, and use the native shell exit after cleanup.
Verified: bash tools/verify.sh (format, unit, build, integration, evidence;
58 tests), then bash tools/run-title.sh --no-build --core-test=mgba --watch 180.
Owner confirms GB/GBC/GBA loading, correct colours/native Quit, and clean menu
and next game. Final capture has zero Vulkan refusals, GPU failure records or
kernel fatal-signal reports; normal frontend exit occurred before window end.
Evidence: evidence/mgba-native/{capture,expectation,iterations,build,abi}.json,
loader/recovery reports and exact linked-driver hashes. Replay with
bash tools/verify.sh evidence. Final build identity:
82024b89e74bbad4ad2ce2e2916d1a49baf26ac325fef20fc71a2c168b862337.
Patch count is deliberately 139: archive diagnostics, menu/cache ownership and
padded-source sampling edits. PS5_Vulkan was not modified. Saves/states,
long-run timing and a fresh FCEUmm/XMB transition matrix remain unverified.
Replace payload dlfcn hooks with a bounded ELF loader and explicit native
runtime bindings. Separate core RX/R/RW segments, reject unsupported ELF
features, and release failed loads. Guard failed menu selection before
frontend teardown and stop polling drivers after failed initialization.
Read core files through mapped buffers and bounded POSIX reads. Convert
libretro XRGB8888 frames to matching RGBA8 staging/sampled textures so real
content uses the verified Vulkan copy path rather than the RGB565 compute
branch. Keep video_ps5 selectable and preserve frontend/audio/error logs.
Validation: tools/verify.sh passed all five gates (52 tests). Console command:
tools/run-title.sh --no-build --core-test --watch 180, with manual core/game
selection. Eight ELF load/unload cycles and live-menu failure recovery passed;
owner confirmed flawless gameplay. Zero GPU refusals/API failure records and
kernel fatal signals across captured launches. Return-to-menu shortcut and
repeated game unload/reload remain unverified; owner closed/reopened the app.
Evidence: evidence/native-core-loading/{capture,expectation,loader,recovery,
abi,iterations}.json; replay with tools/verify.sh evidence. Accepted title
identity 9b287ca7e0d4a04721c68d30fb88b678a62cc568c1f2d44b75716b464c21b6d5.
No PS5_Vulkan edits by this task; its independently advancing checkout is
not used as a substitute for the accepted title's archive-bound identity.
Build pinned source and core-info with this project's SDK, validate the ELF,
and stage both metadata paths for existing saved configs. Use a deterministic
build ID and require exact core readback; refresh stale metadata caches.
Verified: tools/verify.sh (five host gates, 45 tests, 18 evidence records).
Artifact: evidence/fceumm-build/{abi,capture,expectation}.json.
Target: tools/run-title.sh --no-build --watch 120, manual core/game selection.
Metadata discovery and exact core bytes pass; dynamic core open fails, then
content startup crashes in frontend vulkan_alive(NULL). This commit verifies
build tooling only, not playable FCEUmm. Park native loader/recovery acceptance
in parked/native-core-loading/README.md. PS5_Vulkan is unchanged.
Register ps5_joypad with raw buttons/axes and a built-in controller profile.
Route controls through RetroArch's binding helpers and stop OR-ing hardcoded
pad state back into remapped input. Preserve held samples across the extra
binding-screen poll and keep disconnect/interception handling.
Add three upstream edits (105 -> 108): joypad declaration, registration and
built-in profile. No new dependency or toolchain flag. Host tests exercise
upstream mapping/analog helpers, native polling/lifecycle and ELF registration.
Validation: tools/verify.sh (all five gates, 37 tests, 17 evidence records).
Fresh patch replay: 107 applied + one existing marker; second pass unchanged.
Console: tools/run-title.sh --no-build --watch 120; owner confirmed flawless
stick navigation and binding capture, then confirmed manual close.
Follow-up: tools/run-title.sh --no-build --no-deploy --watch 30; full window
alive and script-closed, zero Vulkan refusals/API failures.
Evidence: evidence/native-joypad/{capture,expectation}.json.
Build: e6211dea9fd0bb512e83a9979a6a44c0c8c5fdf175564c753e3fd2a25067e0a9.
PS5_Vulkan unchanged.
Create and repair all seven managed directories to the owner-authorized 0777,
including existing folders and restrictive umasks. Console 0775 modes still
denied every FTP upload; 0777 passed upload, byte-for-byte readback and cleanup
in each folder. Preserve user file modes and configuration contents.
Validation: tools/verify.sh (all five gates, 35 tests, 16 evidence records).
Console: tools/run-title.sh --no-build --watch 30; python3 tools/check-ftp-write.py.
Evidence: evidence/ftp-directory-permissions/{capture,expectation}.json.
Build: 347655429cf4a60f3d7608c3ba4e46ec1c1972f81704cbea9f9e654c9942cfed.
Thirty seconds alive, script-closed; no GPU refusals/API errors. PS5_Vulkan unchanged.
Register a native platform frontend to preserve startup argv and supply browser roots and directory defaults. Keep live settings separate from the packaged seed. Supply the known executable path to avoid the procfs-related config-save crash, and enumerate directories through SDK getdents with a 64 KiB buffer.
Validation: tools/verify.sh passes all five gates (35 unit tests, 15 evidence records). tools/run-title.sh --no-build --watch 90 lists 96 /app0 entries with zero errors, stays alive, and is script-closed with zero Vulkan refusals/API failures. Owner confirms visible folders and configuration files. Live config is unchanged across upload/restart.
Evidence: evidence/native-paths/{capture,expectation}.json includes failed diagnostics and final acceptance. Build identity 26139a294d687ccf21c8fa779e5ad3883273b873b71ce67e1df48f48589f8f76. Patch count 102 to 105; fresh replay/idempotence verified. Runner snapshots build identity to avoid false stale-log failures during subsequent builds. PS5_Vulkan unchanged.
Register audio_ps5 and select it by default. Feed native AudioOut through a bounded worker queue with byte-based writes, rate negotiation, backpressure, pause/resume and cleanup. No SDL or Opus dependency.
Validation: tools/verify.sh passes all five gates (32 unit tests, 14 evidence records). tools/run-title.sh --no-build --audio-test --watch 45 records 193536 accepted/played frames, zero errors/discards, correct channel tones confirmed by the owner. A normal --no-deploy --watch 45 run remains alive and is script-closed with zero Vulkan refusals/API failures.
Evidence: evidence/native-audio/{capture,audio-test,expectation}.json. Build identity 801df0ac6a5754c3e6a6ea688f1e49e8ddd2041aeb131a637b8c8b59f4030d55. Patch count increases from 100 to 102 for registration and default; fresh replay/idempotence verified. PS5_Vulkan unchanged.
Ship the pinned monochrome icons/font, refresh configure flags before builds,
and seed the title asset directory. Correct strip expansion/counts and padded
texture coordinates, populate ribbon descriptors, and retain startup asset logs.
Use single-level static images until mip-chain sampling is separately verified.
RGUI and video_ps5 remain available; PS5_Vulkan is unchanged.
The named edit count grows from 86 to 100 for these XMB changes. All five
tools/verify.sh gates pass (31 unit tests); fresh patch replay/idempotence passes.
The owner confirms flawless rendering, backed by GPU framebuffer readback.
The final normal title runs for 45 seconds with zero refusals/API failures,
loaded assets, successful presentations, and no screenshot instrumentation.
Evidence: evidence/xmb-default/{capture,expectation}.json
Reproduce: tools/verify.sh; tools/run-title.sh --no-build --watch 45;
python3 tools/evidence.py compare evidence/
Retain frontend INFO/WARN/ERROR, driver errors, startup trace and klog.
Restore frontend log setup after rebuilt arguments, mark its build identity,
and retrieve it with each run. Remove periodic frame and button chatter;
log menu texture handover once. Profiling is opt-in, bounded and written only
after capture; validate per-frame partitions and presentation-return cadence.
Verified: bash tools/verify.sh (all gates, 28 tests); fresh vendor patch
replay/idempotence (86 edits). Two 60-second captures completed: first at
59.940238 FPS with maximum interval 16.905813 ms; fresh frontend logging
exposed three late texture-probe stalls, then those probes were limited.
Final refined build launched with zero refusals, logged handover once and
returned 0 before profiling completed; no final timing claim is made.
User requested ending performance investigation and proceeding to XMB.
Evidence: evidence/vulkan-buffered-logging/{capture,expectation}.json
Replay: python3 tools/evidence.py compare evidence/
Console: bash tools/run-title.sh --no-build --gpu-profile 60 --watch 80
Analysis: python3 tools/analyze-gpu-profile.py klog/gpu-profile-<stamp>.tsv
Separate frame preparation, command finalization, submission, presentation,
fence/acquire waits, menu texture updates and remaining frontend time with
monotonic five-second summaries. Preserve Vulkan results and limit five
unbounded texture logs to four calls. Add nine named patch edits (68 -> 77).
Verified: bash tools/verify.sh (all five gates, 26 tests); fresh vendor patch
application and second-pass idempotence; two idle-console runs using
bash tools/run-title.sh --no-build --watch 45. Both completed with zero
refusals or recorded API errors. The final build holds 53.76-57.94 FPS
across eight windows versus the preliminary run's collapse below 2 FPS.
Submission averages 3.525 ms and presentation 8.075 ms; occasional frame
spikes remain. Owner reports noticeable improvement.
Evidence: evidence/vulkan-quiet-timing/{capture,expectation}.json
Replay: python3 tools/evidence.py compare evidence/
PS5_Vulkan is unchanged at 6f0ce0d; the CPU fallback remains selectable.
PS5_Vulkan 6f0ce0d fixes fragment input aliasing. Full 30-second target run has zero refusals and no observed Vulkan result failures; owner confirms correct colours without flicker or triangles. Pixel capture: 909/909 white samples, identical consecutive frames.
Evidence: evidence/vulkan-fragment-inputs/. Verification: bash tools/verify.sh (all five gates, 25 tests); bash tools/run-title.sh --no-build --watch 30. Retire capture and failed blend diagnostics under parked/. Capture-free build verified locally; final upload skipped because console is busy. CPU fallback preserved.
Two regressions failed when generic startup strings accepted an older executable. Require the unique embedded identity on FTP readback and log it at startup; keep the generated header ignored.
Verify: bash tools/verify.sh (all five gates, 25 tests with pending colour work); bash tools/run-title.sh --no-build --watch 30. Evidence: evidence/vulkan-build-identity/{capture,expectation}.json records matching staged and running identities. Pixel correctness is not claimed.
Regression test failed on 0023 because its marker was absent from its own replacement; it passes after the marker correction. Regenerated configured shader source removes accumulated copies.
Verify: bash tools/verify.sh (all five gates, 22 tests). Evidence: evidence/frontend-patch-idempotence/{capture,expectation}.json records unchanged SHA256 values after full patch reapplication; replay with tools/verify.sh evidence. Host tooling only; no new GPU acceptance claim.
Verify: bash tools/verify.sh (all five gates); bash tools/run-title.sh --no-build --watch 90. Evidence: evidence/vulkan-splash-dismissal/{capture,expectation}.json, replayed by tools/verify.sh evidence.
The public splash call returns zero and the owner confirms the Vulkan menu is visible. Blue colours and flickering black triangles remain; the owner manually closed the run, so full-window acceptance is not claimed. CPU video and PS5_Vulkan stay unchanged.
docs/ACTIVE.md is rewritten for the current state - two paths run, the CPU one is
on screen and the GPU one is not yet - and carries the measurements taken at the
moment of the draw, so the next session starts from what the console said rather
than from what was intended.
docs/GPU_PATH_CRITERIA.md gains the outcome: A4 and B1 are met, B2-B4 are not, and
the A2 mechanism recorded in it was wrong for two rounds - the refusal came from
vkCreateComputePipelines, not from an upload taking its compute branch, so both
"request RGB8888" and "match the menu's format" were answers about a path that was
not being taken.
docs/FINDINGS.md and docs/PHASE_LOG.md carry the seven faults with the trace lines
that identify each, including the one that is not a code fault at all (a stale file
on the console that ended every run by itself) and the one that is a measurement
fault (gfx_display_vk_draw instrumented as if it were the menu's draw).
The open question is stated as narrowly as it can be: the menu texture is filled,
the copy to the sampled image runs, the quad is drawn with a valid pipeline and
image in SHADER_READ_ONLY_OPTIMAL, nothing refuses, and the screen is black. The
next step is ../PS5_Vulkan's ps5vk_draw.c, not another gate on this side.
tools/verify.sh passes all five gates.
Command: bash tools/build-title.sh -> 276 of 276 sources and "read_viewport ->" in
build/llvm-pie.elf (build/r9-build.log); make test-unit -> OK (37 patches pinned);
bash tools/verify.sh -> PASS (format unit build integration evidence).
Patch 0033 includes command.h; patch 0031's capture calls
command_event(CMD_EVENT_QUIT, NULL) after writing its trace line. The objective's
evidence names /app0/retroarch.log, and that file has been the same 1200 bytes since the
first round for one reason: this title has always been killed from outside, and the
frontend only flushes its log when it exits itself.
NOT YET RUN: the console was unreachable for this whole round too (ping -c 2 -W 3 exits
1, checked after the FTP attempt failed the same way), so the capture that produces
/app0/shot.ppm and the flushed log is still the first action of the next round.
The console stopped answering this round (ping and FTP: no route to host) before the run
that produces /app0/shot.ppm could be made, so docs/ACTIVE.md and docs/PHASE_LOG.md now
carry the exact commands the next round starts with, and the patch is committed as
built-but-not-yet-run.
The port's capture now asks the driver instead of the frontend's screenshot writer:
patch 0031 reads --ps5-capture=N / --ps5-capture-path=FILE from /app0/args.txt in
vulkan_frame, calls the driver's vulkan_read_viewport at the budget, and writes the
pixels as a PPM (header plus rows, top-down RGB from the driver's bottom-up BGR).
0032 declares that readback entry point at file scope, where gfx/drivers/vulkan.c
already declares its other forward functions.
Evidence so far: the frontend compiles 276 of 276 sources and "read_viewport ->" is in
build/llvm-pie.elf (build/r8b-build.log). The first attempt at this patch did not
compile ("function declared in block scope cannot have 'static' storage class" and "use
of undeclared identifier 'vk'"); both are fixed and recorded in docs/PHASE_LOG.md.
NOT YET VERIFIED ON THE CONSOLE: the run that would have produced the picture could not
be made - the console stopped answering (ping and FTP: "No route to host") partway
through this round, and the deploy that preceded it failed for the same reason. The
first action of the next round is that run: arm /app0/args.txt, run
tools/run-title.sh --no-build --watch 18, then fetch /app0/shot.ppm and look at it.
Command: bash tools/run-title.sh --no-build --watch 18 with
/app0/args.txt = "--ps5-capture=90 --ps5-capture-path=/app0/shot.bmp", after publishing
config/retroarch.cfg with video_gpu_screenshot = "true"
Evidence: klog/run-shot-r7.log, klog/run-shot-r7b.log and their klog captures show
"capture: frame 90 of 90, take_screenshot -> failed" for both .png and .bmp, with no
shot file on the console, and - the decisive part - no new "vulkan:" message in
/app0/trace.txt around the capture line, which is where the driver's debug messenger
writes every refusal. So take_screenshot_viewport bails before read_viewport is called;
its silent paths are a zero viewport from video_driver_get_viewport_info and a failed
malloc. bash tools/verify.sh: PASS (format unit build integration evidence).
The config change (video_gpu_screenshot) was reverted: it was a hypothesis about which
screenshot path is taken, and the outcome did not change, so it does not ship.
Next step in docs/ACTIVE.md: one more trace line from the capture patch printing the
viewport and the dump result; if the viewport is zero, read the frame back in the port's
own code and write a PPM.
Command: bash tools/build-title.sh && (stage /app0/args.txt:
"--ps5-capture=90 --ps5-capture-path=/app0/shot.png") && bash tools/run-title.sh
--no-build --watch 20
Evidence: /app0/trace.txt (klog/run-PPSA99169-031642.log) contains
"capture: frame 90 of 90, take_screenshot -> failed (/app0/shot.png)" - the port's own
options are read by the patched vulkan_frame, the budget is reached, take_screenshot is
called, and the trace says whether it worked. bash tools/verify.sh: PASS (format unit
build integration evidence).
gfx/drivers/vulkan.c (patches/series 0031): the frame counter and the screenshot call,
in vulkan_frame, because that is the one path every frame takes. A second hook in
video_driver_cached_frame (0030) was written and removed: it never ran on the console,
because a content-less run does not take that path either.
src/main.cpp: --ps5-* lines are the port's own and no longer reach RetroArch's option
parser.
The readback behind take_screenshot (vulkan_readback's synchronous path) fails and its
reason goes to /app0/retroarch.log, which never flushes. That, and the picture itself,
are the next step.
Command: bash tools/verify.sh -> PASS (format unit build integration evidence).
Evidence for the finding: three console runs with
"--max-frames=120/200 --max-frames-ss --max-frames-ss-path=/app0/shot.png" staged in
/app0/args.txt (klog/run-shot-r5.log, klog/run-PPSA99169-030553.log) ran their whole
watch window, wrote no shot.png and left /app0/retroarch.log at its old 1200 bytes. The
runloop's max-frames block compares frame_count, which is refreshed from
video_st->frame_count, and only video_driver_frame increments that - the menu is drawn
through video_driver_cached_frame, so the block is not reached without content.
Patch 0029 (a runloop-side counter of the runloop's own iterations, tried on the console
for exactly this) was removed instead of committed: it had no verified effect, and an
unverified patch does not belong in the set. The patch count stays 34 and the configured
tree is back to the committed state. Next step recorded in docs/ACTIVE.md and
docs/PHASE_LOG.md: put the counter and the take_screenshot call in
video_driver_cached_frame (gfx/video_driver.c), where every menu frame passes.
Command: python3 (stage /app0/args.txt over FTP) then
bash tools/run-title.sh --no-build --watch 20
Evidence: klog/trace-r4.txt contains "argv extras from /app0/args.txt = 3" - the title
read the file and passed three extra arguments to rarch_main; klog/run-shot-r4b.log and
klog/run-PPSA99169-025444.log show the run with HAVE_SCREENSHOTS compiled in ("it ran for
20s and this script closed it"); bash tools/verify.sh format -> PASS (the full gate set
passed in the previous round and was re-run for this step's format fix).
src/main.cpp: /app0/args.txt, one argument per line, is appended to argv when present.
The console's launcher passes no arguments, and RetroArch's unattended capture options
are the ones a run needs to change.
tools/build-retroarch.sh: -DHAVE_SCREENSHOTS, because RetroArch's
--max-frames/--max-frames-ss/--max-frames-ss-path block is #ifdef HAVE_SCREENSHOTS and
was compiled out.
The capture does not fire yet: RUNLOOP_TIME_TO_EXIT compares max_frames against the
core's frame_count, and this title runs with no content, so the counter never advances.
That is the next step, recorded in docs/PHASE_LOG.md.
Command: bash tools/run-title.sh --no-build --watch 25
Evidence: klog/run-PPSA99169-022103.log ("VERDICT: it ran for 25s and this script
closed it"); klog/trace-r3o.txt from /app0/trace.txt: 644 x "probe REC: frame entered",
643 x "probe QUAD: menu quad", 40 refusals all at init. Probe-free rebuild (276 of 276
sources) run: klog/run-shipping-r3.log, klog/run-PPSA99169-023553.log ("it ran for 20s
and this script closed it", no fatal signal). bash tools/verify.sh: PASS (format unit
build integration evidence).
patches/series 0024: the display samplers asked for an opaque-white border, which
../PS5_Vulkan refuses, so all four stayed VK_NULL_HANDLE and the menu quad wrote a NULL
sampler - the refused write that ended every frame.
0025: a refused sampler falls back to the nearest one instead of NULL.
0026: the display layout's binding 2 (the HDR shaders' source sampler) is written by the
quad descriptor writer too: the driver requires a write for every binding a stage's
metadata names.
0027/0028: the menu texture takes the swizzle-free 32-bit path in R8G8B8A8_UNORM with
the CPU conversion's channels in that order - the driver samples only identity component
mappings.
Two rounds chased `assertion failed: cmd_buffer->render_pass == NULL` in
vkCmdBeginRenderPass as a driver or frontend bug. It was this port's own probe: the
probe tool writes `insert + anchor`, the REC probe's insert ended with the anchor line,
and the configured tree ended up with the call twice - so the second call found the
render pass the first had just begun. A duplicate detector (lines that appear more
often in the configured tree than in vendor/retroarch) found five such pairs:
vkCmdBeginRenderPass, vkCmdEndRenderPass, vkEndCommandBuffer,
vulkan_filter_chain_end_frame and the `if (quit)` pair from the ALIVE probe. The file
was restored from vendor/retroarch and the port patches re-applied.
tests/test_frontend.py's ProbeSet checked that an insert does not *end* with its
anchor; it now checks that the insert does not *contain* it, which is the property that
matters, and it caught the same mistake once more in the SIGNAL probe. The probe set
itself is otherwise unchanged apart from the marks this round needed.
With the duplication gone the real failure is visible, and it is the driver's: the
frame records the whole backbuffer pass and the chain's draw, and then vkQueueSubmit
asserts with `cmd_buffer->state` neither INITIAL, EXECUTABLE nor PENDING - the state a
recording refusal leaves. The draw is refused. ps5vk_draw.c refuses in three places and
two are silent in this build: `pipeline->draw_refusal` (a string computed when the
pipeline is created) and ps5vk_pipeline_prepare_shaders, the AGC
sceAgcCreateShader/sceAgcLinkShaders step that driver runs at the first draw rather
than at pipeline creation. vk_log.c drops every message unless MESA_DEBUG was set at
build time or a debug callback is installed, which is why neither can be read here.
Verified: bash tools/verify.sh PASS (format unit build integration evidence) after
deleting build/ra/obj and rebuilding, with `strings build/llvm-pie.elf | grep -c
'probe DRAW|probe REC|probe SPAN|probe IMG|probe VDRV'` = 0; 20 unit tests pass.
Next: patch the frontend's instance creation to install a VK_EXT_debug_utils messenger,
so that driver's refusals print into the trace, or take the AGC result code from
../PS5_Vulkan.
The console run now reaches a frame. In order: video_vulkan selected, the console's
khr_display WSI, a device, a swapchain at 3840x2160, four textures, a vertex buffer,
the stock shader reflected, both stages compiled by the driver's own compiler,
vkCreateGraphicsPipelines returning 0, vulkan_init handing back a live pointer, the
input driver opening the pad, and runloop_iterate reaching vulkan_frame -> vkQueueSubmit.
Nothing is on screen yet: the first frame's command buffer already has a render pass
open when the backbuffer pass begins, and Mesa's common CmdBeginRenderPass2 asserts.
Three of the four fixes are port patches, all of them requests that driver refuses:
0016 its pipeline check accepts only VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, and its
draw path refuses a non-indexed draw whose first vertex is not zero; the two
quads are now six-vertex lists, the second triangle reached through the
binding's own offset. This is what turned VK_ERROR_UNKNOWN into a pipeline.
0017 it refuses any sampler that is not clamp-to-edge and leaves the output handle
untouched, while CommonResources destroys every handle that is not
VK_NULL_HANDLE - sixteen refused samplers were destroyed as if real and the
driver asserted. The array is cleared before it is filled.
0018 the khr_display context reported quit from the frontend's signal-handler
state, so the runloop ended on its first iteration and rarch_main returned 0
with no frame drawn. A console title has no terminal and no SIGTERM sender.
The fourth is this port's own CRT: _start went from _init_env straight to the static
constructors and never cleared the BSS, whose tail is 0xc7040 bytes of a segment that
is 0x104b4 bytes in the file. tooling/native/ps5-pie.ld marks __bss_start/__bss_end
and _start clears that range; every run prints the check that says so. Whether the
loader also zeroed it was never measured - the invariant is what this asserts.
Two instruments, both because nothing else could be read:
src/main.cpp sends stderr to /app0/trace.txt and makes it unbuffered. The driver's
__assert prints the expression, file and line to stderr and then aborts, and the
console's libc buffers that stream, so every assertion arrived as a bare `abort is
called(system)`. The trace now carries the assert text, which is what identified the
render-pass state as the current blocker.
tools/apply-runtime-probes.py carries thirty-nine probes, and tests/test_frontend.py
checks the set itself: an insert that ends with its own anchor duplicates the line it
is inserted before, which broke the build three times in this round. Every probe must
carry a marker in its own text, a note and a trailing newline.
Evidence: bash tools/run-title.sh with the probe set applied (captures under klog/,
probes reverted before this commit); the trace's own lines quoted above;
bash tools/verify.sh PASS with a probe-free image (`strings build/llvm-pie.elf |
grep -c 'probe PIPE2|probe VDRV|...' ` = 0 after deleting build/ra/obj, which is what
a revert needs); 20 unit tests including the probe-set checks.
Next: mark the span between the offscreen-passes call and the backbuffer pass - the
first-frame history/feedback clear and the menu texture upload - and fix whichever of
the two leaves a render pass open.
With the driver linked and its five contract refusals fixed, vulkan_init still
returned NULL at the stock shader's pipeline and nothing said why. The reason was
in this repository: src/video_filters_stub.cpp, written when the port had video
filters disabled, defines the whole Vulkan filter chain as NULL-returning stubs.
src/ is linked before the archives, so the stubs won and the driver read "no
chain" - vulkan_filter_chain_create_default was never the real one, no pipeline
was ever built, and neither the log nor the trace could say so. Its own header
said this file is what has to go when the port grows shader support.
Removing it links glslang and SPIRV-Cross into the title, which are written
against FreeBSD's xlocale interface: the console SDK exports the unsuffixed
functions and none of the thirty-four `_l` ones the link then named.
src/locale_shims.c supplies them - each the C-locale answer its counterpart
gives, which is the only locale a title has - and implements the two the SDK has
no base function for (mbsnrtowcs, wcsnrtombs). nl_langinfo(RADIXCHAR) answers
".": SPIRV-Cross reads it to print shader constants.
src/main.cpp now points stderr at /app0/trace.txt. A title's stderr reaches
nothing on this console, which is why no library message - including an
assertion's - could be read; the marks and the library output are one record now.
It also installs a terminate handler naming an uncaught exception's type, which
is how an abort with no message was ruled not to be one.
What the console shows today, in order: video_vulkan selected, khr_display
resolved, device and swapchain created, four textures, a vertex buffer bound,
the stock SPIR-V reflected, both shader modules compiled, and then
vkCreateGraphicsPipelines returning VK_ERROR_UNKNOWN. Both stock shaders compile
through ../PS5_Vulkan's host compiler with the driver's own options, so the
refusal is in the console-only step after that - sceAgcCreateShader and
sceAgcLinkShaders inside ps5vk_graphics_pipeline_create, whose failure message
that project compiles out of its own build (#if !MESA_DEBUG in its vk_log.c).
Evidence: bash tools/run-title.sh with the probe set applied (captures under
klog/, twenty-nine probes, all reverted before this commit); python3
tools/symbolize-crash.py on each capture; the host compiler run on the extracted
stock SPIR-V (both stages, driver options); bash tools/verify.sh PASS with the
shipping image free of probes (`strings build/llvm-pie.elf | grep -c probe` = 0);
18 unit tests.
Next: that driver's sceAgcCreateShader/sceAgcLinkShaders result code, which only
its side can read.
bash tools/run-title.sh --watch 20 on be552f1: the title launches and every one of
its eight attempts ends in `rarch_main returned = 1` with nothing between argv and
the return - no ps5_init, no frame, no signal.
The frontend's own log, fetched from /data/homebrew/PPSA99169/retroarch.log, stops
after the audio fallback, and the setjmp handler's `Fatal error received in:` line
is absent, so no retroarch_fail fired: the return is one of rarch_main's silent
paths, the first being task_push_load_content_from_cli (retroarch.c:6036).
ps5_init's absence is not a regression: the compiled video default is vulkan since
ff6c312 and the config naming ps5 is the one content loading discards (0006).
Next: probes inside drivers_init after the audio block and around the content-load
push, through tools/apply-runtime-probes.py.
../PS5_Vulkan's driver stops being a library RetroArch loads at run time and
becomes ordinary symbols in eboot.bin, because a PS5 title cannot dlopen one
(9d29a1f measured ENOEXEC, ENOENT, NULL and ESRCH). This is the commit where
that link completes.
The first link failed on 24 undefined symbols, which is three problems rather
than one. The count is only knowable because the link line now carries
--error-limit=0; before that lld stopped at twenty.
- Four __eh_frame_* boundaries. The driver's shader compiler is C++ and links
the SDK's libunwind, which finds unwind tables through these boundary symbols
rather than dl_iterate_phdr. tooling/native/ps5-pie.ld provides the same four
../PS5_Vulkan/tooling/psbc/ps5-pie-unwind.ld defines, in this project's own
copy of the layout.
- Eight Mesa utility symbols (u_thread_create, u_thread_setname,
util_barrier_init/destroy/wait, util_thread_get_time_nano,
os_create_anonymous_file, os_read_file). That project's PS5 object list
filters src/util/{u_thread,anon_file,os_file}.c out of the compiler archive,
and its own libvulkan.so.1 links only because a shared object may leave
symbols undefined. tools/build-mesa-util.sh compiles Mesa's own definitions
with that project's PS5 configuration, because u_thread.c's util_barrier
layout has to match the u_queue.ps5.o already inside the archive.
- Twelve sceAgc* entry points. The console provides libSceAgc and
libSceAgcDriver and the SDK stubs neither, so they are declared for the
converter to record as imports. They live in tooling/ps5-stubs/ rather than
vendor/, which is not in the diff.
Evidence:
bash tools/build-title.sh exit 0, no undefined symbols; the artifact grew
from 8,225,706 to 29,859,722 bytes, which is the
driver and its shader compiler
bash tools/verify.sh PASS (format unit build integration evidence)
unittest discover -s tests 18 tests, two new: every sceAgc* the three driver
archives reference is declared by the stubs (20
referenced, 21 declared, none missing), and the
linked build/llvm-pie.elf defines the four
__eh_frame_* symbols
Not proven: the driver has not been on the console since it was linked.
The controller-port guard had been added twice to tools/apply-port-patches.py, so
runloop.c was patched twice, every build from that tree died with rip: 0, and the
crash was chased as an unrelated bug for most of a round. A duplicate is invisible by
reading: the block looks right and the script reports "applied" for both occurrences.
tests/test_frontend.py gains a PortPatches class that parses EDITS and asserts no two
blocks share a (file, anchor) pair, that the count is the pinned ten, and that
input_driver.c carries its three distinct edits. A duplicate was planted to prove the
test fails on it (11 != 10, two failures) and removed to prove it passes.
On purging the patch set: not done, and the enumeration is why. Eight of the ten
blocks are load-bearing - video driver registration, the main rename, input driver
registration, the input-init fix, the compiled input default and the null-joypad
guard - and removing them would take the display and the pad with them. Only the
HAVE_XKBCOMMON declaration and the controller-port guard are dead weight. The set was
instead verified from a pristine extraction: ten blocks, no duplicate (file, anchor)
pairs, three legitimate edits to input_driver.c at different anchors.
gates: bash tools/verify.sh -> PASS, 16 host tests. reproduce: bash tools/build-title.sh
from a wiped build/ra-conf, then bash tools/run-title.sh --watch 12.
bash tools/run-title.sh --watch 20, with the console owner working the pad, gives a
title whose own trace reads
input: pad opened, user=515310723 handle=51119872
input: press, pad=0x00004000 retropad=0x00000001 CROSS -> RetroPad B
input: press, pad=0x00002000 retropad=0x00000100 CIRCLE -> RetroPad A
input: press, pad=0x00000040 retropad=0x00000020 DOWN
menu: framebuffer commit 2 is a new picture (2 of 2 changed so far)
ps5_frame 600: menu commits=76 changes=14 presented=yes
Both halves of the round's goal are answered. Input works with the mapping a
PlayStation player expects - CIRCLE confirms, CROSS cancels - and the picture is not
one frozen frame: 76 framebuffer commits, 14 of them a different picture, against 1
and 1 in every earlier run. The menu was never frozen; it had nothing to redraw for,
which is what input supplies.
The fault was one comparison. ps5_input_init had never run because
video_driver_init_input opens with `if (*input) return true;`, which upstream means
for a video driver that pre-initialised an input driver of its own - and
video_driver_init_internal assigns `tmp = current_driver` *before* calling
video_driver_find_driver, so after the pre-initialisation pass selects a driver, tmp
IS that selection. Measured: `probe INV: entered tmp=ba41e0 *input=ba41e0
configured="ps5"`. The early return fired, the wrap below was dead code,
current_data stayed NULL and every button read answered 0. patches/series 0009 fixes
it with `if (*input != NULL && *input == tmp)`. My first attempt at 0009 cleared the
selection when `tmp == NULL` and never fired, for exactly that reason.
The probes are kept in tools/apply-runtime-probes.py - applied with one command,
reverted with --revert, listed with --list, and never part of the shipping build
because tools/build-title.sh does not call it. A rebuild wipes any probe written
into build/ra-conf, which is how the set that found this fault was lost.
Also reverted an edit this session made to the frozen AGENTS.md; it is back to its
committed state byte for byte.
gates: bash tools/verify.sh -> PASS (format unit build integration evidence), 13 host
tests. reproduce: bash tools/run-title.sh --watch 20.
Markers bracket the crash to command_event() before its switch reaches
CMD_EVENT_CONTROLLER_INIT. The marker directly before that call prints; three
markers after it never do, including the first instruction of
command_event_init_controllers and the case label itself. The register dump is
byte-identical across every config-loading run (rip: 0, rdi/rsi/r13 inside one
0x138-byte frame), so the fault is deterministic and one frame deeper than the call
site.
patches/series 0007 guards the core's retro_set_controller_port_device callback,
which upstream calls unguarded and which only dynamic_dummy.c's empty stub makes
survivable. It is verified compiled in (test %rax,%rax; je before call *%rax) and
the crash is unchanged, so it is committed as a genuine missing check and not as a
fix - the comment says exactly that.
File logging is configured and inert, for a structural reason worth recording:
retroarch_main_init initialises the logger after it parses the config, so a crash
during init happens before any log file exists. log_to_file, log_to_file_timestamp
and log_dir are now in config/retroarch.cfg for the day the config is read; they
need no build flag, because rarch_log_file_init is compiled unconditionally.
The config-path fix (0006) stays parked. input_driver returns to "null", since the
ps5 driver is only reachable through the config file that cannot be read yet.
Two process faults are recorded in docs/FINDINGS.md because both cost real time: a
patch park done by slicing the script by text emptied parked/config-path.patch.py
and removed the 0006 entry while build/ra-conf still had it applied (both recovered
with git checkout), and probe lines inserted after command_event's getters split a
multi-line #if block and broke the link.
gates: bash tools/verify.sh -> PASS (format unit build integration evidence).
reproduce: bash tools/run-title.sh --watch 10 -> title runs, menu visible.
Four console runs, each changing exactly one thing, with a marker in the entry
point so the trace says which variant ran:
-f -c cfg --verbose --menu config read SIGSEGV, rip=0
-f -c cfg --menu config read SIGSEGV, rip=0
-c cfg --menu config read SIGSEGV, rip=0
-f --verbose --menu no config runs, menu on screen
So it is not --verbose taking effect for the first time, and not video_fullscreen
either: dropping each leaves the crash. It is the config file being read at all.
The config itself parses: marking config_load before and after prints both probes,
so defaults, parse and config_load_file all return. The crash is after the read and
before the first frame - no ps5_frame 0 line, and neither runloop_iterate call site
in rarch_main is reached. It is inside retroarch_main_init, in the stretch between
the config load and the first frame.
drivers_init and video_driver_init_internal both complete (overlay, context reset,
display server, mouse cursor, audio init, core info all mark), and ps5_input_init is
never entered, so the input driver is not involved. What remains in that stretch is
the driver lookups - audio, video, input, camera, menu - each indexing a driver
table this build has stripped to almost nothing, and that is where the next marker
goes.
The title is back in its working state: entry argv complete, the config-path patch
parked in parked/config-path.patch.py, input_driver left at "null" so the menu shows.
gates: bash tools/verify.sh -> PASS. reproduce: bash tools/run-title.sh --watch 10.
src/input_ps5.cpp is a complete input_driver_t for this console: the DualSense is
read with scePadInit/scePadOpen/scePadRead and the 120-byte sample layout
../ProsperoLight verified on hardware, the pad's button words are mapped onto
RetroArch's numbering (CIRCLE is RetroArch's A, so CIRCLE confirms a menu entry),
sticks and triggers are reported as axes, and the table is registered in
input_drivers[] before input_null. tests/test_frontend.py pins the registration
from the frontend object's relocations and the button pairing by reading the map
out of the object the title links.
Two faults were found in front of it, both upstream of the driver.
RetroArch's built-in test input driver is on by default (HAVE_TEST_DRIVERS=yes in
qb/config.params.sh), and while it is on video_driver_init_input returns before
initialising any real input driver whenever the configured driver is not "test".
No input driver was ever initialised, so current_data stayed NULL and every button
read answered 0. --disable-test_drivers is now in tools/retroarch-sources.sh.
The title's -c never reached RetroArch. Content loading rebuilds argv from the
frontend's environment and kept only ["retroarch", "--menu"], so the config file
was never read at all - measured as
probe config_parse_file: path="(null)"
probe config_load: after parse input="null" video="ext"
This title has therefore been running on compiled defaults since it first built,
with --verbose dropped as well. A guarded one-line fix restores the title's own
config path.
That fix is parked rather than committed into patches/series: with the config
actually read, the title dies on launch with SIGSEGV, rip=0 - a call through a
null function pointer - both with input_driver="ps5" and with "null", and before
ps5_input_init is entered. Markers through drivers_init and
video_driver_init_internal show the entire video path completing, so the fault is
after driver initialisation. The change and its reasoning are in
parked/config-path.patch.py, and config/retroarch.cfg keeps input_driver="null" so
the title keeps running and showing its menu.
The pad does not work yet and the picture is still one menu frame. What is solid:
the driver exists, is registered, is unit-tested, and the two faults that were
silently blocking every input path are named with their measurements.
gates: bash tools/verify.sh -> PASS (format unit build integration evidence),
13 host tests. reproduce: bash tools/run-title.sh --watch 12.
bash tools/run-title.sh --watch 20 runs the title unattended with no fatal signal,
and the console's owner watched RetroArch's RGUI menu on the television during it.
The title's own trace from that run is committed as
evidence/ppsa-99169-rgui-menu-on-screen/:
ps5_init: own table ident=ps5 poke_interface=4015d0 set_viewport=401590 wrap_type_to_enum=0
ps5_get_poke_interface entered
ps5_set_texture_frame: rgb32=0 320x240 frame=present have=1 (was 0)
ps5_frame 1: menu 320x240 pitch=640 present=1
display: flip 1200 of buffer 1, status=0 marker=1
Two independent faults were behind the black screen.
The first was one line of the build. src/ was compiled with no -DHAVE_* flags at
all while the frontend archive is compiled with fifty, so the two sides disagreed
about RetroArch's video_driver_t: video_ps5 was 136 bytes where the frontend read
144, and every member after overlay_interface was read one slot late. poke_interface
came back NULL, the frontend calls it only when it is not NULL, and RGUI's hand-over
was therefore dropped in silence on every frame while the driver presented 1500
frames of its own probe. tools/build-title.sh now passes tools/retroarch-flags.sh's
own list to the title's sources and writes the configured tree's config.h where
RetroArch's headers look for it. tests/test_frontend.py compares
sizeof(video_driver_t), compiled with the frontend's defines, against the size of
video_ps5 in the object the title links, and checks the member at the frontend's
poke_interface offset against the table's relocations; built without the defines it
fails with "video_ps5 is 136 bytes in the title's object and video_driver_t is 144
bytes in the frontend", which was verified by doing it.
The second was sceAgcInit(8), called before the display opened, left over from the
round that submitted flips through an AGC command buffer.
../PS5_Vulkan/src/demo_renderer.cpp, whose output has been seen on this console,
contains no sceAgc call. Removing it produced the first pixels this port ever put on
the screen.
The lead this round started from is measured false. Holding the frame inside
present() instead of returning into RetroArch's runloop was expected to be the fix;
with the probe's frame held for 8 seconds the bands appeared immediately, before the
hold began, and were still on screen twelve seconds after present() had returned into
the runloop, with the display's flip status at marker=1 for all sixteen samples. The
runloop does not take a presented frame back. The two candidate causes went in
together, so what the run separates is the runloop; the AGC call is what remains with
no other candidate.
present() is a real driver now: flush the back buffer, flip to that buffer's
registered index, wait a vblank, alternate buffers. The probe paint, the readback and
the hold are gone. The bands remain underneath as the instrument that tells a black
screen from a menu that is not drawing.
gates: bash tools/verify.sh -> PASS (format unit build integration evidence).
reproduce: bash tools/run-title.sh --watch 20, then read /app0/trace.txt.
tools/run-title.sh owns the whole sequence: build, publish, verify what the console
stored, listen to the kernel log, launch, watch, close the title itself, and print
the title's own trace. The hand-driven loop had produced two bad readings - runs
overlapped so one launch's trace was read as another's, and a probe build left in
place was mistaken for a finding - and neither can happen now.
Verification changed to match this console. It converts a signed fake self into a
raw ELF as it stores it, so the served file is a different container from the one
sent (8,117,072 bytes against 8,029,263; 12.8% of bytes equal). A digest check was
answering the wrong question and rejecting successful uploads, so eboot.bin is now
verified by the strings only this build carries. sce_module/libc.prx is the one
path this console will not replace, so it is reported and kept rather than fatal:
the title runs against the console's copy, and failing on it blocked eboot.bin.
The crash is fixed. Every joypad driver upstream ships needs a library this SDK
does not carry, so primary_joypad is NULL, and input_joypad_analog_axis reads
drv->axis without checking drv - inside an if (menu_is_alive) block, which is why
pass one survived and pass two died with SIGSEGV at fault address 0x18.
patches/series 0004 returns 0 when there is no driver. The title now runs
indefinitely: 1500+ frames in 25 s.
RGUI's bitmap fonts are downloaded assets, not built-ins: without
<assets>/rgui/font/bitmap10x10_eng.bin rgui_fonts_init fails and rgui_init jumps to
its error label. They are now bundled under assets/rgui/font/ and shipped.
The menu still does not draw, and the reason is measured: RGUI is alive with a
320x240 framebuffer but GFX_DISP_FLAG_FB_DIRTY is never set, so rgui_set_texture
returns every frame. That flag is set at the end of rgui_render, reached only
through menu->driver_ctx->render under MENU_STATE_BLIT. docs/FINDINGS.md records
the path there.
Evidence: bash tools/run-title.sh -> 1500+ frames, no-menu-source, closed by the
script; /app0/trace.txt; tools/verify.sh PASS (format unit build integration
evidence); port changes vs upstream retroarch.c 10 lines, gfx/video_driver.{c,h} 5,
input/input_driver.c 6, runloop.c 0, menu/drivers/rgui.c 0, every probe removed.
The frontend now builds into a signed title, and the PS5 driver is in
RetroArch's table in the image that ships, not only in the source.
bash tools/build-title.sh
-> 225 of 225 sources compiled, archived into build/ra/libretroarch.a
-> dist/PPSA99169/eboot.bin, 8,026,239 bytes
container: signed, plaintext; 12 segments; integrity: valid
digest 71c88975040535a02b462618dd394a7a378034272c9bffa43ce0ca4b717ab9b7
(identical across two consecutive builds)
readelf -r build/ra/obj/gfx_video_driver.c.o
-> .rela.data.video_drivers holds exactly two entries: video_ps5, video_null
nm build/llvm-pie.elf
-> video_ps5, video_null, rarch_main and a single main
bash tools/check-manifest.sh
-> 7 files match their digests, none extra, eboot.bin magic 4f153d1d, and it
fails as it should when one byte of the image is changed
tools/verify.sh
-> PASS (format unit build integration evidence)
Registration and entry point. The port's changes are applied by
tools/apply-port-patches.py to the configured copy under build/ra-conf and never
to vendor/retroarch; patches/series names both. RetroArch's own main is compiled
out with -DHAVE_MAIN, the flag its desktop build passes, so src/main.cpp supplies
the only one and calls rarch_main.
Five faults fixed on the way, each recorded in docs/PHASE_LOG.md: the build read
sources from vendor/ and headers from build/ra-conf, so the driver table in the
image had no ps5 entry; 36 hand-written -DHAVE_* flags included six features
configure had turned off; config.h alone is not enough because a source can test a
feature before it is reached (libchdr without zlib); five Linux-only subsystems
were in the object list and are now off at their own configure switches; and two
link-time stubs the pipeline refuses needed -DNDEBUG and -DZSTD_TRACE=0.
The abandoned route is removed: deploy.py, console-launch.sh, check-payload.sh,
scaffold-native.sh, build-native-app.sh, install-title.sh and deploy.sh. Every
tool named by another tool now exists - tools/fetch-retroarch.sh was named by
three tools and three documents and had never been committed, and it now pins
RetroArch at v1.22.2 (69a4f0e) and drops the history so upstream cannot be
committed into. The gate scripts that had never been written are written:
lint-shell, lint-format, check-manifest. The template's unit test, which built a
test for a file that does not exist here, is replaced by tests/test_frontend.py.
The title has not run yet. It is built, signed and manifested; the folder has to
reach /data/homebrew/PPSA99169, and writes from this machine do not take on that
console.
Two things in one step.
Removed everything websrv-derived, since the intention was to start from
ProsperoLight's foundation: the Option 1 baseline build tree under work/baseline/,
its artefacts in dist/baseline/ and dist/PPSA99005/, the pacbrew ports cache, and
the tools that existed only for the conversion route (build-baseline.sh,
prospero-clang-link, stage-ppsa.sh, fetch-ports.sh, linker/, platform/).
The frontend source list now comes from RetroArch's own build.
tools/retroarch-sources.sh runs RetroArch's ./configure and "make info" and prints
the objects a link needs - its OBJ list grows from 248 conditional OBJ += lines in
Makefile.common, so only make can produce it for a configuration. configure runs in
a copy under build/ra-conf/, so vendor/retroarch is never touched.
Evidence: tools/build-retroarch.sh compiled 225 of 244 sources with the pipeline's
compiler. The remainder is Linux-only and out of scope for this console - udev,
xkb, linuxraw input, and libchdr_zstd. Four fixes got there, each found by a
compile: the generated config.h placed where RetroArch's relative includes resolve;
-D_GNU_SOURCE (the _POSIX_C_SOURCE detour hid strlcpy and broke sixty more sources
than it fixed); -DCLOCK_REALTIME=0 -DCLOCK_MONOTONIC=4 from the SDK header's own
values; and RetroArch's vendored zlib with its compatibility headers on the include
path.
src/video_ps5.cpp implements both interfaces RetroArch asks of a video driver: the
video_driver_t and the video_poke_interface_t RGUI needs, presenting through
src/display.cpp, which has already run on the console.
RGUI is the right first target by measurement, not preference: it references the
menu display context zero times, where XMB references it 72 times, and every
backend in gfx_display_ctx_drivers[] is a GPU API with no software entry. So RGUI
is the only menu that runs over VideoOut alone; XMB needs a GPU context over
../PS5_Vulkan later.
The mandatory surface is six members - init, suppress_screensaver, alive, frame,
ident, poke_interface - and the compile corrected one of them: suppress_screensaver
returns bool, not void. The table is positional, with overlay_interface inside
#ifdef HAVE_OVERLAY before poke_interface and wrap_type_to_enum after it.
ps5_frame prefers the menu framebuffer when RGUI has handed one over, else the
core's frame, and scales nearest-neighbour through the display layer's tiled
addressing.
Evidence: the driver compiles through the pipeline's compiler against RetroArch's
headers, 6,056 bytes, no warnings. docs/REFERENCE.md now records the port's
interfaces and the menu/backend table.
The scaffold title - the native pipeline's scaffolding, this repository's display
layer and its own entry point - was deployed by FTP, launched, and stayed up.
Evidence: the delimited capture klog/PPSA99169-135230.log holds zero fatal signals
and zero exit_value lines, shows the process twice in the shell's accounting
([SceShellCore] 23% 700 PPSA99169 eboot.bin), and the control payload reports
app=49176 title=PPSA99169 count=1 pids=228 while it runs.
The first version of src/display.cpp guessed five constants the console does not
forgive and cost a run: the title built, launched, and reported "eboot.bin calls
exit() exit_value=1" because open had failed. Comparing with the sibling
application that had proved them on hardware showed all five - the 64-bit pixel
format, the direct-memory accessor's type, the mapping protection, the four-
pointer VideoBuffer, and the tiled pixel addressing. All are now taken from that
project with the reason recorded, since none is derivable by reasoning.
Also in this commit: tools/scaffold-native.sh (builds the scaffold, merging the
pipeline's tools into this repository's rather than replacing them - an earlier
version deleted this project's scripts), tools/fetch-retroarch.sh (pinned clone
plus recorded revision), and tools/console-run.sh corrections - the mount wait
removed as it is a one-time step, and the verdict no longer reports "still
running" from a condition that is always true.
ProsperoLight, built here through the native pipeline, was placed on the console
and started flawlessly - the milestone the route change was made for.
Evidence: the owner observed it running; the capture for the attempt holds zero
fatal signals (klog/PPSA99002-134510.log); the converter's inspector reports
"container: signed, plaintext", twelve segments, integrity valid. Two readings
from this session are corrected rather than left standing: the "is not registered"
lines were the console's AutoMounter talking about other app ids, not this title,
and tools/console-run.sh printed "still running" from a condition that is always
true.
tools/build-native-app.sh now carries the four requirements as one command: the
project's own vendored SDK, PS5_CLANG=/usr/bin/clang, the jsonschema stand-in
under tooling/pystub/, and a verified runtime/libc.prx.
The RetroArch swap is scoped by measurement. RetroArch 1.22.2 is cloned fresh,
and the pipeline's graphics layer is VideoOut driven directly from direct memory -
enough for a CPU framebuffer, not enough for RetroArch's menu, which needs a GPU
display context. The backend exists in ../PS5_Vulkan (libps5vk.ps5.a with
ps5vk_CreateInstance defined) with headers in
../ps5-opengl-sdk-0.2.0/third_party/Vulkan-Headers, both reachable through
APP_INCLUDE_PATHS and APP_STATIC_ARCHIVES.
The route is now: prove the whole console path with a complete known-good title,
then replace its sources with a fresh RetroArch. This lands the first half.
Building it needed four things, each found by a failed build and none of them a
change to its sources:
- the project's own vendored SDK rather than the one in $HOME;
- PS5_CLANG=/usr/bin/clang, because its wrapper defaults to a clang-18 that is not
installed, while the sibling project that works uses plain clang;
- a minimal documented jsonschema stand-in at tooling/pystub/, because mbedTLS
regenerates a source file with a script that imports it and the package is not
installed. The stand-in validates the schema features that script uses and
raises otherwise, so a malformed data file still fails loudly;
- a verified runtime/libc.prx. The project ships only its manifest; the
boilerplate's copy carries the same digest, so it was used.
All four are captured in tools/build-native-app.sh. Evidence: make app completed
and the converter's inspector reports "container: signed, plaintext", twelve
segments, integrity valid for dist/PPSA99002/eboot.bin; the folder is staged at
handoff/PPSA99002/.
Not proven: that it runs. Placement is the console owner's step, since writes from
this machine do not reach the console's title folders, and a title must also be
registered with the shell before a launch is anything but "is not registered".