What each loader does with obSCEne

Results per loader, from the reports themselves. EMULATORS.md says what each loader is and how to run it.

The table is generated by obscene-tool compat, and verify.sh fails if it differs from the reports it was built from.

Results

host shadPS4 PS5PCEM fpPS4 kyty orbistoun
Records 36559 36575 36432 194 36520 36549
Ran to the end yes yes yes no yes yes
Output channel host sceKernelWrite write - puts sceKernelWrite
Pass / partial / fail / skip 116/15/358/26 456/7/37/15 118/68/262/67 - 429/11/45/30 416/16/56/27
Excluded to keep the loader alive 0 0 0 0 0 0
Skipped after dying on a previous run 0 4 0 19 11 3
Capabilities established 7 9 8 - 7 6
Checks blocked behind a missing one 3 3 0 - 12 3
Deepest wholly-green section 20 25 20 - 25 4
Census present / absent 826 / 34505 35330 / 0 (void) 3732 / 31598 0 / 0 35330 / 0 (void) 35330 / 0 (void)
Probed functions responding / silent 54 / 0 22 / 32 0 / 0 0 / 0 26 / 28 31 / 23
Died in 900-surface/control 000-boot/number-formatting 000-boot/number-formatting 000-boot/number-formatting 000-boot/number-formatting 000-boot/number-formatting

Sections the loaders do not agree on, pass/partial/fail/skip:

Section host shadPS4 PS5PCEM fpPS4 kyty orbistoun
000-boot 4/0/0/0 4/0/0/0 4/0/0/0 4/0/0/0 3/0/1/0 2/2/0/0
005-generation 0/1/0/1 1/1/0/0 1/1/0/0 1/1/0/0 1/1/0/0 1/1/0/0
007-responsive 2/0/0/0 0/2/0/0 0/0/0/2 0/0/0/2 0/1/1/0 0/2/0/0
010-kernel 3/0/0/0 3/0/0/0 3/0/0/0 2/1/0/0 3/0/0/0 3/0/0/0
015-sync 11/1/0/2 12/1/1/0 11/0/1/2 12/0/1/1 7/2/4/1 5/0/7/2
017-posix 4/0/0/1 1/0/4/0 0/0/0/5 0/0/0/5 2/0/3/0 0/0/4/1
018-relational 18/0/0/0 18/0/0/0 17/0/1/0 16/0/1/1 14/0/1/3 8/0/2/8
020-memory 4/0/1/4 9/0/0/0 8/0/0/1 9/0/0/0 8/0/0/1 2/0/3/4
030-thread 3/0/0/0 3/0/0/0 3/0/0/0 3/0/0/0 3/0/0/0 2/1/0/0
035-libc 30/0/0/0 5/1/19/5 0/0/0/30 - 0/1/15/14 10/1/16/3
037-math 13/0/0/0 3/0/10/0 0/0/0/13 - 0/0/13/0 0/0/13/0
040-file 5/0/0/2 5/0/1/1 6/0/1/0 - 6/0/1/0 2/2/3/0
050-time 1/0/0/0 1/0/0/0 1/0/0/0 - 1/0/0/0 0/0/1/0
060-module 3/0/0/0 3/0/0/0 3/0/0/0 - 1/0/1/1 2/0/1/0
070-user 0/0/2/0 2/0/0/0 2/0/0/0 - 2/0/0/0 0/0/2/0
080-video 1/0/0/2 0/0/0/3 1/0/0/2 - 0/1/0/2 2/0/0/1
090-audio 1/1/0/0 2/0/0/0 2/0/0/0 - 2/0/0/0 1/1/0/0
100-input 1/1/0/1 3/0/0/0 3/0/0/0 - 2/1/0/0 1/1/0/1
110-modules 1/0/0/3 2/0/1/1 1/0/0/3 - 0/0/1/3 1/1/1/1
120-measure 4/0/0/0 3/1/0/0 4/0/0/0 - 3/1/0/0 1/1/2/0
130-layout 3/0/0/1 3/0/0/1 0/2/0/2 - 0/2/1/1 2/1/0/1
150-memory-map 1/1/0/0 2/0/0/0 0/0/2/0 - 0/0/2/0 1/0/0/1
165-gnm 0/0/0/2 2/0/0/0 0/0/0/2 - 2/0/0/0 1/1/0/0
900-surface 2/10/355/3 368/1/1/0 47/65/257/1 - 368/1/1/0 368/1/1/0

Real hardware

The table has no hardware column. A retail console is the reference every loader in the table is measured against, not one of the loaders. Its results are in HARDWARE.md, each citing a record in data/hardware/. ./bin/obscene report and ./bin/obscene deploy capture a run from the console.

obSCEne running on a retail console, drawing its own report

Reading the table

Patched loaders

patches/ holds local changes to Kyty and fpPS4 that return an error or zero where the stock loader stops the process on an unrecognised input. A report from a patched loader goes to reports/<loader>-patched.txt and never occupies that loader's column (D176). A patch that makes an unbound function return zero also turns an honest failure into an apparent success: under the patched Kyty, obSCEne reports a ready display for a display that cannot present a frame.

Behavioural census

910-bulk calls every censused symbol with all arguments zero and buckets what came back. A function taking an out-pointer or a handle cannot succeed on zero, so the correct answer for most calls is a refusal. A library that answers zero to every call is resolving its imports to stubs. A library with a high refusal rate is validating its arguments, and matching it means matching real behaviour. A single zero is not proof of a stub: some functions succeed on zero arguments, and OUTPUT.md states that the bucket cannot separate the two.

Screenshots

obSCEne draws its report to the screen for the case where no text channel works. A capture usually lands on the summary page; once the report is complete the presenter cycles pages, so the page captured depends on when the run finished.

scripts/screenshot.sh captures with PrintWindow and PW_RENDERFULLCONTENT, which renders a window that is covered or never shown; without the flag a GPU-composited window captures as a blank rectangle. It enumerates top-level windows by owning process id, prefers a visible one, and falls back to any window larger than 64x64, because a loader that opens its window from a render thread has no MainWindowHandle.

shadPS4

obSCEne under shadPS4

The 900-SURFACE bar is mostly green because shadPS4 resolves nearly every censused symbol, including libraries it does not implement.

PS5PCEM

obSCEne under PS5PCEM

The header reads GEN 5 (agc). The display takes whichever video-out pair the platform has, preferring the older when both appear present (D140). 900-SURFACE is mostly red, because PS5PCEM resolves what it implements and nothing else.

fpPS4

obSCEne under fpPS4

A detail page showing individual 035-libc checks. The exclusion list names 035-libc checks individually rather than the section, because some of them return and some do not. The header reads GEN UNKNOWN: fpPS4 installs a logging stub for every unresolved import, so both generations' graphics symbols appear present, and presence then proves nothing (D140).

Kyty

obSCEne under Kyty

Kyty re-reads a registered framebuffer from guest memory only when its GPU submission counter advances. obSCEne draws with the CPU and submits nothing, so the stock loader shows a black window; the local patch covers it. Kyty names every import it cannot resolve, which a stub-everything loader cannot do. Stock Kyty's text channel does not reach the report: its sceKernelWrite requires an opened file, and it registers puts under a different library name from the one this module imports.

orbistoun

orbistoun has no screenshot. obSCEne opens its display against the initial user, and orbistoun answers both user-service calls with its Unimplemented placeholder 0x7fff0001, so obSCEne opens no display:

070-user/initialise    fail  0x7fff0001  the user service refused to initialise
070-user/initial-user  fail  0x7fff0001  no initial user could be determined
OBS|display|absent|no initial user, even after initialising the service

The loaders that implement sceUserServiceGetInitialUser each return a different value, so none of them is evidence of what hardware returns.

Regenerating

Each loader gets its own build directory. The exclusion list lives beside the module and is per loader, and scripts/bulk-sweep.sh and scripts/sweep.sh write different kinds of exclusion into the same file.

# One directory per loader. --resume keeps a list already established.
sh scripts/sweep.sh --build /tmp/obs-shad --generation 4 \
    --emulator <emulators>/shadps4/shadPS4.exe --out reports/shadps4.txt
sh scripts/sweep.sh --build /tmp/obs-fp --resume --generation 4 \
    --emulator <emulators>/src/fpPS4/fpPS4.exe --out reports/fpps4.txt

# Loaders needing no exclusion walk run directly against a built module.
VM_MODULE=/tmp/obs-orb/obscene.module.elf sh scripts/run-emulator.sh \
    --emulator <emulators>/src/PS5PCEM/zig-out/bin/game-run.exe --out reports/ps5pcem.txt
VM_MODULE=/tmp/obs-orb/obscene.module.elf sh scripts/run-emulator.sh \
    --emulator <OOPS>/orbistoun/target/release/orbistoun-cli.exe --out reports/orbistoun.txt

# Kyty has its own runner: the report cannot arrive, the unresolved list can.
VM_MODULE=/tmp/obs-orb/obscene.module.elf sh scripts/run-kyty.sh --out reports/kyty.txt
cd tool && cargo run --quiet -- unresolved --root .. /tmp/obscene-kyty/_kyty.txt

cd tool && cargo run --quiet -- compat --into ../docs/COMPATIBILITY.md --write \
    "host=../reports/host.txt" "shadPS4=../reports/shadps4.txt" \
    "PS5PCEM=../reports/ps5pcem.txt" "fpPS4=../reports/fpps4.txt" \
    "kyty=../reports/kyty.txt" "orbistoun=../reports/orbistoun.txt"