Commit Graph

1245 Commits

Author SHA1 Message Date
sylph-decoder
2d77570dab re: CORRECT the resample consequence -- the captures are crops, not resamples
The port pre-registered a test and ran it: against live-splash-publisher.png
a cropped render scores RMSE 558.1 (0.85%) and a scaled one 10118.8
(15.4%). Cropping is 18x better.

Confirmed here independently, from a different observable: the committed
captures are 1279x675, 1280x690 and 1252x754. VARYING heights. A fixed
presenter resample produces one size; crops of differently-sized windows
produce exactly that spread. And ui-render-tone-curve.md had already
recorded every capture aligning at dy=0 dx=0 with correlation 0.9466, which
a 0.9375 vertical scale cannot produce -- the evidence was in the corpus
before I wrote the claim.

So the captures are crops of a 1:1 surface, and every RMSE, glyph count and
surface mean against them is pixels to pixels with no filter to caveat. The
blanket "everything measured off a PNG carries the resample" is withdrawn.

Not refuted: the cvar reading. Canary does letterbox by default. What is
refuted is my inference that the corpus's capture path went through it.
Whether the presenter is bypassed, the window is 1:1, or the tool crops
before saving is open and unestablished.

The error is the one this session keeps paying for: I read a configuration
and inferred a consequence FOR THE DATA without testing it against the
data. The test costs one render and one RMSE and I had every capture needed
to run it.

Also downgrades my refutation of the corpus's oracle principle from
"survives with a qualifier" to "survives, and my qualifier was wrong",
kept rather than deleted because the failure is the instructive part.

Section 1 (gamma: VdGetCurrentDisplayGamma is kStub, the splash shader has
no pow/ramp/lookup) and the two-path distinction stand unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-02 16:14:28 +00:00
sylph-decoder
f1b9f69463 re: what Canary does between the guest's draw and a captured pixel
Answers question 3 of the 2026-09-02 play-test, from Canary's own source.

Gamma: Canary applies NONE. VdGetCurrentDisplayGamma is declared kStub and
only reports kernel_display_gamma_type (default 2, TV/BT.709) to the guest
so D3D can build a ramp. And the guest does not apply one either, for the
splash: its dumped pixel shader is four ALU ops -- tfetch2D, three muls, a
max -- with no pow, no ramp, no lookup.

Geometry: yes, the presenter resamples. present_letterbox defaults true and
present_safe_area_x/y default to 100, so the guest's 1280x720 is scaled to
fit the window and letterboxed with nothing cropped. That accounts for a
number the corpus has carried without explaining: captures measure the game
surface at 1279x675, and a single-pixel oracle placed at 1280x720
coordinates read the copyright line instead.

The structural consequence is the useful part. There are two measurement
paths and only one has Canary in it:

  pixels        guest draw -> EDRAM -> resolve -> front buffer ->
                presenter (scale + letterbox) -> X11 -> screenshot -> PNG
                => carries a resample
  vertex stream guest CPU writes a vertex buffer -> the draw logger reads
                guest memory directly
                => carries nothing

The per-frame alpha series is the second path, which is why it can be
stated as the game's values rather than as pixels we measured. Everything
measured off a PNG carries the resample: every RMSE against a capture,
every glyph count, every surface mean.

Refutation attempt on this corpus's own founding principle, "the oracle is
the real game running in Canary, captured": it SURVIVES but needs a
qualifier it has never carried. A PNG capture is the oracle plus a
resample. Harmless for ordering, counts, durations and change; a filter in
the path for anything pixel-exact. The vertex stream is the stronger oracle
and should be preferred where the question can be asked of it.

Not measured: the resample's actual filter. I read the cvars saying scaling
happens, not the kernel doing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-02 16:11:09 +00:00
sylph-decoder
3895f3d686 re: the interpolator is not found, and four routes are excluded
Question 1's behavioural half is answered elsewhere and measured. This is
the other half -- which function -- and it is not found. Recorded as a
bounded negative so the next attempt does not repeat four searches.

Excluded, all by exhaustive scans of the image:

  integer lerp shape (divw with mullw and subf within 10 instructions):
    28 sites image-wide, NONE in the UI region 0x82200000-0x823FFFFF
  mulli by 40, the keyframe record stride: 31 sites, NONE in the UI region
  addi rX,rX,40 in the UI region: 43 sites; the screen/bundle ones at
    0x823CCA6C and 0x823CCA94 disassemble to a 40-byte container COPY loop
    (CTR=10, ten-word copy), a vector reallocation, not an evaluator
  fmadd-family in the UI region: 811 sites over 142 pages -- too diffuse to
    select on, so float is neither excluded nor narrowed

The first two together say the evaluator does not compute its fraction with
integer multiply-and-divide and does not index records by multiplication.
With the measured steps landing on exact integers, the likeliest remaining
shape is a float lerp with a per-segment reciprocal, converted to a byte --
stated as a hypothesis, not a finding.

Names the route I would take next and did not have budget for: trace DOWN
from the quad emitter. sub_823C2AC0 has exactly 6 callers, four of them
sibling emitters, and sub_822380B0 reads its colour from this+0x04..0x10.
Whoever writes those fields is one step from the interpolator.

Reach stated: the two opcode negatives are exhaustive over the whole image
rather than sampled, but they do not exclude the interpolator living
outside the UI region, which is an assumption inherited from
ui-quad-class-foothold.md and not independently checked.

Not blocking the port: it needs whether to lerp and how, which is answered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-02 15:42:25 +00:00
sylph-decoder
e6f921d442 re: the game interpolates every frame, and its splash is ALSO mostly frozen
Answers question 1 of the 2026-09-02 play-test: interpolate or hold. It
interpolates, piecewise-linearly, evaluated once per present at one unit
per present.

palogo_sqex_eff gives 28 distinct alphas over 28 consecutive presents,
changing on 26 of 27 adjacent pairs. It is not one slope, and the disc says
why: that element declares 0:a=0 -> 15:a=255 -> 30:a=212 -> 45:a=0, three
segments with three gradients. Predicted -2.87 and -14.13 per unit against
measured modal steps of -3 (x6) and -14 (x9). The declared keyframes
predict the per-frame steps, which makes this a prediction rather than a
description.

So the port must lerp: hold-to-next-key would emit 3 states where the game
emits 28. And "eased" is the wrong description -- there is no easing
function, the envelope only looks eased because consecutive declared
segments have different gradients.

Alpha lives in the per-vertex k_8_8_8_8 colour, rewritten into a fresh
vertex buffer every frame. Not a PS constant (ps_c[n=0] on 1048/1048), not
a blend factor, not a texture swap.

🔴 And the game's splash is ALSO mostly frozen. Measured with the same
statistic the play-test used on the port: the game moves 21.2% and 27.8%
against the port's 16.4%, and its longest frozen run is 3.34 s against the
port's 3.20 s. The publisher declares 205 of 255 units -- 80% -- as a flat
hold at full opacity.

That refutes the play-test's second clause. "A fade does not hold one
picture for 3.20 s" -- this one does, for 3.34 s. Its first clause stands:
26 states for a 45-unit build-in is too few, the game gives 49 and 51.

The deficit is the state count, not the freeze: ~100 against 26. Acting on
the second clause would have sent the port to remove the one part of its
splash that is already right.

Not answered: which function does it. This is the behaviour; the
image-side half of question 1 is still open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-02 15:29:34 +00:00
sylph-decoder
cb3446d4f6 re: the three routes to 60 are one witness in three coats -- audit, and the capture sized
The port observed that the justification for 60 has changed three times
while the number never moved, and that three routes sharing an upstream
assumption are weaker than they look. Checked. They do share one.

Route B needs "the guest presents 60x/s", which comes from the vblank
histogram UNDER XENIA'S 60 HZ LIMITER. Route C needs "the vblank is 60 Hz",
which is that limiter's cvar directly. Route D is a wall-clock duration
that lands on 60 only because the vblank is 60 Hz and Canary roughly keeps
up. All three reduce to one fact: the display refreshes 60 times per second
on this emulator. I presented them as corroboration and that was wrong.

What is actually established, by manipulation rather than agreement:
units/second EQUALS the display refresh rate. Forcing 30 Hz gave 30.2
units/s; at 60 Hz it is 59.8/61.3. One factor changed, the output tracked
it.

It becomes "60 units/s" only via an external fact this corpus has not
measured -- that an Xbox 360 outputs 60 Hz. That is a hardware
specification and it is solid, but it belongs outside the measurement
rather than laundered into a third agreeing route.

The conditional form is more useful anyway: it says what to do on hardware
that is not 60 Hz, which is exactly why the port's time-based clock at a
fixed 60 units/s is the right construction rather than a coincidence. And
it keeps the port's `authored` classification correct -- nothing here
promotes it.

Also sizes the clock-origin capture properly. FRAMES=9000 ran to completion
still in the movie at 8999, so the cap really fired this time. Content
hashes show 3967 distinct decoded frames against ADV.wmv's declared 4131 --
96% complete -- at 1.79 presents per decoded frame, not the 2.00 I assumed,
which is why the estimate was short. Needs ~294 more presents to clear the
movie plus >=236 for the plate, so FRAMES=11000.

Free on the way past: 3967 decoded frames over 7091 presents at ~52
presents/host-s is 29.1 movie frames/s against the disc's declared 30.000,
a 3% match that ties the present rate to a disc fact rather than a clock.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-02 15:08:55 +00:00
sylph-decoder
c114b6239d re: the clock rate follows the VBLANK -- reconciling three pages that disagreed
h3 says 2 units per guest frame, units-per-second-measured says
time-integrated at 56.8 units/s, and my own page says 1 unit per present.
All three rest on real data and one mechanism explains all of them: the
clock advances one unit per VBLANK, and presents can be dropped without the
clock caring.

That accounts for the corpus's steps of +17/+34/+51 (1, 2, 3 vblanks
between two logged presents), for the same animation spanning 21 labels in
one capture and 33 in another -- which a strict per-present clock cannot
produce -- for h3's three plate steps of 23, and for my own 14 consecutive
steps of 17 in a run that dropped almost nothing.

My "1 unit per present" was nearly right and named the wrong clock. At
--framerate_limit=30 Xenia vblanks at 30 Hz and the guest presents ~30/s,
so presents and vblanks coincide and the test could not tell them apart. It
correctly refuted time-integration; it could not locate the tick.

Solid: the RATE follows the vblank rate. 255 declared units take 4.26/4.16 s
at a 60 Hz vblank and 8.45 s at 30 Hz -- 59.8/61.3 against 30.2 units/s. A
time-integrated clock predicts 4.25 s in both. So a console at 60 Hz gives
60 units/s, and the port's value stands on a third independent route.

Not solid: per-vblank vs per-present. My step samples are ~12 per run,
which cannot separate them, and I say so rather than reading 18% vs 0% of
two events as support. The load-bearing observation for the mechanism is
the corpus's 21-vs-33, not mine.

The discriminating experiment is named and not run: log Xenia's
D1MODE_V_COUNTER beside each present and check every step is exactly
17 x (vblanks elapsed).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-02 15:05:03 +00:00
sylph-decoder
b15effa587 re: the long capture ran and fell 32% short -- sized the next one from measurement
Attempted the capture this page named: FRAMES=8000, MAXDRAWS=400000, 700 s.
Reached frame 5954, still in the movie, zero UI quads after it.

Neither cap fired -- 24 213 draws of a 400 000 cap, 5954 frames of 8000 --
so it ended on the script's wall-clock timeout and nothing about the method
is wrong. It needs a longer window, not a different approach.

Sized from what this run measured rather than guessed: ~460 presents for
the splashes, a 137.714 s movie at 30.000 fps from the disc = 4131 decoded
frames, 2 presents per decoded frame measured, so ~8700 presents to clear
the movie. This reached 5954, about 68%, at ~22 presents/s armed. So
FRAMES=9000 and a timeout of at least 1200 s.

Also noting a saving I did not take: the h= content hash reads guest memory
for every sampled texture and the movie binds three planes per draw, but
the clock-origin measurement needs vertex alpha and not hashes. That field
is unconditional in canary-patches/0004; putting it behind its own cvar
would make this run materially faster.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 20:09:34 +00:00
sylph-decoder
b2dd779fdc method: a finding is only as portable as the tool that produced it
Records today's trap where the corpus keeps its traps, so it outlives the
session that paid for it.

Four Canary commits existed only inside the container and were cited the
way one cites a public reference -- "canary sylpheed-re d90d14e02, already
built" -- a line that reads as a complete recipe and is reachable from
nowhere but that box.

The failure is silent and delayed by design: the recipe looks complete so
nobody checks it, and it fails only for someone on a different machine long
after its author could say what the flag did. Nothing in the repository
disagrees with itself meanwhile, so no consistency check fires.

Notes why citation checkers do not cover the class -- they scan repository
paths, and an instrument in another container is not a path -- so they
catch the recoverable case (a stale pointer to a committed artefact) and
slide past the lost one.

And that the audit only works pointed at yourself: both agents ran it on
the same day, one finding a stale path to a committed artefact and the
other four artefacts that existed nowhere else, neither able to find the
other's from the far side of the wall.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 20:01:03 +00:00
sylph-decoder
696ed29664 re: the clock origin is blocked -- a 600-present boot capture has no title in it
The clock origin is the last surviving candidate for finding 3, and now
that the clock is known to advance 1 unit per present it is directly
measurable as a count: observe the plate at a known declared alpha, count
presents back to the title's first draw, and the difference is the title's
clock value at its own first draw.

No capture I have contains the title. Three captures agree on what the boot
does: publisher splash (219/228/244 presents), developer splash
(186/185/204), then the attract movie at 422/429/463. Two splashes, then
the movie. There is no title segment.

This re-frames an old blocker as expected rather than mysterious. The
corpus records "three runs, two locales, two launch paths, ~35 minutes of
emulator time, no interactive title" as unexplained. MISSION's own boot
order is splash -> intro video -> title, ADV.wmv is 137.7 s from the disc,
and 600 presents at ~50/s is ~12 s. The title is on the far side of a
two-minute movie; a capture that never reaches it is behaving correctly.

Two ways to settle it. Tapping A skips the movie but risks the save-data
probe that crashed the guest on 2026-08-18, per the capture script's own
header. Capturing through the whole movie needs ~6900 presents against the
600 used here, needs no pad input and cannot crash the guest, so that is
the one to run.

Not run this iteration, deliberately: I would rather pre-register the
origin prediction than start a long capture at the end of an iteration. The
prediction is stated on the page so the next run is not exploratory -- the
plate's a=17 must land 215 presents after the title's first draw if the
clock starts there, and any shortfall is the offset in units.

Reach: the negative is specific. Within the first ~460 presents of boot the
title does not draw. It says nothing about after the attract movie, which
is where the answer is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 19:44:33 +00:00
sylph-decoder
9e73990d1b re: the clock is FRAME-based, 1 unit per present -- my own test refuted my claim
Ran --framerate_limit=30 against predictions committed beforehand. Every
discriminating row went to the frame-based column:

                       frame-based   time-based (mine)   measured
  presents/s           ~27           ~27                 28.4
  modal alpha step     17 unchanged  34 doubled          17 UNCHANGED
  units/s              ~30 halved    ~60 unchanged       30.2 / 30.3
  publisher dwell      ~8.5 s        ~4.2 s              8.450 s DOUBLED
  developer dwell      ~7.0 s        ~3.5 s              6.923 s DOUBLED

Both controls passed first. The limiter took effect (28.4 presents/host-s
against 51-55, interval mass moving from one 60 Hz vblank to two, 422 of
468), and all 8/8 splash quad rects are identical so nothing but the frame
rate differs.

So the UI clock advances exactly 1 unit per presented frame. The step is 17
on every quad at 28.4, 51.4 and 54.8 presents/s, and 255*1/15 = 17 with the
declared T=15. units/second = presents/second, and seconds are not a
property of the game.

The port's 60 stands: the guest presents once per 60 Hz vblank
unconstrained, and 1 unit per present at 60/s is 60 units/s. Same answer as
my first position, finally with a tested mechanism instead of an inference.

This withdraws units-per-frame-is-not-a-constant.md in its central claim.
My "a time-based clock is immune to dropped frames so the dwell is stable"
was a real prediction and it failed -- the dwell doubled. The four-run
agreement I read as evidence was four runs at similar frame rates.

h3-units-per-frame-measured.md's +34 is now the anomaly rather than the
rule: 34 per label at 27.2 labels/s against 17 per present at 28.4
presents/s. Both cannot be presents. Not asserting it is wrong, only that
one of them must explain the disagreement.

Three of my four positions came from inference over a measured quantity;
this one came from changing an input and watching what moved. The
opportunistic comparison pointed exactly the wrong way because nothing
controlled what else differed between those captures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 19:39:30 +00:00
sylph-decoder
c30ac27a4a re: PRE-REGISTER the forced-frame-rate test of the time-based clock
The time-based-clock claim rests on two captures that happened to run at
different speeds. Nobody has changed the frame rate on purpose and watched.

--framerate_limit=30 halves Xenia's vblank ceiling. Predictions committed
before the run:

  quantity          frame-based        time-based (my claim)
  presents/s        ~27                ~27
  modal alpha step  17, unchanged      34, DOUBLED
  units/s           ~30, halved        ~60, unchanged
  splash dwells     ~8.5 / ~7.0 s      ~4.2 / ~3.5 s, unchanged

Accept time-based only if the step lands in 30-38 AND the dwell stays
within 10% of 4.2/3.5 s. If the step stays 17 and the dwell doubles, the
claim is refuted, the clock is frame-based, and the port's 60 is unsupported
again.

The control is the present rate itself, read FIRST: a doubled step also
appears if the limiter did nothing, so if presents/s does not fall to ~27
neither prediction is tested. Second control: the eight splash quad rects
must be unchanged, or something other than the frame rate differs between
runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 19:37:41 +00:00
sylph-decoder
c3718ed75e re: WITHDRAW 120 -- units per FRAME is not a constant, the clock is time-based, 60 is right
My fourth position today, and it returns to the port's.

Splash B's logo quads in this capture step +17 per present, 14 gap-free
steps, four quads agreeing. h3-units-per-frame-measured.md measured the
SAME elements at +34 and concluded 2 units per frame.

Both are right. With the declared T=15, dA/present = 255*(units/present)/T:

  h3 capture    27.2 presents/s   +34   2.0 units/present   54.4 units/s
  this capture  51.4 presents/s   +17   1.0 units/present   51.4 units/s

Units per present halved when the presentation rate doubled. Units per
second did not move. The UI clock advances by ELAPSED TIME, not by frame
count, and "2 units per frame" was a property of a capture that ran at
27 fps, never of the game.

So my 120 units/s is withdrawn: it was 2 units/present x 60 presents/s, and
the first factor is not a constant.

The movie result is re-explained rather than refuted. 2 presents per
decoded movie frame at 51.4 presents/s is a movie decoding at 25.7 fps -- a
30 fps movie at 86% under a slow emulator. The measurement held; the
inference assumed the movie decoded at 30/s in real time, which it does not
when the emulator is slow. Same shape as the first withdrawal.

60 is now positively supported rather than merely undefended. A time-based
clock is immune to dropped frames, which predicts the dwell in seconds is
stable across runs at different frame rates -- and it is, across four runs
whose present rates differ by 1.9x: 255/4.27 = 59.7 and 210/3.46 = 60.7,
both within 1% of 60.

h3-units-per-frame-measured.md's conclusion needs demoting: its measurement
stands, its law is a single-capture artefact, and it is the origin of the
constant this question has thrashed on.

Finding 3 is open again with no surviving named cause.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 19:35:24 +00:00
sylph-decoder
326e4ad2b0 re: the dwell corpus does not falsify 120 -- it measures the screen, not the animation
The port raised this as load-bearing and it is answered, without a speed
factor.

I expected to find the three cold boots slow and argue contamination. They
are not. My capture reproduces them: publisher 4.263 s against 4.297 /
4.604 / 4.370, developer 3.457 s against 3.508 / 3.503 / 3.366. Four runs
agree, so that defence is unavailable and I am not using it.

The flaw is in the comparison. 2.125 s is the publisher's declared
ANIMATION length; 4.3 s is how long the SCREEN is up, and the screen holds
after the timeline ends. Measured as counts in one capture: the publisher
is on screen for 219 presents and animates for about 128 of them. The
falsifier would have found a contradiction at any units-per-second.

The decisive number needs no speed factor: 51.4 presents per host-second on
the publisher splash, 53.8 on the developer. Xenia's limiter marks vblank
at 60 Hz and caps presents there. A 30 fps guest presents every second
vblank, at most 30 per second, and a slow emulator can only make intervals
longer. 51.4 > 30, so a 30 fps guest cannot produce this. A 60 fps guest
can: 51.4 is 86% of 60, matching the vblank histogram's 14% drops.

So 60 units/s is refuted by a count against a hard limit rather than by a
duration against a fitted factor -- the argument the 2.13 s reconciliation
could not make.

The dwell corpus stands in its own terms. Those seconds are the screen's
dwell on this emulator, reproducible, and were never a statement about
units per second.

Recording what I got wrong on the way: I first segmented the atlas eras as
"both splashes then the title" and briefly had a 2x discrepancy that would
have let me declare the dwell corpus contaminated. The segmentation was
wrong, and the durations matching the corpus to 1% is what exposed it. A
convenient answer from an unchecked segmentation is the same trap as a
clean answer from an unguarded assumption.

Reach unchanged: still one boot for the hash ratio.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 19:30:02 +00:00
sylph-decoder
55777bafb6 re: the guest presents at 60 fps -- 120 units/s, and the port's plate IS ~2 s late
Answered against the pre-registration committed before the capture, with a
content hash added to the draw logger.

The buffer rotates three times faster than the content changes: 177
presents carry 3 base addresses but only 102 distinct content hashes, and
the modal run is exactly 2 presents per decoded frame. Consecutive presents
with changed content = 0.5739, inside the pre-registered R band of
0.40-0.60.

That is precisely the failure that invalidated my first answer this
morning. The base address changed every present, which I measured and read
as one decode per present. The content did not.

ADV.wmv is authored at 30.000 fps, so 2 presents per movie frame is 60
presents/s, and 2 units/present x 60 = 120 units/s.

Both controls pass, and they are the ones the withdrawn version lacked --
its two guards tested how I READ the buffer, neither tested whether a
buffer change meant a decode.

Control 1, a static texture must hash constant: the splash atlas changes
once, at an era boundary, and never within an era -- 1 change in 403
samples. Noting that I first wrote this control as "must be constant" and
it read FAIL; stated that way it was wrong, because a re-upload is real
content change and the control has to separate temporal from alternating.
A control that is too strong gets waved away, which is its own failure.

Control 2, the movie luma hash must not be constant: 102 distinct.

Consequence: the plate's t=236 is 1.97 s, not 3.93 s, and the port shows it
~1.96 s late. That is play-test finding 3, and "about two seconds" is the
size the human reported. Every declared duration in seconds across the
corpus is half what we have been quoting; unit counts are untouched.

The 2.13 s third route is explained by emulator speed, and that explanation
is labelled POST-HOC and is not offered as support -- fitting a speed
factor to close a gap is what this corpus keeps losing claims to.

Reach: one boot. This number has moved twice today and a second independent
boot should come before the port rewrites a timeline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 19:23:13 +00:00
sylph-decoder
0b98460772 re: PRE-REGISTER the decode-vs-rotate test, and add a content hash to the logger
The experiment guest-frame-rate-WITHDRAWN.md named. A triple buffer
rotating once per present visits the same three base addresses whether or
not anything was decoded into them, so base identity cannot separate
"decode per present" (30 fps guest, 60 units/s) from "rotate per present"
(60 fps guest, 120 units/s). A content hash can.

command_processor.cc now emits h=<FNV-1a over 4096 sampled bytes> beside
every sampled texture, omitted rather than faked when the address does not
translate, so a missing hash cannot read as a matching one. Incremental
build, one translation unit.

Predictions committed before the capture: consecutive presents with changed
luma content is ~1.00 under decode-per-present and ~0.50 under
rotate-per-present, with acceptance bands and "neither" reported rather
than rounded.

The control is the part I got wrong last time. A STATIC texture must hash
constant: the splash sprite page 0x11A50000 is a fixed atlas, and if its
hash varies between presents then reading guest memory at draw time races
the decoder and nothing in the log may be read. That fails loudly in the
direction which would otherwise manufacture the answer I previously
reached -- the property my last control lacked. A second control in the
other direction requires the movie luma hash NOT to be constant, which
would mean hashing the wrong bytes and would manufacture the opposite
answer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 19:21:04 +00:00
sylph-decoder
69b233bd50 re: the port's 12-unit bracket does not exclude 120 -- its low end is the sampler's floor
The port kept 60 on a stated constraint: the transition quad is declared
black for 12 units, measured at 0.14-0.30 s, giving 40-86 units/s, so 60 is
in and 120 is out. Attempted to refute it, and it does not hold.

Two reasons, either sufficient.

The low end is the instrument's floor. title-plate-delay-measured.md says
of that number, in its own words, "at a sampling resolution (0.125 s) that
cannot do better". 0.14 s IS one sample. At 120 units/s, 12 units is
0.100 s, which a 0.125 s sampler reports as roughly 0.14 s -- exactly the
value read as excluding it. The 86 units/s upper limit is an artefact of
dividing by a floored duration.

And a wall-clock duration off Canary is not independent of the unknown.
Apparent units/s = true units/s x speed factor, and the speed factor is
precisely what makes the three routes disagree. The bracket says "if Canary
ran at real time, 40-86" and whether it did is the question.

What survives is the declared side, and it is a disc fact worth keeping:
the transition opens over 12 units, independently confirmed as 6 frames by
screen-transitions.md's 255/6-per-frame ramp. That leg has no wall clock in
it and is real evidence -- for units per FRAME, which was never in dispute.

This does not argue for 120. Nothing supports 120 either. It removes a
reason for excluding it, and my position is unchanged: the value is
authored until the content-hash experiment runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 19:15:34 +00:00
sylph-decoder
f64fb8f2a4 re: WITHDRAW "the guest presents at 30 fps" -- the clean result WAS the failure mode
Published this morning, withdrawn the same day. The measurement was real;
the inference was not established.

My own pre-registration named three ways the instrument could lie and
guarded two. The third -- "the guest may be frame-locked to its own
presentation rather than to the movie clock" -- is the one that occurred,
and a perfect 1.0000 is exactly what it produces: a triple buffer rotating
once per present gives run-length 1 at ANY frame rate. So the measurement
cannot separate a 30 fps guest decoding one movie frame per present from a
60 fps guest rotating a buffer per present.

The methodological error is the part worth keeping. I wrote the guard down,
saw a result so clean it had no tail, and read the cleanness as strength. A
clean result on an instrument whose key assumption is unguarded is not
confirmation -- the cleanness may be the failure mode's own signature. Both
guards I did build tested how the buffer was READ; neither tested whether a
buffer change means a decode.

What surfaced it: the draw log carries a per-frame gtick marker I had not
noticed. It is host time rescaled (Clock::QueryGuestTickCount is
host_tick_count * guest_tick_ratio, scalar 1.0), so it is a wall clock and
cannot be read as a rate -- but its SHAPE has no phase. Xenia locks vblank
to 60 Hz, and the present interval is ONE vblank 71.7% of the time and two
24.6%. A guest hard-locked to 30 fps presents every second vblank and would
put the mass at 2.

I am NOT replacing it with 120. Three routes now disagree: the withdrawn
movie cadence says 60, the vblank cadence says ~120, and
title-plate-delay's 120 declared units in 2.13 s measured twice to 6 ms
says ~56. Two of the three must be wrong and I do not know which.
Publishing 120 would repeat the mistake this commit withdraws.

The port is told to keep 60 for now, and told plainly that it is an
authored value rather than a measured one.

The settling experiment is named: hash the movie luma plane's CONTENTS per
present rather than its base address, which separates "the buffer rotated"
from "a frame was decoded".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 19:11:05 +00:00
sylph-decoder
728683d111 formats: teach the reference renderer the additive blend, and re-open 8 claims
The blend bit has been decoded against RB_BLENDCONTROL0 since 2026-08-31,
but ui_layout::blit could not draw it, and said so in a comment citing a
refutation that is <render-vs-capture> -- this renderer disagreeing with
itself while it had a stale keyframe association, no leaf geometry and no
rotation.

The consequence the port raised: verify-screen compares two renderers, so a
renderer that structurally cannot express a declared field makes the check
incapable on every screen that uses it -- 12 of 16 -- and the tolerance
silently excuses all of them. A quiet check is worse than a failing one.

Both equations come off the game's own pixel shader, which premultiplies
(oC0 = rgb*A, A), so only the blend register differs: 0x07010701 gives
rgb*A + dst*(1-A), 0x01010101 gives rgb*A + dst. Additive therefore
saturates rather than wrapping, and a transparent or black source is the
identity -- neither is a choice.

No plumbing needed: t8ad::parse already stores +0x04 as T8adImage::flags.

Four controls, pinned against arithmetic per the rotation precedent. The
fourth is the only one that can fail for the right reason: the first three
pass just as well if blit ignores the flag and draws everything additive,
so the discriminator flips only the blend on one sprite and requires two
different answers, each equal to its own equation. That is the same failure
class as the port's non-inverting latch check and my own backward scan that
resolved every guard to "internal".

120 passed, 0 failed on the full lib suite.

67 sprites over 14 screens were being drawn with the wrong blend, including
10 of 18 on the title and ptbtn00f, the PRESS (A) plate's highlight.

R1: tools/stale-instrument render-vs-capture lists 8 claims that died to
this instrument, including both legs of the rest() pair and "the plate-free
title capture may be too early to be settled", which sits on play-test
finding 3. None is re-derived here; this only records that the instrument
no longer exists in that form.

Also corroborates the port's H5: pgloading_loop5 is an ELEMENT resolving to
sprite pgloading_ring.t32, which is additive. I could not find loop5 as a
sprite in any pak and nearly reported a false contradiction from the
element/sprite name split.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 19:03:53 +00:00
sylph-decoder
864ab45a1e re: the blend bit was challenged on pteff10 and SURVIVES; finding 3 loses its second candidate
The port isolated main_menu's 10.88 -> 13.02 regression to pteff10 and
asked whether the per-draw log shows it alpha-over, which would be a
counter-example to ui-blend-mode-decoded.md.

It does not. blend-bit-vs-oracle.txt has pteff10 ADDITIVE on entry 5 (main
menu) AND entry 6 (extras), both labels read out of the guest command
stream, and HANDOFF states it from three independent menu sessions, every
frame.

The premise came from a stale coverage table of mine that the same HANDOFF
entry had already corrected in place. Recording that: a correction which
leaves the wrong table visible upstream of it has not landed.

And the regression was predicted on this exact element before adoption --
HANDOFF flagged pteff10 as the one row the port's renderer would not
corroborate, because for a dim wholly-semi-transparent glow (max alpha 130)
over a dark background additive and alpha-over nearly coincide. A renderer
metric getting worse while the render gets more correct is the standing
rule with a number on it.

Separately, the port answered my finding-3 candidate: a frozen sweep of the
plate region rises monotonically across the declared 214->236, so the port
fades rather than pops and its t=236 is a completion. That kills the onset
branch.

Two of the play-test's four named candidates for finding 3 are now dead --
units/s (measured at 60) and the ramp. Finding 3 has no surviving named
cause. The clock origin is the one to take next, because every measurement
so far is a DIFFERENCE between two events and a difference cannot detect a
common offset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 18:55:50 +00:00
sylph-decoder
5b04661248 re: the guest presents at 30 fps -- 60 units/s, and the port was right
Answered against the pre-registration committed before the capture.

Units per second was the last open number on the PRESS (A) plate, and both
prior measurements of it were wall-clock readings off an emulator that runs
the guest slow by an unknown factor. They disagreed by 2.9x, because a 30 Hz
guest at full speed and a 60 Hz guest at half speed look identical on a wall
clock.

The ruler here is not a clock. ADV.wmv declares 30.0000 fps in its own ASF
header, so a decoded movie frame is a tick the emulator's speed cannot
stretch. Presented frames per decoded movie frame is guest_fps/30 with no
wall clock in the chain.

  predicted  H_A 30 fps ->  60 units/s -> 1.0
             H_B 60 fps -> 120 units/s -> 2.0
  measured                                1.0000

Both pre-registered guards pass. Guard 2: a perfect repeating 3-buffer
cycle, 52 uses each (exactly 156/3), 2 chroma planes per luma on 156 of 156.
Guard 1: run lengths are 156 runs ALL of length 1 -- no smear, so the
dropped-movie-frame bias that would have pushed the answer toward 120 is
measurably absent rather than argued away.

So H_A. The port keeps its 60 and changes nothing.

REFUTES the live H3 hypothesis that 120 units/s explains the play-test's
late plate. That hypothesis was well-formed and attractive precisely because
it would have explained the complaint, which is why it needed a ruler that
is not a clock.

Which means finding 3 still has no cause. The strongest remaining candidate
is decoded rather than speculative: the plate's declared onset is t=214, not
t=236 -- a 22-unit fade, matching the T=22 the oracle confirmed by measuring
+23 alpha per presented frame on that element.

The pre-registered control could NOT be run: this logger build emits vb=
addresses, not vertex contents, so there was no alpha to check +34 against.
A weaker control is substituted and labelled -- the splash shader/blend
census, which validates the log's structure (what this measurement uses) and
not alpha extraction (which it does not).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 18:49:40 +00:00
sylph-decoder
06c69b3143 re: PRE-REGISTER the guest frame-rate test -- the movie is the ruler
Units per second is the one number left on the PRESS (A) plate, and both
existing measurements of it are wall-clock readings off an emulator that
runs the guest slower than real time by an unknown factor. They disagree
by 2.9x, which is what that method is worth here.

ADV.wmv declares 30.0000 fps exactly in its own ASF header (stream #2,
avgTimePerFrame = 333333 x100ns). That is a disc fact, so a decoded movie
frame is a tick of a clock Canary's speed cannot stretch.

The measurement is a ratio of counts with no wall clock in it: how many
consecutive swap labels bind the same movie luma texture base.

  H_A  guest 30 fps ->  60 units/s -> 1.0 labels per movie frame
  H_B  guest 60 fps -> 120 units/s -> 2.0 labels per movie frame

Committed BEFORE the capture so the number cannot be chosen afterwards,
with acceptance bands, the three ways the instrument could lie, a guard
for two of them, and a named control (the splashes' established +34/frame
must reproduce in the same log, or the log is not comparable).

Noting the trap I nearly fell into: stream #1's avgTimePerFrame is a
packet rate and reads as 3.05 fps. Reading the wrong stream is the obvious
way to get this wrong, so it is written down.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 18:43:13 +00:00
sylph-decoder
1e388d0176 re: the pad word is REMAPPED -- my own bit table was mislabelled throughout
input-pad-read-path.md says of the word the C_PAD_DECODER reads: "There is
no shift and no remap on the way in -- the bit positions are XINPUT's own."
That is wrong. sub_8220D500 rebuilds the word out of XINPUT_GAMEPAD before
anything else sees it, into the game's own numbering.

  bits  0-3   A B X Y
  bits  4-7   left stick  UP DOWN LEFT RIGHT   (+/-20000 of 32767)
  bits  8-11  right stick UP DOWN LEFT RIGHT
  bits 12-15  D-pad       UP DOWN LEFT RIGHT
  bits 16-17  START, BACK
  bits 18-19  LB, RB
  bits 20-21  LT, RT  -- digital, threshold >220 of 255
  bits 22-23  L3, R3

Extracted mechanically from the image, no row typed by hand. The control is
the shape of the result: the 24 assignments land on bits 0..23, each used
exactly once, none repeated. A misdecode does not produce a bijection over a
contiguous range, and coincidence does not put the stick and D-pad
directions in the same order in two aligned nibbles.

So every mask in that page's tables names the wrong button. The 0xE000 x18
site, read there as "B | X | Y", is "D-pad DOWN | LEFT | RIGHT" -- eighteen
sites testing a menu cursor, which is what 18 sites should be.

And its headline negative is REFUTED: "LB and RB are not menu inputs" is
false. They are bound at config fields this+0x70 and this+0x84, LT/RT at
+0x74/+0x80. The negative was searched for 0x0100/0x0200 -- LB and RB in
XINPUT's numbering -- in a word where they live at 0x40000/0x80000. Right
function, right buttons, wrong bit positions, so it could only come back
empty. A negative is only as good as the numbering it was searched in.

Also decodes the ring record: +12 HELD, +16 PRESSED, +20 RELEASED, +28/+32
raw trigger bytes. Edge and level are one struct four bytes apart, which
displaces that page's guess that press-vs-hold was split between GetState
and the XamInputGetKeystrokeEx queue.

The superset claim in that page survives and is untouched: sub_82457038
really does compare every XINPUT_GAMEPAD field, and it really is
XINPUT-layout. This page depends on it.

Not decoded: which output bit means which ACTION, and per-screen sets. 5 of
18 output-bit sites did not resolve to a pad guard, so the output map is a
lower bound -- in particular "START is not tested" is NOT claimed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 18:33:42 +00:00
sylph-decoder
1cd5b8b1cb re: the splash blur is a TEXTURE -- palogo_*_eff is a baked 10-px glow
Play-test finding 4, answered as a mechanism and from the disc, so it
generalises instead of describing one boot.

ui-splash-draw-pass.md excluded a post-process from GPU state and closed
with "that softness is in the texture or in which quads are drawn, not in
a pass", leaving the two unseparated. It is both, and they are one fact:
each logo ships a second texture that IS the blur -- the same artwork
outset by exactly 10 px per side, concentric to <=1.5 px, drawn as its
own alpha-over quad.

Three results, each with its control:

* The capture's eight anonymous quads are NAMED from the disc. Predicting
  each NDC rect from declared position + decoded sprite size matches all
  eight bijectively; every match <=0.0061, every runner-up >=0.0272, a
  4.5-8.9x margin. That margin is the control -- eight similar boxes
  would match anything.

* REFUTES splash-quad-timeline.txt's "the same three rects scaled
  slightly larger" (my own earlier wording). The x and y scale factors
  differ by up to 0.28; a uniform scale cannot do that, a fixed 10-px
  border can. The conclusion it supported (draw all six quads) stands;
  the model was wrong, and the wrong model tells a port to scale a
  sprite.

* The T8aD blend bit tested OUT of sample on entries 10/11, which were
  not in its 35-row fit and are the screens under complaint.
  Pre-registered additive=false for all eight against 0 additive draws in
  1048; held 8/8, with the control still reporting 9 additive on entry 6.

Also resolves a REFUTED.md 🟡 <our-reader> in the reader's favour: the
prediction is ours and the target is the oracle, so the agreement is
evidence about the reader rather than a claim resting on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 18:24:14 +00:00
sylph-decoder
46b05d9655 handoff: withdraw the splash rate, and strike the section that carried it
Told the port plainly: their arithmetic was right, the 35-40 is withdrawn,
keep 60 for every screen, and do not average or split anything. Struck the
previous section's heading in place rather than deleting it.

Carried the instrument lesson across, because it is worth more than the
number: the guest timebase does not remove the pacing artefact, since the
game's animation clock is frame-coupled rather than being the guest
timebase. My control verified capability -- does this clock track real time
-- when the question was configuration: is the quantity I divide by coupled
to the frame rate.

Also told them what this leaves: both halves of the human's finding 4 that
were mine are answered and neither points at their export, so their own
unbound-A observation is now the strongest candidate and it is theirs.
2026-09-01 17:30:24 +00:00
sylph-decoder
1e7343e7ac re: WITHDRAW 'the unit rate is per-GamePart' -- it was the emulator's frame rate
The Port's arithmetic was right: a 160-unit sub-interval cannot outlast the
210-unit group containing it, and my hold (4.514 s) outlasted three cold-boot
measurements of the whole group (3.37/3.50/3.51 s).

My own capture says why, once asked the right question. The three numbers I
quoted came from three regions of ONE run, and I never asked how fast the
emulator was going in each:

  splash B (the hold)      3.39 labels/guest-second  -> I reported 35.4
  splash A (publisher)    15.33                      -> I reported 40.0
  the title (the plate)   23.20                      -> I reported 56.8

Monotonic. The 'per-GamePart rate' is the pacing of the region it was
measured in. Splash B was captured at 3.4 fps, an eighth of the title
region.

And the corpus already had this. boot-splash-dwells-are-declared.md says in
its own words that the wall-clock dwell is an emulator-pacing artefact that
varies run to run, with a no-input boot 15-20% long on the same declared
timeline. I re-derived a documented artefact as a discovery and drew a false
conclusion from it.

The instrument lesson, which is the part worth keeping: I believed the guest
timebase removed the artefact. It does not. The game's animation clock is not
the guest timebase -- it is frame-coupled -- so a slow run advances less
animation per guest second and no clock can see that from inside. My control
asked 'does this timebase track real time', which is capability. The question
that mattered was 'is the quantity I divide by coupled to the frame rate',
and nothing I ran asked it. That is PROTOCOL's own warning, which I quoted at
the other agent two iterations ago and then walked into.

Restated: the clock is neither purely frame-counted (21 vs 33 labels for one
animation) nor purely time-integrated (rate scales with frame rate).
Consistent with a clamped per-frame delta, untested, and now the real open
question.

Consequence: no rate measured on this emulator is the console's; all are
biased low. Best estimate stays the declared timeline against the fastest
runs -- 60 units/s, 1.1% on the developer splash. The port keeps 60 for every
screen and nothing needs averaging.

Untouched: section 1 of splash-declared-vs-captured.md. It compares a disc
table against vertex alphas at integer t and never divides by a duration, so
the pacing artefact cannot reach it.
2026-09-01 17:29:52 +00:00
sylph-decoder
12952a87de handoff: one units-per-second cannot be right, the splash timeline is confirmed, and H5 refuted
Three sections, the correction first.

The rate is per-GamePart: title ~57, splash ~35-40. I gave them 56.8 an hour
ago and said the reach was the title; the splashes now have their own number
with T read off the disc. The hold is what makes it safe to hand over -- 160
declared units in 4.514 guest seconds, with no T, no alpha slope and no
interpolation in the arithmetic, agreeing with the same screen's ramp which
shares none of its algebra. Told them a splash played at 60 runs 1.5-1.7x
too fast, which is a TIMELINE cause for the complaint we had both filed
under blur. Classified measured, not decoded, and told them not to extend
either number to any other screen.

The R1-re-opened 'declared timeline reproduces the captured splash' settles
in favour of the timeline: 39/50 exact under truncation, worst error one
alpha level in 255, nothing off by more than one. So their export's splash
keyframes are right and the rate was the defect.

Refutation attempt on their H5, recorded whether it survived or not: it does
not. build_12 and build_15 have identical declaration bodies, so identical
statistics is one fact rather than two coincidences -- as do 0/1, 2/3 and
11/14, while 4/7, 5/8, 6/9 and 10/13 genuinely differ. Also recorded the trap
I hit first: the dump header carries the build number, so the compared text
contained the label distinguishing the subjects and every pair read as
different. A self-comparison control caught it.
2026-09-01 17:20:59 +00:00
sylph-decoder
43a57379f1 re: the declared timeline DOES reproduce the splash -- and the unit rate is per-GamePart
Two results, one of which corrects me.

1. The R1-re-opened 'declared keyframe timeline reproduces the captured
splash' resolves in FAVOUR of the declared timeline. Instrument is a disc
keyframe table against the guest's vertex stream -- no renderer in the chain,
which is what the 'our-reader' tag demanded. Calibration-free test: is each
captured alpha an exact member of the declared piecewise-linear value set at
integer t? 39/50 exact under truncation, 30/50 under rounding, and the worst
error under either rule is ONE alpha level in 255. All 11 non-exact samples
are low by exactly 1 and all are on falling segments. The old refutation
rested on 'still at a=255 nine frames after its declared a=32' -- under the
fixed layout a=32 is at t=206, four units from the end of a 210-unit
timeline. That was the off-by-one association.

2. The unit->seconds rate is PER-GAMEPART, which corrects what I told the
port an hour ago. Title ~57 units/guest-second; splash ~35-40. The splash
figure is confirmed two ways that share no algebra: a 15-unit ramp (T read
off the disc, not borrowed) and a 160-unit HOLD, which is a declared
duration with no T, no alpha slope and no interpolation in it.

And my 'the borrowed T=15 does not apply' was wrong in the other direction:
the disc says T=15 plainly. What failed was the premise of a single global
rate, not the T. Noted at the foot of that page rather than by editing it.

The trap worth recording: all four elements give 650-679 alpha/s, agreeing
to 2%, which reads exactly like one clock. It is a coincidence -- T differs
22 vs 15 and the rates differ 57 vs 37, and the ratios nearly cancel. A
quantity that looks constant across screens is not evidence of one clock
when the thing that would vary is inside it. The hold breaks the tie.

For the port: a single keyframe_units_per_second cannot be right, and a
splash played at 60 runs 1.5-1.7x too fast -- shorter, sharper fades than
the game's, which is the direction the play-test reported.
2026-09-01 17:19:59 +00:00
sylph-decoder
3d270dcd86 re: fix the stale headline on the units-per-second page
The title still said 'not yet a number' after the second half resolved it to
56.8. Kept the failed first half in place and in order -- the borrowed T is
the lesson -- but a reader must not stop at the header.
2026-09-01 17:11:28 +00:00
sylph-decoder
79403b5737 re: the rate is 56.8 units per guest second -- both predictions hold, control passes
The capture reached the title AFTER the harness stopped classifying, so the
plate's ramp was in the log with tick stamps after all.

ptbtn00: alpha 11->231 over 334.4 guest ms = 657.9 alpha/s. With its
independently attested T=22 that is 56.8 units per guest second, inside the
pre-registered 55-65 band. The final step of every ramp is excluded because
it clamps at 255 and reports more elapsed time than it consumed -- including
it drags the plate to 633.0 alpha/s, a 4% error entirely inside the clamp.

Control passes at 1.15%: ptcopyright gives 650.4 alpha/s, and at one shared
clock that makes its own segment T=22.25. Two elements agreeing on a rate
AND independently landing on a round declared length is stronger than either.

Why my earlier 29.9 was wrong, and it is the failure the page predicted: it
used T=15 borrowed from a row that is about A element with a 15-unit fade,
generalised to splash B's quads, which is not what that row says. At 56.8 the
splash elements' implied T is 23-34, none of them 15. And the step quantum
does not rescue 15: splash steps are multiples of 17 = 255/15, which is what
made 15 look confirmed, but at T=28.5 a step of 17 is simply TWO units of
8.9. A quantum fixes T only if you already know the step is one unit.

60 is NOT refuted -- 5.6% away against ~5% quantisation resolution -- so the
port keeps it. But this DOES eliminate the unit constant as a cause of a late
plate: at 56.8 units/s t=236 lands at 4.15 s against the port's 3.93 s, so
the port is fractionally early.
2026-09-01 17:11:19 +00:00
sylph-decoder
8b05b6b692 re: the animation clock is TIME-INTEGRATED -- one prediction held, one failed
Reported against the pre-registration, failure first.

HELD, and by a cleaner argument than the regression: the clock is NOT
frame-counted. The same animation takes 21 frame labels in one capture and
33 in another, and splash A's logo steps +136,+34 in one and
+17,+51,+34,+34,+17,+17 in the other. A fixed per-frame increment cannot do
that. Steps are always integer multiples of 17 (255/15 = one unit), so the
clock advances in whole units at a rate set by how long the frame took.

That retires '2 units per submitted frame' as a MECHANISM -- including my own
page from two hours ago. The measurement stands; 2 was that run's frame
pacing. units = 2 x frames computes an emulator artefact. Recorded as a
proposal against the register, not enacted, and noted at the head of the H3
page rather than by editing it.

FAILED: predicted 60 units per guest second, accept 55-65. Measured median
29.9 over six elapsed-ratio estimates (25.2-36.6). Prediction 3 said 30 was
excluded and 30 is what came out.

Why the rate is not a number yet: units/s = (d_alpha/dt) * T/255, and T is
the load-bearing term. d_alpha/dt is measured cleanly six ways. T=15 for
these elements comes from a corpus row whose usual derivation is circular
with the thing just retired -- a step of 34/frame implies T=15 only GIVEN 2
units/frame. T=30 would give ~60. No amount of re-measuring alpha settles it.

Settles with ptbtn00, whose T=22 is attested by two independent readers with
no clock anywhere in the chain. That capture did not reach the title in 531 s
of attract loop against 243 s in the previous run -- the variable attract
loop capture-harness-status.md already documents at up to 604 s. Instrument
built and control-passed; one run is missing.

Instrument: guest timebase, 50 MHz per emulator.cc:225, scalar 1.0 per
clock.cc:37, so no host wall clock enters any number. Control: 123.24 guest
seconds across a capture running ~118 wall seconds.

Told the port to change NOTHING today: ~30 rests on an unverified T, and
retiring 2 x frames does not by itself supply a replacement.
2026-09-01 17:04:23 +00:00
sylph-decoder
5436ba2039 re: the game blends in the ENCODED space -- no gamma render target, anywhere
The Port asks whether the blend is evaluated on sRGB-encoded values or
linearised and re-encoded. It is answered by one register field I already
log, and the answer is the same on every draw of two full captures.

RB_COLOR_INFO.color_format is k_8_8_8_8 (0) on 2402/2402 splash draws and
33779/33791 draws of the boot-through-title capture; the other 12 are
k_32_FLOAT and k_16_16_FLOAT and are not colour passes. k_8_8_8_8_GAMMA (1)
appears ZERO times, and color_exp_bias is 0 everywhere so nothing stands in
for a gamma either.

The mechanism is Canary's own source, not our inference: k_8_8_8_8_GAMMA is
the ONLY colour format around which a piecewise-linear gamma<->linear
conversion is applied (spirv_shader_translator.h:510 PWLGammaToLinear /
LinearToPWLGamma, render_target_cache.h:720, dxbc_shader_translator_om.cc).
With k_8_8_8_8 there is none, so the blender operates on the stored values
as they are.

So a renderer that linearises before blending and re-encodes after is doing
a different operation -- and the difference is gamma-shaped and exactly zero
on unblended pixels, which is the divergence signature the port reports.

Also renamed my units-per-second pre-registration off the 'h4' prefix: the
Port's BLOCKED.md numbers this blend-space question H4 and two different H4s
in one corpus is how a citation goes wrong.
2026-09-01 17:01:11 +00:00
sylph-decoder
62c4d7fe02 re: pre-register H4 -- is the animation clock frame-counted or time-integrated?
H3 pinned 2 units per guest FRAME. The port needs units per SECOND, and that
decomposition is only valid if the clock counts frames. If it integrates
elapsed time then units/second is the invariant and units/frame is an
artefact of whatever rate the host managed.

Evidence it is time-integrated is already in hand and is the caveat H3 had
to quote: across empty label 5376 the plate's alpha moved +82 where three
adjacent labels each moved +23. 82/23 = 3.57, not an integer number of
ticks, and a fixed per-frame increment cannot produce a fractional multiple
of itself.

Instrument: the draw logger now stamps each frame boundary with
Clock::QueryGuestTickCount() and guest_tick_frequency() -- the GUEST's
timebase, 50 MHz per emulator.cc:225, scalar 1.0 per clock.cc:37. No host
wall clock enters the calculation.

Discriminator, within one run: regress alpha step against the label's guest
duration. Flat => frame-counted. Proportional => time-integrated, and the
slope is the answer.

Predicted before the run: time-integrated with r>0.9, at 60 units per guest
second, accept 55-65, 120 and 30 excluded. Stated honestly as a CONSISTENCY
prediction -- it is what the previous capture's run average implies -- not a
blind one.

Control: the same regression on ptcopyright's ramp, a different element with
a different declared segment. Two elements must agree under the
time-integrated model and need not otherwise.
2026-09-01 16:54:13 +00:00
sylph-decoder
a482d9a139 handoff+index: H3 answered, the settle anchor, and the pad's full field set
Three rows the port is blocked on:

H3 -- 2 units per guest frame, measured on the plate's own ramp against a
pre-registration. Their 5 excluded by >2x, and the reason named: an alpha
step is not a clock rate without the element's declared T, and my own
splash-quad-timeline.txt published alpha against frame with no T column.
That file now warns at its head.

The title settle anchor is t~160, not t=118 -- it is ptcopyright, the last
build-in element and the only glyph one. Said plainly that this is the
reading under which their clock:'shared' collapses, and that a consequence
is not a counter-argument.

Told them NOT to change their 60 units/s yet: units/second is untouched by
this and two of my own captures disagree ~2.9x on frames->seconds.

The pad: sub_82457038 reads every field of XINPUT_GAMEPAD, 14/14 loads
verified against the image, plus a second keystroke-queue path. Flagged as
the superset the game can SEE, not the per-screen set, so it is used to
check a binding table and not to write one.
2026-09-01 16:50:52 +00:00
sylph-decoder
d071898cde re: H3 answered -- 2 units per guest frame, and the settle anchor is t~160
The Port asked which of two of my measurements to believe, 55 units/s vs
~150, a factor of 2.7 off one game. Measured on the plate's own declared
ramp, against a pre-registration committed first.

PREDICTED 11 frames at 2 units/frame, 4.4 at the Port's inferred 5, +/-1.
MEASURED 10 labels, and three consecutive gap-free steps of EXACTLY 23,
which is 255*2/22 on the nose. 5 units/frame is excluded by >2x.

Why the splash read as 5: an alpha step is not a clock rate. For a linear
segment d(alpha)/frame = 255*(units/frame)/T. Splash B's quads step 34 with
a declared T=15; the plate steps 23 with a declared T=22; ONE clock, two
steps 1.5x apart. And the Port's intervals start at each quad's first
submission, which on splash A is already alpha=85 -- not the element's t
where alpha=0, and biased by a different amount per element because T
differs. My own splash-quad-timeline.txt published alpha against frame with
no T beside it, which is the column that makes the conversion possible; it
now carries the warning.

The other half: the 'title settled' anchor is ptcopyright reaching full
alpha -- the last build-in element and the ONLY glyph one, which is what a
glyph counter settling means. Calibrated on the plate's own ramp it lands at
t~168 (t~176 at a flat 2.0/label). The Port's candidates are 118 and 160, 42
units apart: this is 8-16 units from 160 and 50-58 from 118. It is 160.

Instrument fact that bounds all of it: labels with zero draws exist, ~1 in 5,
and the clock does NOT advance a fixed amount across them -- across label
5376 the plate moved +82 where three adjacent labels each moved +23. So an
empty label is a real advance, not a logger artefact, and spans that cross
one are approximate. The conclusion rests on the gap-free steps.

Also recorded: the sweep leaves never settle. They translate monotonically
through every label examined and are still moving when the plate arrives, so
'settled' can only mean the build-in elements are done.

NOT answered, and it is what the port actually needs: units per SECOND.
units/s = (units/frame) x (guest fps); this pins the first at 2 and says
nothing about the second, and 2x30 vs 2x60 differ by exactly the ~2 s the
human reported. My own title-plate-delay-measured.md's 2.13 s does not
reconcile with this capture's 20 labels for the same two anchors -- a real
~2.9x disagreement, now localised to frames->seconds rather than
units->frames. Settling it needs the guest's own frame counter, which
neither capture read.
2026-09-01 16:49:08 +00:00
sylph-decoder
e625aad71e re: pre-register the H3 measurement before reading the capture
The Port asks which of two of my measurements to believe -- 55 units/s from
the plate page, ~150 from their reading of my splash timeline, a factor of
2.7 off one game.

Neither is safe and for the same reason: both convert an ALPHA STEP into a
CLOCK RATE, which needs the element's declared ramp length, because
d(alpha)/frame = 255 * (units/frame) / T. My own splash capture shows that
biting: splash B steps 34/label while splash A's logo steps 68 or 136 across
one label pair. Same boot, 2x or 4x apart. An alpha step is not a clock rate.

So measure on an element whose T is declared: ptbtn00, the plate itself,
t=214 alpha=0 -> t=236 alpha=255, T=22 units.

Predicted, before looking: 11 frames at the corpus's 2 units/frame, 4.4 at
the Port's inferred 5. Accept +/-1. Committed first so it cannot be edited
afterwards.
2026-09-01 16:40:11 +00:00
sylph-decoder
3cc1b51820 re: record that stale-instrument was run for the logger change, and returned nothing
R1 asks for tools/stale-instrument when an instrument improves. The draw
logger improved this iteration; all eight claims that <harness> killed are
about the SCREENSHOT harness -- polling cadence, x11grab latency, the
title/plate classifier -- and none of them ever used the draw logger, so
none re-open. Written down because 'ran it, nothing changed' and 'did not
run it' are otherwise the same absence.
2026-09-01 16:21:39 +00:00
sylph-decoder
79b45abaa3 re: the splashes have NO post-process -- one pass, source-over, alpha in the vertex stream
Play-test finding 4 asked, in order: is there a pass, what is it, where do
its parameters come from, only then what curve. All four, from GPU state.

1. NO post-process pass. Over all 1 048 draws of frames 4..226 (both boot
   splashes): rt0=[tile=0 fmt=0 exp=0] on 1048/1048, pitch=1280 msaa=0 on
   1048/1048, edram_mode only ever kColorDepth or kCopy, resolve dests only
   the two alternating front buffers, and NO texture base anywhere in the
   capture equals a resolve destination. The only texture bound in the whole
   splash region is the sprite page 0x11A50000. No blur, no bloom, no fade
   quad over a resolved image, no tone curve, no resolve-and-resample.

2. What it is: per frame, a full-screen replace triangle (the clear), a
   full-screen black quad through the ordinary blend, ONE batched sprite
   draw carrying every visible element (indices 4/8/12/24), and the two
   presentation resolves.

3. Where the parameters come from: NOT the constant banks -- the splash
   pixel shaders read zero float constants, ps_c[n=0] on 1048/1048, taken
   off each shader's own float_bitmap. NOT immediates. The fade is the
   per-vertex k_8_8_8_8 colour in a vertex buffer the guest rewrites every
   frame.

4. The curve falls out of 3 and is the corpus's existing 34/frame law,
   reproduced on an independent capture. Not claimed as new.

And the composite is ORDINARY SOURCE-OVER, confirmed from the shader ucode
rather than inferred from the blend register: the register reads
ONE/ONE_MINUS_SRC_ALPHA, which looks premultiplied, and is -- because the
shader premultiplies. The two together are src*A + dst*(1-A). So the 'more
pronounced' fade is neither a blend difference nor a pass.

The likely mechanism instead: the developer splash submits SIX quads, three
logos plus three slightly-larger companions that lead them by 8 frames and
are gone 79 frames early. Two over-blended copies of the same art at
different scale is a halo, on screen only during the entry.

Refutation attempt, recorded: REFUTED.md's re-opened rest() pair states its
settling condition as 'a draw capture of the developer splash naming which
of the three glows is submitted at rest'. This is that capture. All three
companions ARE submitted in all 21 frames -- so no rule that hides one
describes this stream -- but they are NOT interchangeable: Q6 leaves the
plateau on its own -5.6/frame decay while Q4 and Q5 hold 255. The one-byte
sibling difference is drawn.

Trap recorded with the evidence: censusing the whole 600-frame log finds six
640x360 textures sampled ~330 times each, which reads exactly like a
half-resolution blur chain. They are the attract movie's chroma planes and
first appear at frame 234, after both splashes. Restricting the window is
what separates them.

Evidence: docs/re/data/splash-draw-pass-census.txt,
docs/re/data/splash-quad-timeline.txt, docs/re/data/shaders/*.ucode.frag.
Logger: canary sylpheed-re d90d14e02.
2026-09-01 16:21:17 +00:00
MechaCat02
647390e1dc register: reclassify under R1 -- name every instrument, re-open the ten our own killed
Both agents asked for this and neither could do it: the register is the file
they both read to decide what NOT to try, so two agents agreeing is not the
authority for changing it (RETRO-2026-08-31-agreed §7.1).

R1: a refutation whose instrument is one of our renderers is not a refutation,
it is "our renderer disagrees" -- 🟡, not . The motivating case was not
careless work. "Blending those sprites additively worsens every measure against
the capture" killed a real disc field for weeks, and read exactly like a
publishable negative; the renderer behind it had a stale keyframe association,
no leaf geometry and no rotation. Nothing in the entry could have told you.

All 222 entries now end with ⟨instrument⟩, read off each entry's OWN stated
evidence -- never inferred. An entry that states none gets `unrecorded`, which
is 83 of them.

Ten moved  -> 🟡, each naming what would settle it: 8 render-vs-capture,
1 our-reader, 1 harness.

Three things the pass turned up:

* The rest() question is OPEN and had been reading as settled in both
  directions -- "rest = last keyframe" was refuted by a sibling argument, and
  that refutation refuted by correlating our render against captures. Both legs
  are our renderer. Which one you believed depended on which entry you found
  first, and it decides the pose every plateau-less element is drawn at.
* A withdrawal never reached its sibling: "2 391 frames, max glyph 0" was
  withdrawn because a long-lived x11grab stream freezes and repeats a stale
  frame; the 1 674-sample negative three lines above it, same probe, was left
  standing as a reinstated measurement.
* 83 of 222 -- 37 % -- record no instrument at all. Not disputed, not safe:
  unauditable, and larger than every other group combined.

tools/stale-instrument is the --stale query the rule needs, because a colour
alone re-opens nothing: the failure was that nothing re-opened a claim when the
instrument that killed it improved. Its own --check found a real bug on the
first run -- sys.exit("text") exits 1, so three faults documented as exit 2
were reporting the same code as an ordinary miss.

R1 is now standing text in PROTOCOL.md, with R5's exception: our tool is the
right instrument for a question about our tool.
2026-09-01 17:21:17 +02:00
sylph-decoder
94051e57f1 handoff: the blend is a bit on the disc, and their three asks
Delivered: T8aD +0x04 bit 0x02 selects additive, so the port derives the blend
instead of transcribing my table -- including for screens neither of us has
captured. With why I got it wrong first: the bit was already in REFUTED.md,
killed by a comparison of two renders made while our renderer had a stale
keyframe association, no leaf geometry and no rotation.

Their asks: pteff10 IS additive on the main menu, three sessions, every frame,
and my coverage table listing it uncovered there is corrected. The sweep ramp is
four leaf keyframes on the disc, with the oracle agreeing on sign and magnitude
and a second identification from the sub-128 alpha. And kind & 0x2 is not mine
to grant -- stated what the decode supports (0 violations in 15493 entries, and
it means the declaration marks the element focusable, not that the cursor can
reach it) and left the adoption to them.

ui-blend-mode-measured.md is superseded in its classification and keeps its
measurements, which are what the decode is fitted to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 07:20:50 +00:00
sylph-decoder
f4987977a3 re: the blend mode IS on the disc -- T8aD +0x04 bit 0x02, DECODED
Reverses two of my own pages. t32-blend-mode-not-on-disc.md said the mode is not
on the disc; ui-blend-mode-measured.md classified it measured and told the port
that which field selects it was unknown. Both were honest and neither is current.

Bit 0x02 of +0x04 set means the game draws that sprite ADDITIVE
(RB_BLENDCONTROL0 = 0x01010101); clear means premultiplied alpha-over.

Fit: 35 elements over three screens, 16 set/additive and 19 clear/alpha-over,
zero errors, every label read out of the guest command stream rather than off a
render.

Control: of every bit of the first twelve header words, exactly one separates
those 35 without error. Nothing ties with it -- which is precisely what the
+0x08 = 0x8050 candidate failed.

Within-pair: ptbtn00 0x0110 alpha-over against ptbtn00f 0x0112 additive, same
screen, same bundle, adjacent draws, one bit apart. And other f variants are
bit-clear and alpha-over, so it is not 'focused variants are additive'.

Out of sample: a prediction committed at bbd85e9, before its capture, on a
different archive -- GP_OPTIONS entry 19, 3 additive of 16, falsified if any
other element drew additive. The game drew exactly po_menu_eff01/02/03 additive
and nothing else.

This revives a claim REFUTED.md killed. The refutation said blending those
sprites additively worsens every measure against the capture -- a claim about
our renderer, made while that renderer had a stale keyframe association, no leaf
geometry and no rotation. Recorded as a revival with its evidence;
check_refuted.py passes with 0 unmarked assertions.

Two other readings of the same bit stay refuted and are explicitly not revived:
'the name contains eff' and 'premultiplied storage'. Those were the bit's
meaning; this is its effect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 07:20:12 +00:00
sylph-decoder
93f8703d06 re: the sweep ramp is DECODED -- four leaf keyframes, and the GPU agrees on sign and magnitude
The port asked for the sweep strips' vertex alpha as a function of position. It
is not a runtime curve to sample: ptloop01/ptloop02's nested leaves declare four
keyframes each, with x, alpha, rotation, scale and time.

  pteff03   loop 600u  x -639 -> 1521, alpha 255 -> 128 -> 255, rot +30, sy 600 %
  pteff03a  loop 720u  x 1721 -> -839, alpha   0 -> 128 -> 255, rot -45, sy 800 %

Checked against three sessions of GPU draws: both signs right every time, and
magnitudes within ~15 % of the declared slopes.

The independent bit worth having: across sessions the measured alpha spans
45..242, and only pteff03a declares alpha below 128 -- pteff03's ramp never
leaves [128,255]. The strip measured at 45 is the 1303-tall one, which the AABB
geometry says is pteff03a for a completely different reason. Two identifications
agreeing.

Stated as limits rather than buried: this cannot separate the two declared
slopes, which are 25 % apart against a quantisation of 6.4 px and one alpha
level over 3-4 frames; and absolute phase is unchecked because the AABB-left to
element-x mapping under rotation and pivot is not established.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 07:11:09 +00:00
sylph-decoder
bbd85e9202 re: T8aD +0x04 bit 0x02 predicts the MEASURED blend, 35/35 -- and a prediction to test it
REFUTED.md kills this claim: 'T8aD +0x04 bit 0x02 selects an additive blend ->
mine, and refuted. Blending those sprites additively worsens every measure
against the capture.' That refutation rests entirely on our renderer, which the
corpus's own rule calls a hypothesis under test. The blend is now measured off
the GPU, so the claim can be tested against the oracle.

35 elements over three screens, every label an RB_BLENDCONTROL0 value read from
the command stream: 16 bit-set and additive, 19 bit-clear and alpha-over, zero
false positives, zero false negatives.

The control that makes it a decode rather than a coincidence: of every bit of
the first 12 header words, EXACTLY ONE separates those 35 elements without
error. Nothing ties with it. A perfect partition on a small sample is worthless
if half the header partitions equally well, which is the mistake +0x08 = 0x8050
was.

And the pair no confound survives: ptbtn00 = 0x0110, ptbtn00f = 0x0112 -- the
PRESS (A) plate and its own highlight, same screen, differing in exactly this
bit, drawn alpha-over and additive respectively.

Committed alongside is a PREDICTION for GP_OPTIONS, written before the capture
that tests it: a different archive, a different element set, and a MIXED
prediction -- po_menu_eff01/02/03 additive, 592 elements alpha-over. Falsified
if those three draw alpha-over or anything else draws additive. The developer
splash was considered first and rejected as a test: both its elements predict
alpha-over, so it can fail but cannot discriminate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 07:06:07 +00:00
sylph-decoder
f817e13694 method: a truncating instrument, and an unreachable stop condition
The UI draw capture printed 8 vertices = two quads per draw, so a batched draw
dropped the rest silently. Four EXTRAS elements therefore appeared in no draw on
any screen, which reads as 'the game does not draw these' -- and the port spent
an iteration measuring them as the worst on the screen and asking about them.
When an instrument says an element never appears, check its limits before
believing the game.

And 'press down until the cursor stops moving' is not a stop condition on a
menu that WRAPS: it was unreachable, the loop only exited by exhausting its
budget, and it landed on EXTRAS because a dropped press cancelled one lap. An
earlier version of the same loop pressed (A) on NEW GAME after two identical
readings caused by a dropped press.

What held throughout is the part worth keeping: verify the state you measured,
never the actions you took.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 07:00:41 +00:00
sylph-decoder
f122b4de4e re: EXTRAS complete -- ptframe4, pteff21/22/23 and pteff10 are all ADDITIVE
The four elements the port measured as the worst on EXTRAS, and which appeared
in no draw, were in a draw all along: the 24-index additive batch holds six
quads and Canary printed the first two. Cap raised to 64, screen re-captured,
all six named. Same draw as ptframe3, whose state was already measured -- the
one-way implication doing real work.

pteff10 is identified too, and it needed the resting SCALE: it ships as 409x144
and is drawn at 200 % x 500 % = 816x720. The matcher's 'try 1x and 2x' rule
could not name it at any scale and reported a near miss against something else,
which is a failure wearing the clothes of an answer. Candidates are now the
declaration's pivot*2 scaled by the resting keyframe as well as the texture at
1x and 2x, and the tolerance is the log's own NDC print quantisation rather than
a chosen number.

Flagged rather than buried: pteff10 measuring additive is in tension with the
port measuring it nearly exact under alpha-over. Both can be true for a dim
semi-transparent glow over a dark background, and it is the one row a rendering
check does not corroborate.

Also stated: the three full-screen alpha-over draws are NOT individually
identified -- four elements declare 1280x720 -- so the label on those rows is a
candidate, not an identification.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 06:59:11 +00:00
sylph-decoder
d63ec731ef re: correct the blend page's reach sentence -- five elements uncovered, not one
sylpheed-port checked the coverage claim element by element and it was wrong.
The page said everything on the two screens was covered but for pteff10; on
EXTRAS, ptframe4, pteff21, pteff22 and pteff23 were also in neither the per-draw
log nor a prose row -- and they are exactly the four the port measures as the
worst elements on that screen.

The result rows were never wrong. The claim about what they covered was, and it
is the kind that reads as reassurance.

Also flagged, and fair: 'every button' is a class generalisation in the page
whose own instruction is to read it as per-element facts. It came from one
element on the main menu. Now stated as such.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 06:48:41 +00:00
sylph-decoder
d4bef47e38 re: the TITLE's blend states, the menu replicated twice, and the sweeps are on screen
Two more emulator runs close two of the three reach limits on the blend page.

THE TITLE (entries 4 + 2 composited). Everything alpha-over except the two
rotated sweep strips and -- the finding -- ptbtn00f, the PRESS (A) plate's
focused variant, which is ADDITIVE while its own base ptbtn00 is not. That is
what the documented plate pulse is made of, and a port drawing both alpha-over
cannot reach the pulse's peak by any pacing.

And ptlogo_back2 / ptlogo_back2eff are ALPHA-OVER. They are frame-shaped, large,
dark and 94 %/87 % transparent -- every surface property the menu's ptframe* have
-- so 'frame-shaped and mostly transparent implies additive' is refuted on the
one screen that could test it.

THE MENU replicates draw for draw in two further sessions; the one-session
caveat is retired.

THE VERTEX CAP was 8 = two quads, so a batched draw reported its first two
elements and dropped the rest. Raised to 64. This is why ptframe4, pteff21,
pteff22 and pteff23 looked like elements the game never draws: EXTRAS' 24-index
additive draw holds six quads and the log printed two.

THE SWEEPS are on screen on the main menu in every captured frame, stepping
~0.03 NDC per frame in opposite directions with their vertex alpha ramping, at
different phases in two sessions. The leaf group runs on the menu. What that
does not say is how much they contribute.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 06:42:05 +00:00
sylph-decoder
7ae5597530 re: retract 'the game batches elements that share a mode' -- its own log refutes it
I wrote that yesterday in HANDOFF, the finding page and INDEX. It is false: in
one main-menu frame, draws 5, 6 and 7 are three separate additive draws --
consecutive, identical blend state, not merged.

Only the one-way implication holds: elements inside one draw share a blend
state; sharing a state does not put elements in one draw. The wrong version
would have licensed the port inferring a mode for an element nobody observed,
which is the one thing that page must not do. The ptframe4 conclusion is
unaffected -- it is in the same DRAW as ptframe3, not merely the same mode.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 06:16:37 +00:00
sylph-decoder
126eeec437 re: kind bit 0x2 is the FOCUSABLE flag -- decoded, 0 violations in 15493 entries
The declaration entry's kind word (+0x28) and its focus/nav index (+0x2C) are the
same fact twice: kind & 0x2 is set iff the focus index is >= 0. Checked over 24
UI paks and every parseable build in each -- 1062 focusable elements, 14431 not,
zero exceptions. The test is two-sided, so it would fail if any focusable element
lacked the bit or any non-focusable element carried it.

Consequence: kind == 0x3002 is not the test for a button. It catches 778 of 1062
and misses 284 (26.7 %) at 0x2, 0x2002, 0x3003, 0x73002, 0x73003 -- including
ptbtn00.rat on GP_TITLE's PRESS (A) plate, which is 0x73002. And 0x3000, 817
elements, looks like a button and is not focusable.

This is also the refutation attempt on sylpheed-port's kind census. Their claim
-- every decoration 0x0, every button 0x3002 -- is exactly right on the two
screens they checked, reproduced here independently, and fails one build over on
the title they have not run yet.

The other kind bits are reported as observed structure and explicitly not
claimed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 06:16:09 +00:00
sylph-decoder
cf73cfcbcf refuted: my own 0x8050 candidate (twice), and the port's frame sharpener
0x8050 dies disc-wide (38 sprites, high byte tracks the archive) and again on
EXTRAS, where pteff21/22/23 share it with the frames.

The port's 'neither frame has a fully-opaque pixel' is true and is not the
discriminator: pteff10 has max alpha 130, no opaque pixel, and they measure it as
nearly exact. Their direction survives -- the draw path did answer -- so what is
refuted is the reason, not the conclusion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 06:09:05 +00:00
sylph-decoder
5a16ce5aba method: a refusing instrument, and a boot that outlasts its capture script
Two traps this run paid for.

ring_row.py's ROW0/SPACING are x11grab constants. On a /sylph-home/re/shots/shot-0001.png grab of the
same live main menu the rows read 180.5/419.5/502.0 -- ROW0 is 45 px out, 0.57
of a step. The module refused rather than naming the wrong item, which is the
good failure, and is_main_menu() therefore returned False ON A REAL MAIN MENU. A
run gated on it would conclude 'not the menu' while sitting on the menu. Not
recalibrated: three rows from one session are not a calibration and other tools
share the constants; the module now says so where the numbers are.

menu_draw_capture.sh's 420 s title deadline fired, and the emulator left running
was at the settled title minutes later, took one A, and reached the menu first
try. A timeout is a measurement of the timeout. Leaving the emulator up after a
failed script rescued this run for one minute against a twenty-minute reboot.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 06:08:41 +00:00