Project status
What actually works. Deliberately unflattering - an emulator status page that overstates itself is useless to its own author six months later.
The repository is committed and published, and its CI workflows run. That is worth saying first, because several claims below are checked by CI, so they are exercised on every push rather than only in a local run.
The index-versus-tree distinction has already cost something: the
line-continuation guard used git grep, which searches the
index, and reported "none added" while seeing a fraction of the tree
(D199).
The conformance probe runs
obSCEne's minimal module - one import, one library, built as a loader
bring-up instrument - loads, links, resolves sceKernelWrite
and prints from inside the guest. It is the first guest here that is not
a commercial title, and the first whose imports arrive already named
rather than as hashes.
Getting there took three fixes and none was specific to it: orbistoun read only the standard dynamic tags, where the hardware loader reads the vendor's and treats their values as offsets into a data segment rather than addresses. Every title in the corpus carries both sets, which is the only reason that had ever worked (D247).
The full probe runs too. It reaches twenty-seven conformance sections and runs to completion, announcing every check by name before making the call:
| Section | Pass | Partial | Fail | Skip |
|---|---|---|---|---|
| 000-boot | 3 | 1 | 0 | 0 |
| 005-generation | 1 | 1 | 0 | 0 |
| 007-responsive | 2 | 0 | 0 | 0 |
| 010-kernel | 5 | 0 | 0 | 0 |
| 015-sync | 11 | 1 | 1 | 1 |
| 017-posix | 4 | 0 | 0 | 1 |
| 018-relational | 18 | 0 | 0 | 0 |
| 020-memory | 9 | 0 | 0 | 0 |
| 030-thread | 2 | 1 | 0 | 0 |
| 035-libc | 29 | 1 | 0 | 0 |
| 037-math | 13 | 0 | 0 | 0 |
| 040-file | 5 | 0 | 0 | 0 |
| 050-time | 1 | 0 | 0 | 0 |
| 060-module | 3 | 0 | 0 | 0 |
| 070-user | 2 | 0 | 0 | 0 |
| 080-video | 3 | 0 | 0 | 0 |
| 090-audio | 1 | 1 | 0 | 0 |
| 100-input | 3 | 0 | 0 | 0 |
| 110-modules | 2 | 0 | 1 | 1 |
| 120-measure | 3 | 1 | 0 | 0 |
| 130-layout | 2 | 2 | 0 | 0 |
| 140-oracle | 1 | 0 | 0 | 1 |
| 150-memory-map | 2 | 0 | 0 | 0 |
| 160-gpu | 0 | 0 | 0 | 2 |
| 165-gnm | 1 | 1 | 0 | 0 |
| 900-surface | 368 | 1 | 1 | 0 |
| 910-bulk | 0 | 0 | 0 | 1 |
| total | 494 | 11 | 3 | 7 |
Read 900-surface before reading the
total. Three hundred and sixty-eight of those passes are one
section, and that section's own remaining failure is it
reporting that its passes are meaningless while every
import it exercises is a stub. It is deliberately not gamed, and a total
that hid that would be the sort of plausible output principle 3
forbids.
The three failures are 015-sync/machine-kind,
110-modules/names - which answers 0x7fff0001,
our own placeholder - and that 900-surface/control
self-report.
The shape of the change since this table was first
written is worth keeping, because it is what a session of
subsystem work looks like from outside: 037-math was
thirteen failures and is thirteen passes; 035-libc sixteen
failures and none; 018-relational five failures and eight
skips, now eighteen passes; 017-posix four failures and
none. Fifteen of the twenty-seven sections did not run at all before,
because the probe stopped earlier.
That is the whole point of it, and it is worth comparing with the
table above. A commercial title says "faults at
image+0x43c4" after nine attempts and three eliminated
classes. This says which check failed, in which area, and what it
wanted.
And it names something no title could have. The
probe's own run reports libc::round,
libc::sqrt, libc::pow and
libc::trunc as landing on stubs - while
037-math passes all thirteen of its checks. Both are true:
the probe reaches those functions through
sceKernelDlsym, which is called seven
times and which nothing implements, so a name looked up at run time
resolves differently from the same name in the import table. That is a
whole resolution path this project has never exercised, and it took a
guest that asks for symbols by name to expose it.
Every failure above is a fact about orbistoun, not about the platform. The probe is a stand-in here, so none of it grades as a measurement of the hardware (D246) - what it grades is this emulator, which is exactly what a conformance suite is for.
Runs guest code; renders nothing
Every one of the six commercial executables in the corpus loads, links, and executes real guest code. None produces a pixel, and no guest has yet spawned a thread.
| Title | Reach | Imports | Calls | Standing | Ends |
|---|---|---|---|---|---|
| PPSA99980 | flipped | 246 | 444,296 | 100% | ran to the time limit |
| PPSA02664-app0 | flipped | 199 | 417,670 | 100% | image+0x39f7c |
| PPSA03416-app0 | flipped | 198 | 469,663 | 100% | image+0x39f7c |
| obscene | flipped | 193 | 280,274 | 100% | ran to the time limit |
| obscene-payload | flipped | 187 | 4,914 | 100% | 0x5e2d |
| PPSA25872-app0 | entered | 151 | 321,976 | 100% | image+0x17554a3 |
| PPSA04263-app0 | entered | 70 | 30,261 | 100% | image+0x196b91a |
| PPSA21564-app0 | entered | 57 | 500,260 | 100% | the title's own modules+0x7af792 |
| PPSA28061-app0 | entered | 47 | 933 | 94% | image+0x43c4 |
Under a measured policy - stubs answering by name, so these reach further by construction and are not comparable with the table above:
- BFpilot_v0.4.4 reached 0 imports, ending at
0x1, with 1 function answered by name - BackPork_0.1 reached 0 imports, ending at
0x1, with 1 function answered by name - CheatRunner_v0.17 reached 0 imports, ending at
0x1, with 1 function answered by name - Lapy-JB-Daemon_v1.2 reached 0 imports, ending at
0x1, with 1 function answered by name - PPSA02664-app0 reached 215 imports, ending at
image+0x42c76, with 2 functions answered by name - PPSA03416-app0 reached 39 imports, ending at
image+0xb14be3, with 1 function answered by name - PPSA04263-app0 reached 72 imports, ending at
image+0x196b91a, with 1 function answered by name - PPSA21564-app0 reached 55 imports, ending at
image+0x11ccd, with 1 function answered by name - PPSA25872-app0 reached 56 imports, ending at
image+0x7b594e, with 1 function answered by name - PPSA28061-app0 reached 60 imports, ending at
the guest called abort, with 1 function answered by name - ShadowMountPlus_1.6beta16 reached 0 imports, ending
at
0x1, with 1 function answered by name - WebKit-Autoloader-Installer_v0.4.0 reached 0
imports, ending at
0x1, with 1 function answered by name - elfldr_v0.26 reached 0 imports, ending at
0x1, with 1 function answered by name - etaHEN_2.5B reached 0 imports, ending at
0x1, with 1 function answered by name - ftpsrv-drakmor_1.15-ng-stable reached 0 imports,
ending at
0x1, with 1 function answered by name - ftpsrv_v0.21.1 reached 0 imports, ending at
0x1, with 1 function answered by name - garlic-savemgr_v1.13 reached 0 imports, ending at
0x1, with 1 function answered by name - klogsrv_v0.9 reached 0 imports, ending at
0x1, with 1 function answered by name - kstuff-lite_v1.10 reached 0 imports, ending at
0x1, with 1 function answered by name - kstuff-toggle_0.6 reached 0 imports, ending at
0x1, with 1 function answered by name - kstuff_v1.6.7 reached 0 imports, ending at
0x1, with 1 function answered by name - nanoDNS_0.4 reached 0 imports, ending at
0x1, with 1 function answered by name - obscene reached 193 imports, ending at
ran to the time limit, with 1 function answered by name - pldmgr_v0.5.1 reached 0 imports, ending at
0x1, with 1 function answered by name - prosperous reached 0 imports, ending at
0x1, with 1 function answered by name - ps5-app-dumper_v1.11 reached 0 imports, ending at
0x1, with 1 function answered by name - ps5-linux-loader_v2.4 reached 0 imports, ending at
0x1, with 1 function answered by name - ps5-web-file-manager_v1.7 reached 0 imports, ending
at
0x1, with 1 function answered by name - ps5debug-NG_1.3.0 reached 0 imports, ending at
0x1, with 1 function answered by name - ps5upload_v5.14.0 reached 0 imports, ending at
0x1, with 1 function answered by name - shsrv_v0.20 reached 0 imports, ending at
0x1, with 1 function answered by name - websrv_v0.34 reached 0 imports, ending at
0x1, with 1 function answered by name - zftpd_v1.5.0 reached 0 imports, ending at
0x1, with 1 function answered by name
"Standing" is the number to read, not "calls". It is the share of calls answered by a real implementation rather than a placeholder, and it exists because a call count rises when stubs start lying (D181). PPSA04263's ninety-nine million calls are one function in a loop; PPSA28061's nine hundred are the interesting ones.
Two titles print their own diagnostics through an implemented
printf, which is how four functions were named (D186), and
one prints its texture loads - ten files, correct sizes, read from
/app0, which is what confirms the filesystem layer rather
than inferring it.
Where the roadmap actually is
Phases 0b, 1, 2, 2b, 3 and 4 are done. Phase 5 has begun and
is not close to its own observable result: mutexes and
semaphores are built and exercised - one title constructs eleven mutexes
during static initialisation - but scePthreadCreate has
never been called by any title. Every guest is still in single-threaded
startup.
So the three current walls are phase 4 completion problems, not threading ones. Phase 6's contents (shader decode and translation) are being built ahead of the spine in parallel.
Numbers, measured rather than estimated
| Functions declared / implemented | 953 / 705 |
| Declared in a library that serves nothing | 219 across 27 libraries - names written down, no implementation |
| Recorded behaviours | 759 - 365 published, 25 measured, 75 guest-observed, 256 assumed |
| Open questions a hardware probe could settle | 790 |
| Symbol database | 30184 names - 714 from this repository, 29453 from this repository and the module, 17 from this repository and a run of the module, 0 unaccounted |
The furthest title imports 733 symbols, of which 565 have no name yet. That one is not in the block: it needs a title, the corpus is not tracked, and a generated number that cannot be recomputed by a contributor who has no titles is a number that will be wrong without anybody being able to tell (D240).
Every one of those is printed by the tool rather than counted by
hand: symbols, knows, questions,
audit symbols/generated.json, and
verify <module> respectively. A number in this
document that no command produces is a number that will be wrong within
a week.
That last ratio is the honest headline. Naming has improved sharply - reading candidate names out of the modules' own bytes found 175 in one pass, where a sweep of 2.58 billion generated candidates found none (D193) - and it is still 565 unnamed in one executable.
Every harvesting mechanism has now been run to exhaustion against the current inputs, and the marginal yield is zero. The last full pass over all 53 modules named nothing: the 2.6-billion-candidate sweep found none, the corpus strings found none they had not already given up, and the runtime harvester found none. That is not a fault - it is the plateau those mechanisms have, and it says exactly where the next name has to come from. Either a word the vocabulary does not have, or a source that is not yet being read. Both are listed in PROVENANCE.md and BACKLOG.md; the cheapest is a cited C++ ABI name list, and the one aimed at a live wall is call-position inference.
The three walls
Both of the first two now point at the same thing, reached from opposite directions: an out-parameter nobody wrote. That convergence is worth more than either result alone, and it is a measurement rather than a reading - each step below eliminated a class by running something, not by arguing about it (D217).
PPSA28061,
image+0x43c4. A null dereference after ten textures load correctly. Nine attempts. The full register set - visible since D230, captured all along - saysrbxholds a host heap pointer, one of our own allocator's results, so the guest is holding a valid allocation when it dies;r13=0is the null it reads through; andrcx=rdx=0x7fff0001, a sentinel that also appears as an argument in PPSA02664. Known not to be a missing import, not a wrong return value, and now not a read of uninitialised stack: with the stack filled with0x5a(D185) the run is byte-identical - same fault, samerdi=0x3andrax=0x9ba49, same ten textures. Three classes gone; a side effect nobody performed is what is left, somewhere that is not the stack.PPSA02664 / PPSA03416,
image+0xafc959. A write to0xfffe0withrdx = 0x100000.memalignwas ruled out by dumping it. The lead islibkernel::0x6abac2f3dc6f8cee, unnamed, called immediately before - and its arguments were unreadable for weeks because the dump path's readable window was declared a guard page too low, so its one pointer landed in the excluded page and rendered as a count (D217).Corrected, it reads:
arg1 = 0x100000- the same megabyte asrdx- witharg3 = 0x40000alignment, and0x100000 - 0x20 = 0xfffe0is the faulting address exactly.It is a region allocator, and that is now a positive fact rather than an elimination. Mapping the faulting address and watching it shows the guest writing a circular list head with one node - an arena descriptor, locked by the mutex it takes immediately before. It asks for a megabyte aligned to 256 KiB and lays that header at
region_end - 0x20; with the base lost,region_endis0 + 0x100000(D224, corrected by D226).The whole visible interface of that function is now eliminated - all eight slots of
arg0andarg5, and the return value, each with a count proving the plant landed. Two of the eight are inputs rather than out-parameters: dyeingarg0+8orarg0+0x10makes the caller die on an illegal instruction, while dyeing any ofarg5's three leaves the fault byte-identical to an ordinary run. Also eliminated: unwritten stack, unwritten heap,memalign, and the one field of the faulting object that stays zero all run.So the base does not come back through this call at all, and the question moves earlier: something before it should have established the region and did not. That is a negative result rather than a lead, and it is worth more than the reading it replaces - the allocator characterisation stands, but "this is where the base is lost" does not.
Nor does it come back through any other unimplemented call. All seven in the title had their return values dyed in one run and their offset-zero out-parameters swept in two more; the fault is byte-identical every time.
memalignis eliminated too, now that a forced answer reaches implemented functions and can be paired with a mapping so the guest runs on (D234). What remains is an implemented function, or not a call at all. Implementing the allocator is the fix; the mapping is a diagnostic and buyingFURTHERwith it would be exactly the progress principle 3 refuses.Nor through the arguments of any implemented one. Every import the guest calls - all twenty-three, implemented and not - has had a sentinel planted at each of its first six argument slots, twice, in 276 boots taking fifty seconds. No import's fault follows sentinel arithmetic, so the megabyte base is not delivered through an argument of anything the guest calls. The slots that were refused are a measurement too: they hold something that is not a writable address, which for this call set is most of them.
Two classes of result came out of it, neither of them a lead. Five imports dereference
arg1- the fault lands exactly on the sentinel, which is what overwriting any live pointer does. One,scePthreadMutexattrInit, derails the guest into non-code (D244). Nothing was ambiguous: no import came back as an inconsistent move.And the return values are exhausted too. Every import, implemented or not, has been forced to answer each of two sentinels placed high enough that a region base computed from one still faults. Twenty-two of twenty-three are indifferent - byte-identical faults whatever they are told to answer. The exception is
memalign, whose answer is dereferenced at+0x10; a real relationship, and not this wall's+0xfffe0. Six and a half seconds for all of it (D249).So the base comes back neither through an argument of any call nor as the answer to any call. What remains is not a call at all - established at load time, or computed by the guest from something it already has.
The other diagnostic axes are exhausted too, and also negative. Unwritten heap and three reservations sized around the faulting address change nothing at all. Poisoning zero-initialised statics does change the run, and is unusable here: the guest reaches eight distinct imports instead of twenty-three and dies somewhere it had never previously got to, so the poison breaks it long before the wall. Recorded as a regression rather than a lead, which took a second signal to see (D232).
One of those was not a measurement when it was written down. "The stub return" was eliminated by a run in which nothing was overridden:
StubPolicyis keyed by symbol name and this function has none, so the override matched nothing and the default answered. It reported no change because no change was the only thing it could report. A forced-return diagnostic that reaches a function by hash now exists, and the run under it says(1 answered)- so the elimination is real, and reproduced the broken run's output exactly (D230).The full register set at the fault - printed since D230, captured all along - reads
rbx=0x20,r14=0x20,r15=0x10, putting the header offset in a register, and leaves the missing base in one ofrax,rsi,r8,r11,r12, all zero.PPSA04263. Spins on
sceKernelDirectMemoryQuery- 852 million calls in twenty seconds - reaching no synchronisation primitives and never allocating anything. It calls four imports, exactly one of them unimplemented:libc::0x92f57c2dc704346f, the same function PPSA02664 calls and the one handed the guest's entry point. That it never accounts for all the memory it was told exists was checked and disproved by reading the test that asserts the map has no gaps, rather than by scheduling a run. Four answers have now been swept and it is indifferent to all of them: the return code, the third structure field, whether the buffer is cleared, and the map shape. Given four regions it walked every one, in order, correctly, and restarted anyway (D218).
A correction worth keeping. The out-parameter reading above was the only surviving explanation for the first two walls this morning. Planting values into both stack-pointing arguments of the wall function moved nothing, so it is now in trouble itself. Things eliminated by measurement, nothing confirmed - a worse position than yesterday, and a better-understood one.
And a second correction, of the same kind twice over. Two of those eliminations were diagnostics that never ran: an override that could not key an unnamed function (D230), and a plant that could only reach offset zero of an eight-word structure (D229). Both reported "no change" and were read as results. Every diagnostic now reports a count of what it actually did, because an experiment that did nothing and an experiment that changed nothing produce identical output otherwise - and only one of them is a measurement.
Known unknowns
Deliberately left as gaps rather than filled with plausible values:
Two
libcfunctions are characterised but unnameable. One is handed the guest's entry point and called once by every title; the other takes aprintfformat string. Their argument dumps say what they do (D194). Every naming source this project has is exhausted for both - every harvested standard name misses, their modules' strings do not contain them, no grammar shape reaches them. This is the case obSCEne exists for.Function arities in the declarations are provisional where they are not marked otherwise. A wrong arity degrades trace fidelity; it does not break a call.
Every vendor error code.
GuestErrorplaceholders avoid the high bit precisely so they cannot be mistaken for established firmware values.The symbol audit reports tiers rather than a pass or a fail. A name either re-derives from this repository alone, or was worked out by observation - ours, checkable by anyone holding the title, but not reproducible by re-running an index - or is unaccounted, meaning the grammar cannot regenerate it at all.
The middle tier is the honest one to watch. It grows as titles are read and shrinks as the grammar learns to spell what they contained (D195), so it measures the distance between what this project has seen and what it can reproduce. The current split is in the generated block above rather than written out here, because it moves every time a search runs. See PROVENANCE.md.
Measurement that cannot currently be trusted
The guest limit was wall-clock, so the duration was fixed and the call count varied - and the call count is what every verdict is read off. Three identical runs of PPSA04263 returned 77.5M, 75.8M and 87.6M calls, a 13% spread with no change to the build.
Now built. A call budget fixes the count and lets the duration vary; three runs after the change returned 20,000,000 calls each. The wall clock stays as a backstop, because a guest that stops calling imports never reaches a budget and would hang, and the exit status says which of the two fired (D238).
What that does not fix: call throughput still cannot be compared across runs, because the duration is now the varying quantity. A performance question about the dispatch path is still unanswerable this way - it needs process CPU time, not wall clock.
Conditions records the limit, the stub policy and the
build on every trace, and a comparison across differing conditions is
labelled as measuring a settings change rather than progress (D181).
That makes the gap visible; it does not close it.
Presenting a specific console
orbistoun's Machine presents a console by four axes -
generation, kind (retail/devkit), revision, and firmware - all
configured in shell.toml and all refusing by default,
because an unconfigured emulator does not claim to be a particular
machine.
The reference target, measured on real hardware (D405), is a retail Prospero-generation console on system software 12.40:
[machine]
generation = "ps5"
kind = "cex"
revision = "base"
firmware = 4672 # 0x1240 = 12.40, the form call 649 answers
kernel-release = "0.0-prototype" # what kern.osrelease returns on this consolefirmware = 0 and an empty kernel-release
refuse the calls that read them, rather than answering a version nobody
measured.