2026-09-03 - (/loop) The oscillation is settled: it was the guest's own allocator
guest runs 69 across 13 bases and both directions
oscillation 9-of-12 -> 0-of-51, and flipped on demand
Arrived by hand again - the nineteenth wakeup that did not fire.
What was built
ORBISTOUN_HEAP_BASE, a diagnostic that serves every
guest allocation from a bump region reserved at a fixed address instead
of from std::alloc. Same block shape as the host path -
D128's header, total bytes aligned to align -
so free, realloc and header_of
are written once and never learn which allocator answered.
free asks whether the pointer is in the region
before it reads the header, because both allocators
write the same header and dealloc against reserved memory
is undefined behaviour.
The dependency edge D499 priced in does not exist:
orbistoun-libc already takes orbistoun-thunk,
which already reserves at fixed bases, and principle 4 requires the
reservation to go through orbistoun-mem anyway.
What it settled, in three steps
Fixing the heap collapses the oscillation. Twelve interleaved pairs: the host heap gave 2080/46 nine times out of twelve, the fixed heap gave 2077/44 twelve out of twelve.
The address value is not what does it. Thirteen
bases from 0x1000_0000_0000 upward, varying bits 16 through
45 - fifty-one runs, every one 2077. A negative, and the one that made
the next step worth taking.
Reversing the order flips the branch. The region grows downward instead of upward and nothing else changes - same base, same span, same determinism, same contiguity, still never reused. Six interleaved pairs, zero crossover:
ascending 2077 2077 2077 2077 2077 2077
descending 2080 2080 2080 2080 2080 2080
And the traces say what the guest is doing
only in 2080 sceKernelAllocateMainDirectMemory, sceKernelMapDirectMemory,
sceKernelSetVirtualRangeName, a fourth sceKernelDirectMemoryQuery
only in 2077 sceKernelMprotect
A heap deciding whether it can grow in place. Block above what it
holds - extend the range, one mprotect. Block below - take
a fresh direct-memory segment, map it, name it. The host allocator
settles that relationship differently run to run because it reuses freed
blocks, and the whole ~40/60 split was that.
So -down is now the cheapest way to reach the
direct-memory path on a title that otherwise never asks for it:
three functions and a query, one environment variable away.
Three surprises worth keeping
The first base broke the run, and the address map is
why. 0x7400_0000_0000 looked clear of the module
base and the thunk table. It is
orbistoun_kernel::MAPPING_BASE, which
sceKernelReserveVirtualRange hands out from - sixteen
refused reservations and a fall from 2077 calls to 219. The map was
twenty bases across nine crates with no document to check first. The
default moved into the 0x0000_5E2* family this project
already keeps for regions of its own invention, and the map is now
docs/ADDRESS_MAP.md, gated by a test that walks the tree
and refuses two bases within four gibibytes - watched refusing this
exact collision at a distance of zero.
Every intervening diagnostic was silent on the path that
matters. heap_fill_summary and
direct_fill_summary printed from
summarise_calls, which only a timeout and an exhausted
budget reach. A fault reached neither - the most common ending this
project has, and the one they exist to explain. Moved to
persist, which every ending comes through.
The verdict was reporting an allocator strategy as
progress. The 2080 branch reaches three imports the 2077 branch
does not, so the report calls it FURTHER. Both branches
fault at the same instruction on the same read of 0x8.
Nothing got further; different code ran.
Both halves were broken and watched to fail
allocate ignores the region -> first block at 0x2a3... , not base+16 FAILED
free skips the range check -> STATUS_ACCESS_VIOLATION (0xc0000005)
The descending direction is an instrument, so it has its own test asserting it really descends - a region that failed to reverse and a region that reversed and changed nothing are the same output otherwise (check 3).
What is not established
Which branch the console takes. Both are paths the guest has; the platform's own allocator picks by a rule nobody has measured. What orbistoun has now is the ability to choose one deterministically, which every later experiment against this wall needed.
scePthreadSelf still answers a host address that differs
between runs. Fifty-one runs say it is not what decides this branch, but
it is a second place a guest can read the host's layout and it is
untouched.
Decision: D513.