Threads are real, and the entry point was reading garbage

Guest threading, built as real host threads throughout - principle 6, and there was never a version of this worth compromising on. scePthreadCreate, Join, Self, MutexInit, Lock and Unlock all resolve to implementations now rather than to stubs.

Each guest thread gets a host thread, a stack of its own at its own address, and its own entry into guest code with the argument the guest supplied. Stacks are spaced further apart than a stack is tall, so an overrun hits a guard page rather than a neighbour - packed adjacently it would read as memory corruption instead of as a stack overflow.

Locks could not be host Mutexes. That type's guard is the critical section, and the guest locks in one call and unlocks in a different one with arbitrary guest code between them; there is no host frame to hold a guard in. So it is a small state plus a condition variable, with ownership tracked - a non-recursive lock taken twice by its owner is refused rather than waited on, and releasing somebody else's lock is refused rather than performed.

The affinity work is configuration rather than a decision (D200). A request is recorded on the thread whatever happens to it, so a title that turns out to depend on placement can be found rather than guessed at, and the mapping folds rather than clamps - clamping puts back together threads the guest deliberately separated. The whole thing is thread::Settings, serialisable, reachable from ServiceConfig, because a question that needs a rebuild to try is a question nobody tries.

Three surprises, in ascending order of how much they cost

The knowledge file already had the answer. Handles started as small opaque integers, with a comment justifying it. The knowledge file - written a day earlier - recorded that an unimplemented scePthreadSelf returned 0x7FFF0001 and a title faulted with read of 0x5: that code being dereferenced at an offset. The guest reads fields out of what this call returns, so a handle of 1 reproduces the same fault at a lower address. Handles are now addresses of zeroed blocks (D151). The file is an input, not a report, and it only got read because an unrelated test failed.

A deadlock that passed every test. The lock table was held while a lock was acquired, so a waiter slept on the condition variable still holding the table, the owner could not reach the table to release it, and nothing woke. Every single-threaded test passed. The test that catches it blocks a real second host thread, which is the only shape that can.

rdi was never empty (D152). Adding an argument to enter_guest changed rdi from an undefined clobber to an explicit zero, and two unrelated titles instantly faulted with read of 0x0 at the identical offset - image+0x7a. Identical across two titles means the entry path, not title code. The guest entry point dereferences its first argument register immediately, and for days it had been dereferencing whatever the compiler left in that register: a stray host pointer, returning plausible garbage, good for another sixty thousand bytes and thirty-seven imports of apparent progress.

There is a real process argument block now - a zeroed page, never written, because the layout is not known from any lawful source. Both titles are back to thirty-seven imports and image+0xf2f6. Parity, not progress - the same number, now meaning something.

Nobody would have written an experiment to check whether the entry point reads rdi. Setting an inert thing to a defined value and watching two titles break identically answered it in one run.

State

Threading is built and tested but not yet exercised by a guest: both titles still die before reaching a thread call. It is ready for whichever one gets there first, and for obSCEne, which will call these deliberately rather than incidentally.

orbistoun-translate's agreement test faults with an access violation. That is the GPU thread's crate, it depends on nothing touched here, and the previous worklog entry already names the suspect - a resource the Vulkan runner is not releasing between dispatches. Left alone.