D564 - A placeholder answered as a size is a very quiet four gigabytes

Status: guest-observed Date: 2026-09-04

What was found

PPSA28061 stops at 25 imports where its record claimed 47. Chasing that turned up the sequence below, which is worth reading in order:

#311  sceUltWaitingQueueResourcePoolGetWorkAreaSize(16, 16) -> 0x7fff0001
#312  malloc(0x7fff0001)                                    -> a pointer
#313  _sceUltWaitingQueueResourcePoolCreate(...)            -> 0x7fff0001
#314  sceUltUlthreadRuntimeGetWorkAreaSize(16, 3)           -> 0x7fff0001
#315  malloc(0x7fff0001)                                    -> a pointer
#316  _sceUltUlthreadRuntimeCreate(...)                     -> 0x7fff0001

0x7fff_0001 is orbistoun's unimplemented placeholder. As a size it is 2,147,418,113 bytes, and this emulator's malloc served both requests. Four gigabytes, twice over, and neither the run report nor any gate said a word.

The principle this sharpens

Principle 3 says a stub returning success is indistinguishable from working code. The placeholder exists to be loud - it is a value no vendor function returns, so a guest that acts on it goes visibly wrong.

That reasoning assumes the return is a status. Where a function answers a size, the placeholder is not loud at all: it is a number the caller spends, and spending it succeeds. The D125 family already covers a placeholder answered where a pointer was wanted; this is the same mistake one type over, and it is quieter than the pointer case because nothing crashes.

Worth stating as a rule: a function whose answer is arithmetic - a size, a count, a length, an offset - cannot be left unimplemented safely. It needs a real number or an explicit failure, and the placeholder is neither.

Correction, same day: that rule already existed, and was already wired

The paragraph above was written as though this were a new insight. It was not. knowledge::Returns::Count carries the rule verbatim - "A count, a length, a size. Zero is the safe answer: a caller that loops over the result then does nothing, where a large value walks off the end of a buffer" - and Returns::stub_value returns Some(0) for it. The service consults that on every unimplemented call.

So a function classified count would have answered zero, and no 2 GiB request would ever have been made. The sizers answered the placeholder for a much duller reason: they had no knowledge entry at all, so there was no returns to consult.

That relocates the finding, and makes it larger rather than smaller. The failure is not a missing rule; it is that the safety net only covers functions somebody has written down. Across every trace on this machine, 172 distinct functions are called and unimplemented, and 97 of them carry no returns classification - for all 97, the placeholder is handed to the caller whatever it means to read it.

Classifying them from their names is not the answer and this project already knows it: of the four whose names look size-shaped, sceAgcDcbSetIndexSize is a setter and sceKernelAprResolveFilepathsToIdsAndFileSizes returns a status. Half the name-shaped signal is wrong, which is D356's argument for refusing to classify prose.

What was implemented

The five libSceUlt setup calls PPSA28061 makes: sceUltInitialize, both GetWorkAreaSize functions, and both constructors. Arities came from the run - the third register on each sizing call holds the placeholder left by an earlier stub, the evidence that fixed sceKernelCreateEqueue (D516) and scePthreadSetaffinity (D523).

Sizes are now 0x1100 and 0xa80 - 4,352 and 2,688 bytes where they were 2 GiB each.

The work-area size is orbistoun's own number, not a measurement, and this is defensible for the same reason the Agc shader object would be: orbistoun implements the sizer and the constructor, and stores nothing in the block - an Ult object's state lives in this crate's table, the way every handle family here works. The number is chosen to be non-zero, so a caller checking for a failed sizing sees success, and proportional to the request, so one sanity-checking "more threads, more memory" is not surprised.

What it did not change

PPSA28061 still stops at 25 imports. The abort is Agc's, not Ult's - the guest goes on to sceAgcDriverRegisterDefaultOwner, sceAgcCreateShader, both answer the placeholder, and it calls abort. That was clear before this was implemented and is unchanged by it.

So this bought no reach. It bought the removal of two 2 GiB allocations, a runtime and a pool that actually construct, and the retirement of five entries from the work list. Constructing a runtime is not running fibres on it: _sceUltUlthreadCreate remains unbuilt, and a guest reaching this far then creating a fibre will stop there - which is a better place to stop.

What this does not establish

That the work-area size is adequate. A console whose constructor validated the block would reject one this small, and nothing here would see it - the constructor that reads it is also orbistoun's.

Nor what sceUltInitialize's three arguments mean. 0x1c, a structure whose first word is 0x18, and 1. Recorded, not interpreted.