Skip to content

PS4 feasibility: ThreeNative experiments in the shadPS4 emulator (PRD-784) - #621

Queued
jonit-dev wants to merge 118 commits into
developfrom
feat/prd-784-ps4-feasibility
Queued

jonit-dev wants to merge 118 commits into
developfrom
feat/prd-784-ps4-feasibility

Conversation

@jonit-dev

@jonit-dev jonit-dev commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

What is this PR about?

It tests whether ThreeNative can run on a PS4. Each experiment runs first in the shadPS4 emulator. A run that passes on the emulator says PS4_EMULATED, never PS4_HARDWARE. PRD: docs/PRDs/done/PRD-784-ps4-feasibility-experiments.md.

Why does it matter?

The owner wants a go/no-go for PS4 and one proof of concept on a real console. Emulator runs come first, so the owner spends console time only on a build that already works.

Progress

PRD-784 boxes (all run in shadPS4 v0.21.0, PS4_EMULATED):

  • E1: an OpenOrbis ELF boots and prints its marker.
  • E2: vulkan-ps4 draws a triangle.
  • E3: a triangle draws through webgpu.h (Dawn), with compute and sampled textures.
  • E4: V8 runs a script (jitless).
  • E5: the engine and V8 render the cube for 300 frames.
  • E6: one command builds the artifact and prints each send step.
  • P4-1: a DualShock 4 drives the game through navigator.getGamepads() (virtual uinput DS4).
  • P4-2: E5 presents above 60 fps by default. The lane runs on gamescope 144 Hz inside its Xvfb when gamescope is installed (TN_PS4_HOST_HZ=0 = plain Xvfb, about 60 fps): Cube Spin E5 PASS fps=80.7, starter E5 PASS fps=73.9 with the DS4 stick held forward; E1-E4 and E3CS still PASS.
  • P4-3: Cube Spin renders as on desktop: textures, environment light and cast shadows (visual judge CLOSE to the desktop shot).
  • P4-4: the starter (humanoid) loads, walks with the DS4 and animates: E5 PASS fps=60.0.
  • P4-5: a React HUD draws on PS4 with no web view (React in V8, native-css backend), 60.1 fps.
  • AC-1, AC-2.

Label prd:100%: every countable box is ticked. The console run is not a box: it stays under ## Blocked on in the PRD (real PS4 model and firmware, a homebrew-ready console, then the PS4_HARDWARE run of the E6 bundle). The owner's goal (2026-11-07) files the PRD in docs/PRDs/done/; the console run stays open under its ## Blocked on.

Driver fixes that real hardware also needs (vulkan-ps4 patches)

  • 0045: mip levels and CPU-tiled uploads.
  • 0046 and 0047: varyings named by slot, SPI_PS_INPUT_CNTL programmed (floor and wall textures).
  • 0048: 256 epilogue slots (the starter went from 42-53 to 60 fps).
  • 0049 and 0050: cube and array texture descriptors, per-level colour targets.
  • 0051: integer vertex formats (the skin index), so skinned meshes animate instead of holding the bind pose.
  • 0052: layer views and layer clears, so the PMREM environment light is not black.
  • 0053: Vulkan 0..1 clip-space depth, so shadow maps compare the depth they stored.

Each patch has a red-green test in packages/runtime-native/tests/ps4-stage-guest-ui.test.mjs (9/9) and a before/after sheet in the PR comments, judged by a fresh subagent.

Known gaps (not boxes)

  • ⚠️ Thin floor lines alias into short dashes at 640x360 (the grid texture has no mip chain on any platform).
  • ⚠️ shadPS4 crashed in 2 of about 20 runs (exit 139/133, write to 0x0); each passed on a plain rerun.
  • ⚠️ Real PS4 GPU time is unmeasured. The lane's plain Xvfb holds 60 fps because each presented frame is copied into it (its clock is not the limit: -fakescreenfps 144 still reads 58-60). With a zero-copy host present (gamescope at 144 Hz) the guest passes E5 at 75-80 fps.

…its marker

run-experiment.mjs pins shadPS4 v.0.21.0 and OpenOrbis v0.5.4 by sha256, builds the guest into
a create-fself eboot.bin, boots it on a private Xvfb with a private HOME, and passes only on the
guest's stdout marker. The run also creates shadPS4's home/1000 first, so no Save Migration box
reaches the owner's desktop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jonit-dev jonit-dev added the prd:25% PRD phase 1 verified label Nov 6, 2026
jonit-dev and others added 2 commits November 6, 2026 10:59
…adPS4, draw still empty

The runner builds the pinned OpenGNM stack with ten patches. It turns on shadPS4 GPU readbacks
and judges the guest's own frame on the host, which fails closed. E2 is still red: the frame is
only the clear colour, and the next missing register is CB_COLOR_CONTROL. The PRD records
Cube Spin as the E5 target scene.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Patches 0011 and 0012 write CB_COLOR_CONTROL, SPI_SHADER_COL_FORMAT and PA_CL_CLIP_CNTL at
pipeline bind. E2 judges shadPS4's presented frame from Xvfb screenshots, because a guest GPU
copy of a render target reads stale memory in the emulator. Result: drawn=12.55% PASS. The
vertex-buffer guest stays red (0%) as the control, and as open work for E5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jonit-dev jonit-dev removed the prd:25% PRD phase 1 verified label Nov 6, 2026
@jonit-dev

Copy link
Copy Markdown
Contributor Author

PRD-784 E2 progress (PS4_EMULATED, shadPS4 v.0.21.0): vulkan-ps4 draws a triangle. Left: the gl_VertexIndex guest passes. Right: the vertex-buffer control still draws nothing, which is open work for E5.

e2-pass-vid-triangle.jpg
e2-pass-vid-triangle.jpg

e2-control-vertexbuffer-empty.jpg
e2-control-vertexbuffer-empty.jpg

Route A: Dawn d14ae3d9 on its Vulkan backend over vulkan-ps4. Driver patches
0013 (honour pName) and 0014 (VK_KHR_surface queries) close the gaps in the
driver, not in Dawn. e3 judges the presented frame like e2. Adapter limits:
maxTextureDimension2D 4096, so the device runs in Compatibility mode.

Also carries the E4 V8 recipe (still red: Isolate::Enter ThreadDataTable reads
zero) and the TN_PS4_GDB runner option used to chase it.

Co-Authored-By: Claude <noreply@anthropic.com>
@jonit-dev jonit-dev added the prd:50% PRD half the phases verified label Nov 6, 2026
@jonit-dev

Copy link
Copy Markdown
Contributor Author

E3 green (PS4_EMULATED): Dawn webgpu.h compiles a WGSL triangle at run time (Tint → SPIR-V → psbc → GCN) and presents 600 frames in shadPS4. Orange = the shader's vec4f(1,0.55,0), blue = clear 0.25. Adapter PS4 GPU (GNM), maxTextureDimension2D 4096 → Compatibility mode. Driver patches 0013–0014 add the missing VK_KHR_surface queries.

e3-webgpu-dawn-triangle-shadps4.png
e3-webgpu-dawn-triangle-shadps4.png

psbc compiled every vertex input to (0,0,0,0): its RADV key had no VS prolog.
0015 emits the fetch-shader prolog with its pointer in user SGPR 0 (Sony ABI).
0016 makes vulkan-ps4 build one V# per attribute from the VkFormat and one
instancing mode per input; an unmapped format logs instead of silently reading
R32_FLOAT. TN_PS4_E2_GUEST=e2 and vid both PASS; e3 PASS.

Co-Authored-By: Claude <noreply@anthropic.com>
@jonit-dev

Copy link
Copy Markdown
Contributor Author

Vertex buffers render (PS4_EMULATED): positions and colours now come from a VkBuffer through a GCN fetch shader (patches 0015 psbc prolog + 0016 vulkan-ps4 V# table). This was the cube blocker for E5.

e2-vertex-buffer-shadps4.png
e2-vertex-buffer-shadps4.png

Two OpenOrbis ABI traps, both true on hardware:
- musl alltypes.h declares pthread_*attr_t as 4 bytes; libkernel stores an
  8-byte pointer. pthread_mutexattr_destroy erased V8's saved this in the
  RecursiveMutex ctor (gdb register bisect). ps4-compat.h now declares the
  attr types at pointer size before musl's.
- create-fself raises PT_TLS p_align to 0x20 after lld fixed tp offsets for 8,
  so every initialised thread_local read 0 (AllowHeapAllocation CHECK).
  ps4-malloc.c pins a 32-aligned TLS member; e4 fails fast on a bad probe.

PS4_EMULATED E4 PASS mode=jitless, twice.

Co-Authored-By: Claude <noreply@anthropic.com>
@jonit-dev

Copy link
Copy Markdown
Contributor Author

E4 green (PS4_EMULATED): V8 13.1.201.22 (the desktop pin) runs a script in shadPS4 → PS4_EMULATED E4 PASS mode=jitless (twice). The guest source holds only 6*7, so the 42 is V8's own result. Two OpenOrbis ABI traps fixed, both true on real hardware: musl declares pthread_*attr_t as 4 bytes while libkernel writes 8, and create-fself re-aligns PT_TLS after lld fixed the thread-pointer offsets. Phases 1–2 are done. Next: E5 (engine cube → Cube Spin).

jonit-dev and others added 11 commits November 6, 2026 14:32
tn-ps4 preset + cmake/ps4.toolchain.cmake; ps4-deps.sh stages the e3 Dawn and
e4 V8 prebuilts. 9 engine targets (foundation, scene, world, renderer, abi,
bindings, shader, host_services, adapter_v8) compile with 0 errors against
OpenOrbis libc++ 11 via shims (ranges, source_location, charconv floats checked
byte-identical to host libc++ on 25 values, cmath, memory, functional). Two
TN_PS4 branches where a shim is impossible (pmr pool, heterogeneous find).
Desktop tn-linux configure unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
… eboot

SDL3 3.2.30 static with only dummy video/audio (no source patch, four configure
answers); PLATFORM_PS4 surface from the null Xlib carrier E3 proved; ps4_main.cpp
feeds /app0/game.js, 1920x1080 and jitless V8 flags. e5-build.sh links the
player archive, 20 engine archives, SDL3, Dawn, V8 and vulkan-ps4 with OO's
link.x (ps4-malloc.o first) and writes a 52 MB eboot.bin; 165 imports, all
resolved by create-fself. Desktop/android/ios paths unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
…n shadPS4

e5 experiment: --scene/TN_PS4_SCENE bundles and cooks a game into /app0, the
verdict is the presented frame (clear box, 2-75% drawn) plus a frame
difference. Boot fixes: SDL3's dummy video is opt-in (hint), the dummy driver
refuses SDL_WINDOW_VULKAN, and the adapter request is Compatibility (vulkan-ps4
reports 4096 limits). Adapter, device, surface, renderer and game.initialize all
come up; still red: shadPS4 asserts on psbc's 32-bit descriptor-set pointer.

Co-Authored-By: Claude <noreply@anthropic.com>
… addresses need

psbc passed each descriptor-set pointer as one 32-bit user SGPR with
address32_hi = 0. PS4 GPU addresses are 44-bit and vulkan-ps4's tables sit at
0x2005e0000, so hi=0 is wrong on hardware too, and shadPS4 asserts on it
(FlattenExtendedUserdataPass). 0017 reserves a lo/hi user-SGPR pair and reads
hi from it; the driver already writes both halves. e5 now loops 300 frames
(next red: vkCreateImage OUT_OF_DEVICE_MEMORY). Also fixes e3-build's shim
staging of shim/sys and integer std::abs in shim/cmath. E2 (vid, e2) and E3 PASS.

Co-Authored-By: Claude <noreply@anthropic.com>
…-ps4

The engine's 4x MSAA RGBA16F scene target failed sceGnmCreateRenderTarget and
every D32 depth target silently fell back to a plain texture. 0019: opengnm
read the sample count as log2 (16 fragments for 4x) and its register fields
used >>1 (8x -> 4); both now use the count and a real log2. 0020: the GPA
bank-sanity check is advisory, as in Sony's library. 0021: depth uses
DEPTH_2D_THIN_1K, MSAA colour THIN_2D_THIN. 0018 logs a failed create in full.
e5: 19 images, 0 create failures; next red: psbc supports descriptor set 0 only.
E2 (vid, e2) and E3 PASS.

Co-Authored-By: Claude <noreply@anthropic.com>
…-ps4

0022: psbc gives each used set (0..3) its own 64-bit user-SGPR pair (a 4-set
VS uses 12 of 16 SGPRs; RADV's indirect table was rejected because its entries
are 32-bit). 0023: vulkan-ps4 binds every set at firstSet with dynamic offsets
and writes both halves per stage. 0024: wave64 below GFX10. e5 reaches
TN_PLAYER_FIRST_PRESENT; next red: shadPS4 decodes past a psbc shader's end.
The E2/E3 guests now hold their last frame until the harness stops them, so the
7 s capture never races their exit. E2 (vid, e2) and E3 PASS.

Co-Authored-By: Claude <noreply@anthropic.com>
…56-byte boundary

The driver's prebuilt clear_ps blob predates the 256-byte code layout: its code
sat at +0x74 and SPI_SHADER_PGM_LO keeps bits 8..63, so the GPU (and shadPS4)
started executing the container header. 0025 relocates the blob so the code is
256-aligned. e5 compiles 3 more pipelines; next red: shadPS4 rejects ACO's
PC-relative constant-data loads. E2 (vid, e2) and E3 PASS.

Co-Authored-By: Claude <noreply@anthropic.com>
… valid packet

0026: vk_ps4_CreateComputePipelines zeroed cs_regs and copied only the code
address, so SetCsShader emitted COMPUTE_PGM_RSRC1/RSRC2 = 0 and every compute
shader ran with 0 user SGPRs (hardware reads USER_SGPR too). 0027: opengnm's
DISPATCH_DIRECT encoded 4 payload words as count 4 (PKT3 count is payload-1),
so the next packet's header was consumed. e5 now dispatches compute; next red:
host device lost during the first compute submit. E2 (vid, e2) and E3 PASS.

Co-Authored-By: Claude <noreply@anthropic.com>
…le GnmCsShader header

The slot table was read 0x1c bytes early, inside COMPUTE_PGM_LO, so binding 0's V# landed on
CS user SGPRs 0-3 where the shader expects its descriptor-set pointer (SGPR2/3). Adds opt-in
TN_PS4_VK_VALIDATION / TN_PS4_VK_CRASH_DIAGNOSTIC / TN_PS4_VK_LAYER_PATH knobs. E2 (e2, vid)
and E3 PASS; E5 next red: host device lost at buffer_cache.cpp:571 after the first dispatch.
A Dawn guest dispatches 512x8 threads over two storage buffers and a uniform and colours the
triangle from the result: PASS drawn=12.55% colour=64,65,0. The engine kernel's V#s, set
pointer and dims match it, so E5's device loss is not in the compute dispatch: the frame-2 flip
fence waits 16 s, then buffer_cache.cpp:571 reports the lost device.
…he GoldHEN send steps

One command builds the E5 app folder, packs a 52.9 MiB fPKG with OpenOrbis PkgTool.Core (run on
a pinned private Debian libssl1.1, sha256-checked) and prints the FTP 2121 upload, verify and
Package Installer steps; --dry-run does no network I/O. E5's TITLE_ID becomes TNPS78405 and its
CONTENT_ID 36 characters, the shape pkg_build requires.
@jonit-dev jonit-dev added prd:75% PRD phases in, acceptance open and removed prd:50% PRD half the phases verified labels Nov 7, 2026
jonit-dev and others added 4 commits November 6, 2026 18:40
…nd its own set memory

Every draw pointed the GPU at one mutable vertex-buffer table, so a draw executed after a later
draw refilled it read the wrong V#s: the cube's normal attribute reached the host with no format
(NVRM Xid 69, SET_VERTEX_ATTRIBUTE_A(1)) and the device was lost. Tables and composed descriptor
set memory now come from sceGnmCmdAllocInside, like the clear constants. E5 runs 300 frames with
no device loss (frames are black: next red); E2 e2/vid, E3, e3cs PASS. E5 captures move to 8 s
and 12 s, inside the window the guest presents.
e3tex (rgba16float target, textureSample + textureLoad) PASS: sampled colour 7.86% of the
presented frame. Its variants expose two vulkan-ps4 reds E5 hits: e3tex8 (rgba8unorm target)
samples the clear but not the drawn triangle, and the draw-based clear on an fp16 target
presents black. e3texmsaa waits for the ICD to report MULTISAMPLE on fp16.
…with FillMemory

The draw-based clear covered only part of its target (rows 0..63 of 256 on rgba8, run to run
variable), so a sampled rgba8 target showed a partial clear. A whole-surface clear whose colour
fits one 32-bit word now fills the RT's own byte size (tile-invariant); other clears keep the
draw, which now names its target's viewport (0030). e3tex8 PASS with the full clear; E2 e2/vid,
E3, e3cs, e3tex PASS. The draw clear's partial coverage is not root-caused yet.
@jonit-dev
jonit-dev added this pull request to the merge queue Nov 8, 2026
Any commits made after this event will not be merged.
@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

The merge queue stopped on test-native (failure).

Fix, push, re-enqueue.

@jonit-dev
jonit-dev removed this pull request from the merge queue due to a manual request Nov 8, 2026
@jonit-dev
jonit-dev enabled auto-merge November 8, 2026 21:31
@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

The merge queue stopped on test-native (failure).

Fix, push, re-enqueue.

@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

The merge queue stopped on test-native (failure).

Fix, push, re-enqueue.

@jonit-dev
jonit-dev added this pull request to the merge queue Nov 8, 2026
Any commits made after this event will not be merged.
The desktop prebuilts arrived in third_party/webp and third_party/libuv, and SDL3 came with its full
source tree. Three lookups then leaked a host into the PS4 configure: SDL3's own CMakeLists ran under
the OpenOrbis toolchain and refused to build without threads, and the Linux libwebp/libuv archives
were offered to a FreeBSD-ABI link (WebPGetInfo undefined at the player link). Exclude TN_PS4 from
all three, and link the libwebp the tn-ps4 tree does build from webp-source, so EXT_texture_webp
still decodes instead of silently loading white.

pnpm ps4 --usb now writes TN-PS4-INSTALL.txt beside the package: the install steps, the sha256 and
the one precondition (a jailbroken console), so the stick carries its own instructions.
…or it

GoldHEN's own releases stop at v2.4b18 (2024-10-28, firmware 5.05 to 11.00) and no 2.4b18.13 tag
exists, so the reseller page claiming 14.00 support has no release behind it. The retail-14.00
research record shows browser read/write and WebKit native execution, with the sandbox escape,
kernel read/write and any complete jailbreak still not demonstrated. The console bundle on the stick
is therefore ready and the install is one menu tap away, but nothing can install it until a 14.00
HEN exists.
@jonit-dev
jonit-dev removed this pull request from the merge queue due to a manual request Nov 8, 2026
@jonit-dev
jonit-dev enabled auto-merge November 8, 2026 22:19
The previous line said no public method existed. afterglow-web carries the kernel exploit for 12.00
through 14.00, run from the console's browser, with patchset-1400.bin and a ps4-hen hen.bin whose md5
matches the patched build its README requires. The research record I relied on is from 2026-10-26 and
was superseded ten days later.
…corrupt

A fake PKG without sce_sys/about/right.sprx installs and then refuses to launch with the console's
"data is corrupted" banner: the app has no licence to start under. The OpenOrbis samples carry the
blob in their .gp4 and this package did not. Stage $OO/src/modules/right.sprx beside the icon and
let it ride the file list; addMissingDirs accepts that one level deeper path only when create-gp4
emitted its parent, so nothing is mis-packed. The self-check copies it and asserts it landed.
…easibility

# Conflicts:
#	docs/verification/native-coverage-2026-08-28.md
#	packages/create-threenative/__tests__/scaffold.spec.ts
@jonit-dev
jonit-dev added this pull request to the merge queue Nov 9, 2026
Any commits made after this event will not be merged.
@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

The merge queue stopped on test-native (failure).

Fix, push, re-enqueue.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 9, 2026
@jonit-dev
jonit-dev enabled auto-merge November 9, 2026 02:36
@jonit-dev
jonit-dev added this pull request to the merge queue Nov 9, 2026
Any commits made after this event will not be merged.
@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

The merge queue stopped on test-unit (2/4) (failure).

Fix, push, re-enqueue.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 9, 2026
@jonit-dev
jonit-dev added this pull request to the merge queue Nov 9, 2026
Any commits made after this event will not be merged.
@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

The merge queue stopped on test-unit (3/4) (failure).

Fix, push, re-enqueue.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 9, 2026
@jonit-dev
jonit-dev added this pull request to the merge queue Nov 9, 2026
Any commits made after this event will not be merged.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

prd:100% PRD phases and acceptance complete

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant