2026-09-03 - (/loop) The oscillation is one allocation round, and the backlog is now the whole list
runs of PPSA02664 37
outcomes 2 (2077 calls/44 distinct, 2080/46)
hypotheses killed 4
differential 146 -> 194 cases
obSCEne backlog 022 4 entries -> 504 functions, 717 questions
Arrived by hand again - six scheduled wakeups now, none of which fired.
The ±3 is a branch, and it moves the verdict again
D488 left a ±3 call oscillation with the note that
it "does not move the verdict, which keys on distinct imports".
At the wall D489 moved the guest to, the distinct count oscillates too:
2077 calls / 44 distinct, or 2080 /
46. Fifteen and twenty-two of thirty-seven runs. The verdict
reads same on one and FURTHER on the other, on
identical code.
It is one allocation round:
| 44-distinct | 46-distinct | |
|---|---|---|
sceKernelMprotect |
1 | absent |
sceKernelAllocateMainDirectMemory |
absent | 1 |
sceKernelMapDirectMemory |
absent | 1 |
sceKernelSetVirtualRangeName |
absent | 1 |
sceKernelDirectMemoryQuery |
3 | 4 |
Exactly D488's table, one round instead of the fourteenth - the same phenomenon that decision half-fixed, not a second one. Ordered by first call the two runs are identical through index 18, every import with the same count, and diverge at 19.
Four hypotheses killed
The persisted run record (deleted before each of three runs), the
retained sandbox (ORBISTOUN_SANDBOX=ephemeral, six runs),
uninitialised memory contents (heap, stack and direct filled together,
five runs each) - all still bimodal.
And my own first reading: the first six runs alternated perfectly, which reads as a mechanism carrying state between runs. Thirty-one more runs made it a coin flip. Six samples could not tell those apart, which is an argument for the three-sample rule being a floor rather than a target.
What is left is a leak the report already names
One non-deterministic input remains: D128 serves malloc
from std::alloc, so the guest holds host heap
addresses that move under ASLR on every launch. orbistoun's own
dump diagnostic says what is wrong with them:
arg5 = 0x7ff71530f030 -> no region this run mapped, and address-shaped
D128's reasoning - identity-mapped, so a host allocation is a guest allocation, and a private arena buys nothing - is about correctness and it holds. It did not weigh reproducibility.
Not built here. The test is a heap at a fixed base,
and that needs orbistoun-libc to depend on
orbistoun-mem and to register the region in the address
space - a second allocator, which D128 declined, and which done
carelessly recreates the collision class D463 and D488 have already paid
for twice. That is a decision to take deliberately with this measurement
in front of it. D499.
And the obSCEne backlog is the whole list now
status reports 717 open questions a hardware
probe could settle; the backlog written yesterday held four.
Joined orbistoun-cli questions against what obSCEne can
call and tiered it by what blocks the measurement: 137 callable
today (1.26M recorded calls), 63 needing a
signature, 303 not even censused. obSCEne
D320.
Two of the loudest functions a guest touches -
sceKernelDebugOutText at 220,383 calls and
_Getpctype at 415 in the current run - were not on
obSCEne's radar in any form.
And the ctype family goes from no coverage to exhaustive
Differential item (c) was largely stale - strstr
repeated prefixes, strncasecmp edges and qsort
duplicates/reversed are all already covered. The real gap, found by
diffing the bound implementations against the cases: thirteen
ctype functions with no case between them, and this title calls
into that family 415 times in a run that dies after 2,077 calls.
146 -> 194 cases. The eleven predicates are swept
over all of 0..127 as two 64-bit bitmaps each - a case per
character would have been 1,408 of them for the simplest functions in
the file, and a bitmap samples nothing.
tolower/toupper get the boundaries instead,
because their answer is a byte and
@[/backtick-{ are where an implementation that
adds 0x20 without a range check shows itself.
Stopped at 127 deliberately: above it the answer is a locale
property, and pinning glibc's high half here would record an assumption
as the contract. obSCEne's 035-libc/getpctype is what
settles that half.
Compared as classified or not, never as the raw return - the standard promises non-zero and glibc answers a mask out of its table.
Watched failing. Clearing bit 1 of
isalpha/high-half (the letter A) in the
committed reference gives
returned 0x7fffffe07fffffe, glibc 2.39 returned 0x7fffffe07fffffc
- orbistoun holding the correct answer against a corrupted expectation.
Restored with the same two-line anchor. The harness already had
every_recorded_case_can_be_rebuilt, which is why a green
run means the new cases were compared rather than skipped.
One thing to watch: the two edits I made to break and restore rewrote
the reference file with CRLF - Python's text-mode write translates on
Windows - and the gate strips carriage returns from the fresh output
before diffing, so it would have failed on line endings alone.
Regenerated in place through the same pipeline the gate uses;
od and a byte count confirm 0 CR, 588 LF, 194 cases.
And the instrument lied about it, which is the fifth this
session. grep -c for a carriage return in Git Bash
reports 3 for a file just written with three LF-only lines: the
$'\r' escape never reaches grep. Two readings of "588 CR
lines" were facts about the shell's quoting. The control took one line -
grep a known-LF file and a known-CRLF file, and watch them answer the
same number.
State
cargo test --workspace green - 117 suites, 1992
tests, 0 failures. clippy --tests clean, fmt
clean, identity scan clean on both repos.
Nothing committed. The day holds worklogs 292-349 and D466-D499.
Next: the 95 outstanding hardware measurements. The
remaining differential gap after this tick is strnlen,
strlcpy, strnstr, strpbrk and
strdup/strndup - all fit helpers that already
exist. The Annex K _s forms stay out: glibc does not
provide them, so there is nothing to diff against.