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:

"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).

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:

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 console

firmware = 0 and an empty kernel-release refuse the calls that read them, rather than answering a version nobody measured.