Commit Graph

263 Commits

Author SHA1 Message Date
sylph-decoder
36538fae87 re: F6 unit 10 -- the parent's declared alpha gates the sweep, and it multiplies
Build 4 declares ptloop01/02 alpha 0 until t=70, full at t=100, with 238..250
an EXIT ramp -- which answers the brief's "is 238..250 an entry or an exit"
for these elements. Builds 5/6 declare the same records flat 255, so the
build must be named before the question has an answer.

Control ran in the direction that could have flattered the hypothesis and
did not: the leaf's own alpha FALLS 255->128 where the capture RISES 8->255.
Decomposing against the leaf's declared curve leaves an implied parent that
pins at 255.0 +/-1.5 across hundreds of frames while the drawn alpha swings
242->132->145. Parent alpha is multiplied in; closes that standing 🟡.

Restores unit 8's 0.514: leaf/title = 0.4795 and 0.4667 across two runs that
differ 2x in frames. I over-withdrew it last iteration by discarding the
ratio along with the frames it had been quoted in.

Adds examples/ptloop_parent_keyframes.rs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-02 19:39:21 +00:00
sylph-decoder
02d7f9062b re: F2 -- no gain field in tables.pak, with the two unchecked sites named
The port has no gain anywhere and confirm sits 3 dB above the music. Asked
the disc rather than choosing a number.

Five audio-bearing objects in tables.pak, zero tokens matching VOL GAIN
LEVEL DB ATTEN AMP MIX LOUD. Control passes: the same matcher finds 38
SE_UI hits, so "0 hits" is not a broken matcher.

The negative is stronger than a name search usually is, and I expected it
not to be. Dumping the schema shows the token stream is value-then-key
pairs -- "40, LINE_PITCH", "0, Y_OFFSET_ANALOG_STICK" -- so NUMBERS ARE
TOKENS AND THEY CARRY NAMES. A gain in this format would have a name, and
the name search covers exactly the space where it would live. That turns "I
did not find one" into "one is not there in this file".

Reach, and it is why this is undecodable rather than decoded: I did not
check the .slb bank headers, which is the other conventional home for a
per-wave gain beside a wave index -- and is where the play-test's own
framing points. No .slb exists as a loose file on the extracted disc, so it
costs a pak extraction I did not have budget for; it is the first thing the
next attempt should do. Nor did I check the executable, where a mix could
be immediates in the sound-play path around sub_821C5580.

So this is not "the mix is not on the disc". It is "the mix is not in the
table where a cue's fields live".

Pointer, not a finding: object #15 lists po_sound_scr.prt -> SOUND among
GP_OPTIONS' screens, so a user-facing sound options screen exists and at
least one volume is runtime state.

Port keeps authoring nothing yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-02 17:08:07 +00:00
sylph-decoder
fa27509937 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
e263041337 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
958eca6b6b formats: expose the T8aD blend bit on the public API, with a control
`ui-blend-mode-decoded.md` established the field but nothing on `Element`
reached it, so the exporter could only key a blend map by SCREEN NAME --
which asserted-by-omission that the Japanese menus blend differently from
the English ones.

Adds `sprite_header_word_04`, `header_word_04_by_name`,
`sprite_blend_additive` and `blend_additive_by_name`, plus
`examples/blend_api_check.rs`: the accessor the exporter will actually
call, checked against the same 35 oracle rows read out of the guest
command stream.

22/22 agree, 0 mismatched. The control requires entry 6 to report BOTH
values (additive=9, alpha-over=7), so an accessor stuck at one answer
fails rather than scoring 100%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 18:18:47 +00:00
sylph-decoder
4ad4ca3680 retro: the Decoder side, and the integrity check it forced
The human asked both agents for a critical retro; it reached me relayed through
sylpheed-port, and I am treating it as a message rather than as their word while
staying paused on RE iterations.

Worst failure first: I READ the refutation that mattered and routed around it
instead of auditing it. REFUTED.md killed the blend bit with our own renderer as
its instrument, and I quoted the 'a claim resting on our renderer is a claim
about our renderer' rule at the port in the same session while not applying it to
my own register.

Underneath it is the sharper one: I twice accepted render-derived labels for a
disc-side question. My 'no field separates them' negative was tested against a
partition that was wrong in six places, all six of which the oracle later called
additive.

Also recorded: the silent vertex truncation, a coverage claim written rather than
computed and wrong by four, a batching generalisation refuted by the log it was
written from, and navigation whose fix was worse than the bug.

Two additions to the port's list of shared costs: neither of us has ever given a
negative a positive control, so 'absent' and 'my search does not work' are
indistinguishable in every undecodable page I have written; and we keep
attributing a three-way residual to whichever leg we happen to be looking at.

Their eight proposals attacked one by one -- P2 sharpened into a
re-classification rather than bookkeeping, P5 pushed back on ('suppression
localises disagreement; only the oracle labels it'), the rest agreed with
amendments.

And the check the retro forced: my rival sweep covered the T8aD header and not
the 60-byte declaration entry, whose earlier hunt used the corrupted labels. Swept
properly, 16 of the 35 measured elements have no declaration entry at all and 0
declaration bits separate the other 19. The decode is not underdetermined -- run
because it could have gone the other way.

The PROTOCOL delta is presented, not applied.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 11:08:34 +00:00
sylph-decoder
3d8b1c2d4b tools: the sweep leaf's declared ramp, and the slope measured off the GPU
sweep_leaf_ramp dumps the nested ptloop01/ptloop02 leaf keyframes -- position,
alpha, rotation, scale and time -- which is where the ramp the port asked for
actually lives.

sweep_positions now also pools alpha against position per strip and prints the
slope, with the quantisation stated: NDC prints to two decimals, so one frame's
dx is 6.4 px and alpha is one level, and at three or four frames the two
declared slopes (+0.0814 and -0.0651) are inside that noise. It is a direction
and magnitude check, not a discrimination.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 07:10:35 +00:00
sylph-decoder
088df913b2 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
fa887b4e5f 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
698409fdfe tools: walk to EXTRAS by cursor movement, and size-match against several builds
menu_blend_capture.sh counted two DOWNs to reach EXTRAS, which is wrong twice
over -- EXTRAS is the fifth item, and on 2026-08-31 four DOWNs landed on OPTIONS
because one press was dropped. It now presses until the cursor stops moving,
which needs no item count and no row calibration. Its title deadline follows the
same change as title_blend_capture.sh, 1200 s not 420.

ui_blend_map.py takes a comma-separated build list, because the live title is
TWO builds composited -- 4 draws the art, 2 draws the PRESS (A) plate -- and a
one-build size table cannot name the elements of a title capture.
frame_alpha_census takes its builds from argv for the same reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 06:22:53 +00:00
sylph-decoder
880a4d562b 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
dad9f2761a re: no per-element field on the disc separates the four too-dark frames
The port measures four elements as rendering too dark against the capture --
ptframe1/2 on the main menu, ptframe3/4 on EXTRAS -- with the shortfall
correlating +0.77/+0.80 with the BACKGROUND and only +0.24 with the element's
own contribution. That is the signature of a blend that scales what is already
there. It asked whether the disc selects one.

Three examples, one negative, wider than the one I gave last iteration:

* frame_alpha_census -- every T8aD sprite on builds 5 and 6 by alpha. It
  REFUTES the port's own sharpener: 'neither frame has a single fully-opaque
  pixel, against ptbase's 99.1 %' is true, and pteff10 (max alpha 130, 100 %
  partial, no opaque pixel) is measured by the port as NEARLY EXACT. So being
  wholly semi-transparent is not what makes the frames special.

* frame_vs_accurate_words -- all 12 T8aD header words for both screens, plus a
  per-BIT sweep of +0x04 and +0x08. NO word and NO bit puts the four frames on
  one side and pteff10 on the other. It also kills my own remaining candidate a
  second time: +0x08 = 0x8050 is shared with pteff21/22/23 on EXTRAS.

* frame_keyframe_unknowns -- the keyframe record's fade, tint, rotation and its
  two unexplained signed words. unknown_4 and unknown_8 are ZERO on every
  keyframe of both screens, so they carry nothing here; no frame takes a value
  of any field that another element does not.

Reach: the 60-byte declaration entry, the T8aD header word-wise and bit-wise,
and the keyframe record. Four elements, two screens. Not the executable's draw
path, which is the next commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 05:37:43 +00:00
sylph-decoder
abab343d2e re: a .t32 element carries no blend/alpha mode -- undecodable, with reach
Answers the port's ask about ptframe1/ptframe2, whose residual is uniquely higher on
flat pixels than edges and signed one direction -- a body-intensity difference.

Prior work covers .prm primitives and a refuted T8aD +0x04 bit; neither covers a
.t32 element. All 15 words of the 60-byte declaration entry are read: 3 are the
name, 8 constant, the rest kind, focus index, position and pivot. The frames are
kind 0, identical to every other plain sprite.

One candidate found and refuted by myself: T8aD +0x08 is the only word where both
frames agree uniquely on that screen, at 0x8050 -- but 38 sprites carry it
disc-wide, only 8 named frame, and the high byte tracks the archive. It is an
atlas/format word, not a mode.

Also records a false positive of my own test: +0x00 and +0x08 first read as
'separating the frames' because those words are the name string.

So any blend the port picks is authored. Reach: not looked at the executable's draw
path, where a mode selected in code rather than data would live.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 05:13:27 +00:00
sylph-decoder
c06654293c re: name the elements under the port's hot residual tiles -- a frame, a button and an effect
sylpheed-port mapped the menu's edge residual at 64 px tiles and handed over
coordinates without names, which is the division I proposed: the map is theirs, the
element inventory is mine.

Under the hot band at x 384..704, y 64..256 sit ptframe1.t32, ptbtn01.rat (the NEW
GAME button) and pteff12.t32, an effect element -- all three hot under BOTH
coordinate readings, so the answer does not depend on whether their tiles are in
design or capture space. ptbtn02 is hot in design space only.

So the hot region is not one element but three of different kinds overlapping, which
is consistent with their null: they looked for two families of tile and found one
continuous population, so the region has no character of its own.

This names what is there, not what is wrong -- their map already excludes local
displacement in these tiles. Also records their control limit: a +2 px displacement
reads back +0.839 because the slope saturates, so any slope they report is a floor
on the displacement and never a ceiling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 05:02:19 +00:00
sylph-decoder
293040b61f re: verify that static records' declared cycles are visually inert in GP_TITLE
sylpheed-port turned my point -- that a static record still declares a cycle, so a
nonzero +0x08 against a largest time of 0 is a real disagreement -- into a check on
the screens they ship. Re-derived from my reader and it reproduces exactly: 65
nested records in GP_TITLE, 20 declaring a cycle with every pose at t == 0, and 0 of
those with any element carrying more than one pose.

A record whose elements each hold a single pose renders identically looped or held,
so holding them still is correct and now measured.

It includes ptbtn11/12/13, EXTRAS' buttons in both language entries, each declaring
120 units with one pose per element. Had any carried two poses, a menu button the
disc says animates would have been held still on the one submenu the port's P5 gate
walks.

Reach: GP_TITLE only; 1530 static records exist disc-wide against the 20 here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 04:03:32 +00:00
sylph-decoder
e2b7139273 re: settle the 49.6/92.3 split -- both are real, and the second explanation was wrong too
sylpheed-port corrected their own reconciliation and I reproduced it: 0 nested
records on this disc lack a timed keyframe. All 1530 are static -- timed, every pose
at t == 0 -- so the question is well-formed there and 'not exact' is a real answer,
not an absent one. A static record still declares a cycle length.

So the two percentages are two populations and neither corrects the other: 92.3 % of
animated records, 49.6 % of all nested records including static ones, same numerator
1643. Both need their population attached.

Two wrong explanations preceded this, both mine to carry: that my scan filtered
untimed records, which .max() returning Some(0) prevents, and then their 'questions
never asked' framing which I adopted.

The page is rewritten to the settled state rather than stacked. Nothing the port
depends on moved at any point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 04:01:01 +00:00
sylph-decoder
e6a9d372f3 re: reconcile the 49.6/92.3 split -- same numerator, and my explanation of it was wrong
sylpheed-port reconciled the population gap exactly and I reproduced it: filtering
max_t > 0 gives 1781 records and 92.3 %, their figures precisely. Same numerator,
1643, both ways.

My stated explanation was wrong. I said the scan requires a timed keyframe; it does
not, because .max() returns Some(0) rather than None for records whose keyframes are
all at time 0, so 1530 records where the question has no content stayed in my
denominator and counted as failures by construction.

The conclusion is untouched -- +0x04 is 0 % under either denominator.

Records their diagnosis of why it stayed invisible: the numerator agreed to the
unit, so a shared 1643 read as agreement and neither of us looked there. And both
halves needed a qualifier neither carried -- 92.3 % is of the records where the
question is meaningful, not of nested records.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 03:57:21 +00:00
sylph-decoder
73c2b31e1b re: the +0x08 falsifier does not identify +0x08 -- the value is right, my argument was wrong
sylpheed-port aimed my own boundary finding at my loop-length control. Reproduced
from my reader over every pak: +0x04 has 0 violations too, so the falsifier rejects
+0x0c and accepts +0x04 and never discriminated. What identifies +0x08 is the
exactness statistic the page presents as secondary -- exact match in half the
records against 0 % for +0x04.

Population differs from theirs, 3311 records against 1781, because this scan takes
every pak and requires a timed keyframe, so the percentage moves but the
discrimination does not.

Second time this week with the weight on the wrong leg: a count taking credit for an
exclusion argument, now a falsifier taking credit for an exactness statistic, and
both times the real discriminator sat beside it described as a formality.

Their general form is sharper than my boundary rule: an interior consistency check
is satisfied by any internally consistent reading, and that is what a wrong offset
into a regular structure usually is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 03:52:09 +00:00
sylph-decoder
7626f9e646 re: GP_DIALOG 0/1 is a byte-identical duplicate; 2/3 is a structural pair
The two adjacent pairs with identical element sets split. 0/1 are the same 59 810
bytes stored twice -- a duplicate, not a language pair. 2/3, the DIFFICULTY build,
differ in size and in 2.77 % of bytes from offset 0x1BB while sharing every element
name, which is what a language pair looks like.

Control: entries 10/11, known to be two different dialogs, differ in 54.90 % of the
common prefix, so the comparator separates unrelated dialogs.

Supported, not proven, with the untested step named: I have not captured DIFFICULTY
in ja. That the two are ENGLISH and JAPANESE rests on the disc's convention, not on
a capture of this screen. This partially restores a claim I withdrew, at lower
strength than the original phrasing.

Also records a refutation attempt on the port's BGM_103 exclusion: it survives and
is tighter than they stated -- of 32 census rows, exactly one bank carries EITHER
wave size, not merely both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 03:12:59 +00:00
sylph-decoder
9125783cff re: the 37 are refuted too -- adjacent GP_DIALOG entries carry different STAGES
Both agents left the language reading standing for the 37 pairs that differ without
a button-count mismatch, and both observed that nothing rewarded closing it. Two
scans closed it, against my own reading.

All 39 equal-button-count pairs share button names and rows exactly, which does not
settle it -- two dialogs sharing a button template look identical by that test. What
differs does settle it: pzstg10 against pzstg02, pzstg11 against pzstg03, pzstg12
against pzstg13. These are the DLG_STAGE_TITLE01..16 dialogs and an adjacent pair
carries two different stages, with different sprite counts, which is a different
amount of text rather than a translation of the same text.

So the whole 63 is explained by one fact -- adjacent entries are unrelated dialogs
-- with no residue. The 2 identical pairs remain unexplained but are no longer
anomalous against a hypothesis, because the hypothesis is gone.

Recorded about process rather than the disc: a bound nobody is incentivised to test
is exactly where a convenient claim survives, and the next reader cannot tell
whether a bound was respected or merely never revisited.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 03:04:40 +00:00
sylph-decoder
653491a91f re: my language-sprite reading is refuted, and 'EN/JP pair' is withdrawn from the DIFFICULTY row
sylpheed-port refuted the untested reading I recorded yesterday with a count, and I
reproduced it: 26 of 65 adjacent GP_DIALOG pairs differ in BUTTON COUNT, which two
languages of one dialog cannot. The names agree once read rather than counted --
ranking_NEXT against ranking_JUMP, py_ranking against pzeff, pzstg10 against
pzstg02.

So adjacent entries are unrelated dialogs, the 63 never needed the language
reading, the 2 matching pairs need no special account, and the 140:70 ratio is a
counting coincidence -- the same fact my halves-pairing zero was showing from the
other side.

Preserving their caution: this does not establish that 0/1 and 2/3 ARE language
pairs. Identical element sets is equally consistent with a duplicate, and for the
37 pairs differing without a button-count mismatch the language reading is
unsupported rather than refuted.

Withdraws a delivered claim: I called entries 2/3 'an EN/JP pair' in HANDOFF. The
DIFFICULTY identification does not rest on it -- unique geometry plus the capture
does -- but it was stated as fact and was not one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 02:57:47 +00:00
sylph-decoder
2dba970c29 re: the dialog id to pak-entry join is not positional -- hypothesis refuted
GP_DIALOG has exactly 140 entries against the table's 70 records, a 2:1 ratio that
would make the unbound join an ordering question. It does not hold: adjacent
pairing gives identical element-name sets on 2 of 65 pairs, halves pairing on 0.
GP_TITLE's language pairs share element sets exactly, so identical sets are the
signature there; in GP_DIALOG almost nothing matches.

Residual and unexplained: the only two adjacent pairs that DO match are entries 0/1
and 2/3, and 2/3 is the DIFFICULTY build.

A reading I am not asserting: dialog text may be baked into language-specific
sprites, which would explain the 63 by construction but leaves the 2 needing their
own explanation. Not tested.

The join stays unbound; positional ordering is now ruled out, which narrows where
to look next.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 02:54:46 +00:00
sylph-decoder
06f9af75ea re: decode the dialog table -- 70/70 records, DLG_SELECT_DIFFICULTY is id 2000
Chasing the reach I recorded this morning found the binding it said was missing.
Every DLG_ string in the image is pointed at by one aligned word at a 12-byte
stride: {u32 handler, u32 id, u32 name_ptr}, spanning 0x820A0A2C..0x820A0D68 with
three distinct handlers. Complete -- 70 names, 70 records, none unmatched -- and the
ids are banded and monotonic with a single gap at 24. Read from the image directly.

Refutation attempt on the shared reach, which both agents had recorded: 'another
four-button dialog with the same rows would be indistinguishable'. Scanned every
build in every pak for four buttons within 6 px of 259/329/399/469. Control found
both incumbents; zero rivals exist anywhere on the disc. So the geometric
identification is unique disc-wide, which is stronger than what either of us
claimed.

Still unbound: id 2000 to a pak entry. The tie remains uniqueness plus the oracle
capture, not a pointer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 02:50:35 +00:00
sylph-decoder
6ba003219b re: DIFFICULTY is a dialog -- DLG_SELECT_DIFFICULTY, GP_DIALOG entries 2/3
Closes a negative of mine whose failed assumption I had named: I searched for an
8-record btn-named build in an archive of its own, assuming DIFFICULTY's four items
pair with f variants the way GP_TITLE's screens do. It has its own prefix and is
not a GamePart screen at all.

Three independent routes agree. The image lists DLG_SELECT_DIFFICULTY among the
DLG_* dialog names at 0x820A41BB, and GP_DIFFICULTY appears zero times. GP_DIALOG
entries 2/3 are the only builds there with pcbtn00..03 -- four buttons at design
rows 259/329/399/469, spacing 70, an EN/JP pair. And my capture of the running
screen puts its four rows within 4 px of those, with spacing 70.5/69.5/70.0 against
the disc's 70/70/70.

Reach stated: the entries are identified by button count and geometry, not by a
binding from the DLG_ name to a pak entry. No such binding was found.

Also notes the consequence for Q6's count-match: NEW GAME opens a DIALOG from an
external archive, which still matches the count but is not the same category as
OPTIONS or TUTORIAL opening a GamePart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 02:44:39 +00:00
sylph-decoder
3f46b9cf16 re: GP_TITLE holds exactly three button screens, and EXTRAS is the only internal destination
Serves Q6's open question. boot-config-and-gamepart-registry.md records a
count-match for the title part's event numbers -- four menu items load an external
archive, EXTRAS stays inside GP_TITLE -- explicitly as an observation rather than a
decode. Half of it is disc-checkable and now has support.

Every button record in all 16 GP_TITLE entries: ptbtn00 (the plate), ptbtn01..05
(main menu), ptbtn11..13 (EXTRAS). No fourth button screen, so no DIFFICULTY build,
and DIFFICULTY is what NEW GAME opens. The other four destinations have their own
paks -- GP_OPTIONS, GP_SAVE_LOAD, GP_TUTORIAL -- while EXTRAS' two children are
GP_MISSION_SELECT and GP_MOVIE_THEATER, so EXTRAS is internal and its children are
not.

Still NOT a decode of the event numbers: the shape the count-match asserts is real
on the disc, but nothing shows a given event is a given row.

Negative recorded with its reach: DIFFICULTY's build is not located. I searched for
an 8-button-record build on the assumption its four items pair with f variants, as
GP_TITLE's screens do. They may not, so the negative is narrower than 'not found'.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 02:18:06 +00:00
sylph-decoder
1bf619460a re: menu focus does not survive a reboot -- six fresh boots, three following a session that ended elsewhere
No new boot was spent: six runs had already captured the first menu entry of a
fresh boot, and all six read NEW GAME. Three of them follow a session that ended
with the cursor on EXTRAS or OPTIONS, which is what makes it a test of persistence
rather than a repeated observation.

Reach stated rather than implied: every session ends with the emulator KILLED, so a
game that writes menu state on a clean shutdown would never get the chance. This
measures 'does not survive a killed session'.

Refutation attempt on the port's extras/initial_focus: ptbtn11 -- it SURVIVES.
ptbtn11 is the top button on the EXTRAS build, with the main menu as a control
where ptbtn01 is top and is known to be NEW GAME.

Incidentally corrects ring_row.py's stated calibration. It cited capture_y = 49.5 +
1.060*design_y, fitted against menu_focus.py's row centres, which are NOT the
disc's button rows -- the disc says 162/242/322/401/482, spacing 80, and
menu_focus.py drifts up to 17 px against them. Re-fitted: 64.82 + 0.9919*design_y,
residuals under 0.7 px. No item assignment changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 01:51:03 +00:00
sylph-decoder
e35c56a5ed re: refutation attempt on Q2's 'shipped twice' -- it survives, with one real caveat
The doubt was my own artefact: the entry dump printed only the first two sprite
names in HashMap order, making 11 and 14 look like different studios. Full sets
are identical.

7 of 8 pairs declare identical sprite sets, control included. 4/7 does not: entry
7 carries nine sprites entry 4 lacks, including ptlogo_jp and ptlogo_jpeff, so the
Japanese title is a different element inventory rather than the same screen
localised. That matches the JP capture work from the other side.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 22:05:03 +00:00
sylph-decoder
ec9ab203af handoff: Q2 enumerated six of eight screens and mis-paired the splashes
sylpheed-port caught that a reader counting the Q2 row gets twelve entries with no
slot for the boot splashes -- in a row already corrected once for an
ordinal-versus-entry error.

Verified off the disc: the splashes are 10/13 (palogo_sqex, publisher) and 11/14
(palogo_gamearts/seta/anima, developer). The row said 'in entry space 10/11 are
the publisher and developer splashes', which names one half of each of two
different pairs rather than a pair. All eight screens now enumerated, with the
per-entry names committed as reference data.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 21:56:45 +00:00
sylph-decoder
e5f1b7c2d4 formats: expose loop_length_units -- the port was reading +0x08 itself
sylpheed-port reports that a record's loop length is on no public ref at all
(example, test and docs/re/ only), so their screen.rs parses the four bytes with
its own RATC guard. That is my field to publish.

One function serves both levels, since a nested .rat leaf is itself a RATC bundle
with the same header shape. Returns None for a non-RATC or short slice so callers
need no guard of their own.

Verified against the disc, controls first: rejects a non-RATC slice, rejects one
too short for the field, reads big-endian at +0x08 -- then reproduces every
published value (ptbtn00f 120, ptloop01 600, ptloop02 720) over 65 GP_TITLE
records with 0 violations of +0x08 >= largest keyframe time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 20:54:16 +00:00
sylph-decoder
8d43e16c6f re: two fixed-code-under-unfixed-description hits in the crate the port pins
sylpheed-port named a pattern narrower than "docs go stale": a correct fix
sitting directly beneath a refuted description in the same file, within twenty
lines. Not drift -- editing at the point of failure without re-reading the frame
around it. Applied their grep (the vocabulary the OLD rule needed) to my crate
and found two.

ui_layout.rs:308, in rest_plateau's fallback: `continue; // the last frame
carries no time`. That is the pre-fix rule, on a branch that is now UNREACHABLE
-- measured at 0 untimed of 24 811 keyframes across 965 builds. Kept as a guard
because `time` is still Option<u32> and a malformed group could yield None, but
relabelled: it is no longer a description of the format.

ui_layout.rs:268, on the `lastall` rest override: "This is what the shifted time
reading predicts ... testing it against the captures is an independent check on
that reading." The shifted reading was refuted by the record-layout fix in the
same file. The override survives as a plain "take the last keyframe" diagnostic
alongside the documented `last` and `maxalpha`, and now says so.

Both corrections quote the original sentence so the change is visible rather than
silently overwritten -- the practice the port adopted from me this iteration.

Verified by artifact rather than by "it compiles": a comment-only edit must leave
output byte-identical, and the build-7 render's md5 is unchanged at
141771d8f1a2b3496cfd679c6cd45d1a.

METHOD records the pattern with the two greps that find it: the vocabulary of the
dead rule in code, and a HEDGE around something the current reader states exactly
in prose -- a "~0" marks where the old reader could not see.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 17:57:30 +00:00
sylph-decoder
22846bf30e re: finish the --help audit -- a stale percentage and a missing noun, both shipped
The METHOD entry I wrote an hour ago says to read your tool's own --help as if a
stranger wrote it. I had done that for ONE of sixteen leaf commands, which is the
"a rule written down is not a rule applied" failure this corpus already records
twice. Finished it across the whole surface.

One survivor, and it fails in two ways at once. `screen render --settle` said a
narrow window means the bundle never settles, "(42 % of them, mostly loop*
fragments)".

MISSING NOUN: inside `screen render`, "them" reads as the builds you would render.
The 42 % is over composable bundles -- a different and much larger set including
~1 700 two-element fragments a user of that flag never renders. ui-settle-time.md
states its population precisely; the help inherited the number without it.

STALE: recomputed under the corrected reader, the composable figure is 862/2211 =
39 %, not 731/1758 = 42 %. The POPULATION GREW BY 453, which is the keyframe
record-layout fix's signature -- it times a group's final pose, so bundles that
previously showed one timed keyframe now show two and qualify. Third consequence
of that fix not being swept, after fade_quads.py and screen-transitions.md's
0.87-4.08 s fade-in.

And the share a --settle user actually faces is 38 %: 185 of 491 screen builds.

Corrected in the help text with all three numbers and their populations, and in
ui-settle-time.md, whose three-row table is marked pre-fix and superseded rather
than edited in place. Verified by artifact -- the tool's --help output is quoted,
not merely recompiled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 17:45:43 +00:00
sylph-decoder
82b20bc51d re: test the port's black-backdrop predicate disc-wide -- exact locally, rare globally
sylpheed-port proposed that a screen declaring a full-screen .prm at t=0 with
fade == 0xff000000 is standalone, and one without it is composited: 12/4 across
their sixteen exported screens, every exception independently known to be
composited. They asked for it against archives they do not have. That is my lane.

CONTROL: the predicate reproduces their split exactly. GP_TITLE's sixteen bundles
give 12 with and 4 without, the four without being entries 0, 1, 2, 3 --
build_00, build_01, press_start, press_start_jp -- and the element names match
(pteff00.prm, palogo_eff0.prm, pgloading_eff00.prm). Independent derivation from
the disc, not a re-run of their tool.

DISC-WIDE it is rare: 76 of 965 screen builds, 7.9 %. GP_STAGE_CLEAR 4/4,
GP_SYSTEM 2/2 and GP_TUTORIAL 2/2 are all-yes; GP_HANGAR_ARSENAL is 0 of 390, and
GP_READY_ROOM, GP_OPTIONS, GP_PAUSE_MENU and GP_GAMEOVER are all zero.

So it is not a general standalone/composited test. GP_OPTIONS and GP_PAUSE_MENU
are screens a player plainly sees as screens and declare no backdrop; read as
"composited" the rule would make 92 % of the game's screens composited, which the
archives do not support. What it appears to separate is narrower: screens that
BEGIN FROM BLACK from everything else. A pause menu over gameplay, a hangar over a
3D scene and a plate over a title all lack a backdrop without being the same kind
of thing -- the negative class is heterogeneous, which is what a two-way rule
cannot express.

For the port: exact within GP_TITLE, so --black for those twelve is justified from
the file rather than assumed; do not carry it into the four archives they have yet
to export, where in three of them it classifies every screen alike.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 17:36:10 +00:00
sylph-decoder
9a80ed6bd2 re: (A) on a settled boot title DOES reach the menu -- the withdrawal explained
Clearing my own debt: I withdrew navigation.md's "boot title accepts a single A"
counter-example as confounded by three concurrent emulators and never re-ran it,
which left the claim unsupported rather than settled.

Clean trial: exactly one emulator verified by count, gated on the plate pulse
(glyph in [500,2500] held 12 samples) so the press lands on the BOOT title rather
than the attract loop's, delivery confirmed at [file-pad] keystroke vk=5800
down/up. Glyph after the press is 0 at +2 s and +4 s -- the transition -- then 327
steady from +6 s through +39 s.

327 is a proxy and reading a proxy is the habit this corpus keeps cataloguing, so
the screen was checked with which_title_screen.py instead: main_menu at RMSE
19.91 and 20.08 with margin ~10, inside the 9.9-11.7 band its control establishes
on four known captures. The before frame gives the "neither" signature at margin
0.10, correctly, since the title is neither main_menu nor extras.

So the count is 3 of 3, the latency is 4-6 s -- which is why a script that
presses and looks 0.5 s later concludes the press was dropped -- and the two
earlier failures were the confound, not the game.

Refutation attempted: sylpheed-port's leaf segment rates. Derived independently
from the disc and they SURVIVE exactly -- pteff03 +4.0000 then +4.0000 then a
hold, pteff03a -4.0667 then -4.0625 then a hold. So their inversion stands: my
linearity gate fails on the leaf whose declared track is perfectly straight.

And records the third structural consequence of main being stale, which they
raised: HANDOFF.md is the delivery contract and it lives on an unmerged branch,
so their checkout contains none of this week's entries. Findings written into the
contract reach them only through messages -- the channel the protocol says does
not count as delivery. Writing it in the contract is necessary and not sufficient
when the contract lives where the other party cannot see it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 17:17:41 +00:00
sylph-decoder
dd7edd5e38 re: withdraw "the sweep rate matches the disc" -- two errors that cancelled
sylpheed-port corrected the disc figure I compared against: the leaf's final
segment HOLDS, so a cycle length is not a motion duration. Verified from the
disc rather than accepted -- pteff03 moves over t=0..540 of a 600-unit cycle
(4.000 px/unit, not 3.600) and pteff03a over 0..630 of 720 (4.063, not 3.556).

Checking that sent me back to my own measurement, which was worse. "6-7
px/frame" came from eyeballing deltas between consecutive APPEARANCES in a
capture that skips frames, so a delta of 7 often spans two frames. A
least-squares fit of x against frame over all 132/112 points gives +4.287 and
-4.348 px/frame, with rms residuals of 3.6 and 3.3 px.

So the confirmation I reported was a prediction 20 % too low meeting a
measurement 50 % too high. Neither number was right and the agreement was an
artefact of both being wrong -- which is the most dangerous form of agreement in
this corpus, because nothing about it looked suspicious.

The corrected numbers say something larger than the claim they replace. Measured
against declared: 4.287/4.000 = 1.072 units per frame, and 4.348/4.063 = 1.070.
Two independent strips with different cycle lengths and different declared rates
agree to three significant figures. Q1 establishes 2 units per RENDERED frame for
top-level elements. So either a nested leaf record advances at about half the
top-level rate, or Q1's factor does not apply to nested records. Measured, not
explained, and flagged as deserving its own iteration because Q1 is load-bearing.

One untested candidate recorded: 1 unit per 1/30 s of game time against the
~28.5 fps this corpus measures for the idle title gives 1.053, close to 1.07.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 16:35:09 +00:00
sylph-decoder
2145691586 re: verify the sweep leaves' full extent -- port's table confirmed, plus a new fact
Attempted to refute sylpheed-port's leaf table by measuring it against the disc.
It SURVIVES to the digit: ptloop01 -> pteff03, cycle span 600, x track
-639..1521, scale (100, 600); ptloop02 -> pteff03a, span 720, x -839..1721,
scale (100, 800).

The existing ptloop_leaf_sweep_at.rs samples only t=340..540 -- a window chosen
to compare two competing fits -- so it could never have shown the extent. That
gap is what let my "ptloop01/02 do not free-run" claim stand: measured over the
parent's 200x90 pivot rect, which a leaf travelling -639..1521 is almost never
inside. ptloop_leaf_extent.rs sweeps the whole cycle instead.

New fact neither of us had: the leaves are IDENTICAL on entries 4, 5 AND 7 --
the title, the main menu and the JP title. Same leaf names, spans, x tracks,
scales and parent rest position. So the menu declares exactly the same sweep as
the title, and the still-open menu question is about the game's behaviour rather
than a different declaration.

The quad is 400 px wide at scale_x 100 % -- not widened -- and scale_y 600/800 %
makes it 1080/1440 px tall, taller than the screen. A full-height strip crossing
the frame and going off both sides, which is why a phase-to-phase diff covers the
union of two positions and looks frame-wide.

And my own "centre running x~921->1041" was a 30-unit window of a 600-unit cycle
whose centre spans -439..1721. A sub-range is not an extent -- the same caution
as a pivot not being a bounding box, one level up, and I made both errors within
a day.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 16:01:13 +00:00
sylph-decoder
ee5ed2cfd2 re: count the in-range fallbacks -- and the control that failed is the answer
sylpheed-port pointed out that classifying defaults "by inspection" is exactly
the method that cannot see an in-range fallback, and that correction applies to
my own sweep from an hour ago: I waved 64 sites through by reading them.

Counted instead, disc-wide over 965 builds and 24 811 keyframes:
  ui_layout.rs:1681  untimed poses (would fabricate t=0):        0
  ui_layout.rs:1010  pose_at queries 168 264, None (reads a=0):  0

Two zeroes, which is the result this corpus now distrusts most, so the detector
was made to prove it can see a hit: ask pose_at for a time no build declares.
The control FAILED -- 10 906 out-of-range queries, 0 None -- so the detector was
blind and the :1010 zero measured nothing.

The failure is the finding. pose_at is TOTAL: reading the source, its only None
path is an `if ks.is_empty() { return None }` guard, and disc-wide there are 0
elements with zero keyframes out of 5 453. So :1010's unwrap_or(0) is unreachable
BY CONSTRUCTION, which is stronger than "0 in this corpus" -- and it was
established by the control failing rather than by the count passing. Without the
control this corpus would have recorded a true conclusion resting on a
meaningless number.

:1681 stands differently: 0 of 24 811, and time really is Option<u32> with the
stale reader demonstrably producing None (its screen info prints a trailing -),
so the state is representable and a detector would see it. :973 is not a hazard
-- guarded two lines later by `if tmax == 0 { return false; }`, where reading is
sufficient because the guard is the proof.

METHOD gains both: a zero is worth nothing until the detector is shown able to
report non-zero; and the habit under several of this week's errors, which is
reading a PROXY for the thing when the thing itself is one command away -- a line
count for an era, a type name's spelling for its default, an ordinal for an
entry, a fallback's text for its firing rate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 15:15:42 +00:00
sylph-decoder
593ce46069 re: sweep my own crates for fallbacks that fabricate a quantity
The mirror of sylpheed-port's sweep after their exit_ramp_units catch, where a
refuted 24.0 survived in a `get(..., 24.0)` fallback because the authored entry
had been deleted as progress and the deletion was a no-op.

112 fallback sites across sylpheed-formats and sylpheed-cli. 64 supply 0, false,
empty or Default -- sentinels asserting nothing. Of the 48 remaining most are
pass-through or an extent. Positive control: the filter found media.rs:314
unwrap_or(anchor), the voice-region start fallback landed earlier this session,
so the detector finds a known case rather than only reporting absence. The
mesh.rs cluster (1.0, 0.85, 0.5, 0.70, 0.45) is env-var tunables with defaults
documented in xbg7-mesh.md.

ui_layout.rs, the crate the port pins, has 8 sites; 6 sentinel or pass-through
and 2 that could fabricate a quantity. Both fabricate a value that is
LEGITIMATE, which is worse than the port's conspicuous 24.0:

  :695  unwrap_or((DESIGN_W, DESIGN_H)) -- 1280x720, which is what every real
        screen states, so no parser output can distinguish read from invented.
        MEASURED: it fires 0 times in 965 builds disc-wide, so design_w/design_h
        is read and the port can rely on it.

  :1681 kf.time.unwrap_or(0) in the serialiser -- 0 is a real keyframe time
        (pose 0's time IS 0). Unreachable today under the corrected record
        layout, the same status as their exit_ramp_units branch, but a
        fabricated 0 would be indistinguishable from a real one.

The measuring instrument failed its own control first: a version reading EVERY
RATC child reported all 965 builds stating a non-standard design size
(GP_TUTORIAL 12x3), where `screen list` prints 1280x720 for every one -- a T8aD
sprite header read at +0x18 is garbage that passes the range test. Filtered to
the .rat records, it reproduces screen list exactly.

METHOD: a fallback default is an authored value no reader can see, and the
dangerous ones are IN-RANGE -- the only way to know is to count how often they
fire, which no parser output reveals.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 15:06:15 +00:00
sylph-decoder
c364cde476 re: sweep the disc for the ordinal foot-gun -- GP_TITLE was the mildest case
Last iteration I retracted three claims because `--build 10/11` on GP_TITLE are
entries 12/15, and named the untested remainder in my own report: how much else
in the corpus used a build ordinal as an entry index. This is that sweep.

`screen --build N` indexes a predicate-filtered list, so every rejected entry
shifts every later ordinal. Disc-wide: 21 of 24 build-bearing archives diverge,
18 of them at ordinal 0 -- `--build 0` is entry 108 in each GP_MAIN_GAME_*2D,
24/26 in GP_HANGAR_ARSENAL/GP_READY_ROOM. GP_TITLE is the ONLY archive whose
first ten ordinals are the identity, which is the sole reason 207 of the
corpus's 226 build citations are safe. Second foot-gun: `--all` swaps the
predicate and renumbers 18 archives, so `--build N` and `--build N --all` are
not the same object.

The instrument failed its control first. A version using parse_build as the
predicate reported GP_TITLE as 16 builds, ordinal == entry throughout -- it
would have certified the exact bug it was built to find. The shipped version
uses the same predicates screen_builds() uses and reproduces `screen list` on
GP_TITLE exactly.

Audited all 226 citations. One real defect: a five-row table in
ui-keyframe-time-unit.md headed "declared element (build 11)" spans builds 10
and 11 -- palogo_sqex is in 10. All five placements re-verified and correct, so
the linear-ramp measurement is untouched; only the label was wrong. Fixed with a
per-row bundle column. GP_DIALOG --build 0 and GP_DEBRIEFING_PILOTLOG --build 10
re-run and reproduce.

Refutation attempted: sylpheed-port's corrected mid-ramp test rests on
ptlogo_all_eff holding a=127 from t=112 to t=246. Their quote is exact and it is
a plateau. The refutation fails; their correction stands.

METHOD already carried the rule I broke, and ui-splash-addressing already said
the splashes need --all. The failure was not missing knowledge -- it was
addressing a bundle by index without grepping for the index first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 13:20:41 +00:00
sylph-decoder
3356343cad re: RETRACT the splash settle windows -- --build takes an ORDINAL, not an entry
The port recomputed the publisher splash's widest keyframe-free gap as 190 units
against my 8 and said one reading must be wrong. Mine was, and the library was
never wrong -- only my invocation.

From the file: entry 10's union of times is [0,15,30,45,235,239,251,255], widest
gap 190, and settle_window() returns Some((45,235)). Entry 11 gives 145. Both
match the port exactly.

The cause is that screen render --build N takes a BUILD ORDINAL. screen list says
[10] entry 12 and [11] entry 15; the splashes are entries 10 and 11 and are not
screen builds at all, so my --build 10/11 rendered the LOADING screens. This is
the foot-gun HANDOFF already documents, which the port caught months ago in the
mirror direction.

Three retractions:

1. 'Width does not predict quality' -- withdrawn. It rested entirely on the
   splashes being width 8 while winning 75x. They are the widest of the five, so
   width and mid-ramp are perfectly confounded across every screen either of us
   has measured and the width hypothesis is NOT refuted.

2. 'My filter excluded the splashes' -- withdrawn; at 190 and 145 they were never
   near the 10-unit cutoff. The other half stands: it admitted the 10-19 bucket,
   the worst at 45.1 %.

3. The splash rows of settle-vs-rest-against-captures -- void. They scored
   loading-screen renders against splash captures. I discarded them for a railed
   gamma fit; the real reason is that they were the wrong screens, and the railing
   was that mismatch surfacing where my instrument could report it.

Surviving: the title row (ordinal 4 = entry 4) and the disc-wide censuses, which
iterate pak entries directly and never touch the ordinal path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 12:59:29 +00:00
sylph-decoder
9d32852724 re: census the settle pose's own failure mode -- and refute the obvious explanation
The port found ptmsg, the main menu's footer, at alpha 127.5 at that screen's
settle instant. Verified: build 5's window is [44,56] = 12 units and
screen render --settle already prints 'narrow -- this bundle may never settle'.

Disc-wide, elements caught mid-ramp at their screen's settle instant: 25.5 %
overall, 40.9 % on windows under 10 units, 45.1 % on 10-19, falling to 11.7 % and
15.0 % on wide ones.

The obvious reading of that table -- narrow window means the settle pose is bad --
is REFUTED by the screens that motivated the proposal, and I nearly published it.
The two splashes have an 8-unit window, narrower than the main menu's 12, and the
settle pose beats rest() there by 75x and 33x. Width does not predict quality.

The predictor is the port's own statement: the settle pose wins decisively where
rest() lands on a transient's peak, and loses slightly where rest() is already
sound and an element arrives after the window closes.

And my own rest_vs_settle filter was wrong in both directions: dropping bundles
under 10 units admitted the 10-19 bucket, the worst at 45.1 %, and excluded both
splashes at width 8 -- the strongest evidence FOR the proposal. A threshold taken
from a documented rule of thumb and applied without checking which screens it
admitted and which it threw away.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 12:45:33 +00:00
sylph-decoder
642341b12b re: rest_plateau() picks the wrong plateau -- and it is the whole residual
rest_plateau() selects the LONGEST run of identical adjacent poses, which need
not be the run covering the screen's settle instant. rest_vs_settle left a 21.9 %
disagreement that I recorded as ambiguous by construction. It is not.

  CONTROL  exactly one plateau, covering the settle instant:
           3 072 / 3 072 agree (100.0 %)
  TEST     more than one plateau, at least one covering:
           1 622 elements, agree on 586 (36.1 %)
           of the 1 036 disagreements, rest() landed on a run NOT covering the
           settle instant: 1 036 -- all of them, no exceptions

Both poses are genuinely held in these cases -- they are plateau cases, not
transients -- so this is rest() returning a pose the screen has ALREADY LEFT by
the time it settles.

This corrects my own METHOD entry of two iterations ago, which said a candidate
cannot be adjudicated against the incumbent it replaces. Too strong. The bare
comparison cannot; the comparison plus a structural property that independently
says which side is wrong in each disagreement can. What I lacked was not an
oracle but a discriminator.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 12:38:18 +00:00
sylph-decoder
0c9224fbbc re: propose settle-instant posing -- and my control cannot validate it
The proposal: pose every element at the SCREEN's settle instant rather than
asking each element for its own resting pose. On the 2 249 fallback elements in
settling bundles the visible-pose rate falls 73.6 % -> 34.7 %.

But the control fails twice. Naive, over every plateau element: 46.6 %. That one
was misspecified and I caught it by asking what the number means physically --
rest() finds *a* held pose and many elements hold one during the build-in then
move on, so it answers a different question and disagreement proves nothing.
Restricted to elements HOLDING ACROSS the settle instant: 78.1 %, still not a
pass.

And the residual is ambiguous by construction: rest_plateau() picks one plateau,
so an element with two whose settle instant falls in the other will disagree --
and there pose_at(settle) is RIGHT. The control cannot separate 'the candidate is
wrong' from 'the incumbent is wrong'.

Recorded as the general point: comparing a candidate to the incumbent cannot
adjudicate when the incumbent is the thing under suspicion. It is the wrong shape
of experiment, not a tuning problem.

What does adjudicate is the oracle and it is the port's measurement, not mine --
publisher splash against a committed capture, settle-instant pose RMSE 2.17 /
0.01 % differing against --pose=rest 9.05 / 0.75 %. My numbers describe the
proposal's effect; they do not establish it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 12:22:44 +00:00
sylph-decoder
c89e4da723 re: my own 1697 audited -- and the first correction failed its control
Applying the port's physical-story rule to my own number. '1 697 fallback fires
return a visible pose' was published as if it were a defect count; it is not, since
an element that genuinely ends visible should rest visible.

The first correction split the 1 697 by whether the element's LAST keyframe is
visible: 347 correct, 1 350 transient peaks. Plausible, arithmetic fine, and
WRONG -- 12 278 of 13 991 elements (87.8 %) end at alpha 0 because a screen's exit
ramp drives everything to zero, so the split carries almost no information. The
1 350 is not published.

What survives needs no such split: the fallback runs only when no two adjacent
poses are equal, i.e. only when no pose is held, so every pose it can return is
un-held by construction -- and 1 457 of the 2 305 times it returns the element's
MAXIMUM alpha, the brightest un-held pose.

I ran that control only because the port had just been bitten by the same exit
ramp, its census calling ptmsg -- the main menu's permanent footer -- 'a 2-unit
flash'. Without its message the 1 350 would have shipped.

METHOD gains the sharpened form: the physical-story test catches confident FALSE
claims, not just nulls. A wrong number usually still has a story, just an absurd
one. Plus the tell that its fix was right -- re-keyed on the screen's span, the
false positives fell out on their own, and a definition that stops needing
hand-maintained exceptions is usually the correct one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 12:13:46 +00:00
sylph-decoder
ad9cd948c6 re: the port's two extra elements are PLATEAU cases -- refutation succeeds, and its point gets bigger
The port listed palogo_gamearts_eff and palogo_seta_eff among GP_TITLE's four
visible dwell-fallback fires; this census listed only palogo_sqex_eff and
palogo_anima_eff. Checked, and the census is right: gamearts_eff and seta_eff
hold a=255 at identical x, y and scale from t=15 to t=30, which is a plateau at
pair index 1, so rest_plateau() handles them and t=15 is the CORRECT answer. They
are not fallback cases.

The distinction is not cosmetic -- a plateau is a pose the element genuinely
holds, and only the dwell fallback is the unsound path.

But the refutation makes the port's underlying point STRONGER. Its rest pose for
those two really is the flash's peak, reached by the SOUND path. So 'a rest
render is not a frame to score against a capture' does not follow from the
fallback being unsound: a plateau can itself be the held peak of a transient. The
rule covers both paths, and the fallback census understates the exposure rather
than bounding it.

Also records the port's oracle number for the rule -- publisher splash against
the committed capture, timeline RMSE 2.17 / 0.01 % differing against --pose=rest
9.05 / 0.75 %, 75x the differing area.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 11:54:43 +00:00
sylph-decoder
cae2f7596e re: the rest() fallback -- its example dissolved, the question got bigger
ui-resting-pose.md built its dwell-fallback section on GP_TITLE build 7's
ptlogo_eff3.t32, listing keyframes [46, 61, 103, -] -- the STALE PARSER's output.
Corrected they are [0, 46, 61, 103], the longest gap moves from 61->103 to 0->46,
and BOTH ends of the new longest gap are a=0. The element no longer selects a
visible pose under either indexing, and build 7 renders byte-identical under the
corrected and legacy readings (0 px differ). MISSION lists this element as the one
case a Japanese capture was needed to discriminate; it is not.

But losing an example is not closing a question, so: disc-wide census. The
fallback fires on 2 305 of 13 991 elements and returns a VISIBLE pose in 1 697 of
them -- 74 %.

GP_TITLE is 5 fires, 4 visible, and all four are on the SPLASH screens:
palogo_sqex_eff and palogo_anima_eff, each [0:a0 15:a255 30:a212 45:a0], a flash
peaking at 15 and dead by 45 where the fallback returns t=30 a=212.

Independently converged on from the other side: the port, working from the JP
capture and knowing nothing of this census, found ptlogo_back2eff1's rest.t at the
peak of its own 4-unit sparkle with six staggered across the logo, so --pose=rest
fires every sparkle at once -- a frame the game never shows.

Consequence recorded as a rule: a render posed at rest is a legitimate common
reference for comparing two DECODERS and is not a frame to score against a
capture of the game.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 11:50:07 +00:00
sylph-decoder
5f386111ad re: count the voice-region population properly -- 25 three-chunk, and 17 of them were broken
Pays the debt from the truncated audit. The census prints population, coverage
and skips in the same output, and ends with an explicit END line, so a cut-short
run cannot be read as a complete one.

  POPULATION 104 movies; COVERAGE 95 resolved, 9 unresolved, 0 unreadable
  70 one-chunk regions, 25 three-chunk regions

The port's 25 was right; my '8 of 10' was not a count.

Cross-referenced against the fix's own sweep, which also ran to completion
(78 + 17 + 9 = 104): all 17 changed regions are three-chunk, none is one-chunk,
and 8 three-chunk regions were never affected -- which the 1.5 MB cap predicts,
since a region only trips the filter if its span exceeds it.

So 'the defect is specific to the multichannel regions' survives with complete
populations on both sides, while 'all three-chunk regions were broken' does not.
The original 8-of-10 was wrong in its denominator and coincidentally shares a
digit with the 8 that are unaffected, which is the kind of resemblance that
carries a dead number into a later document.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 09:46:08 +00:00
sylph-decoder
fa832fad1e re: the menu BGM loops at 61.93 s, and the game never reaches the fade
240 s parked on the main menu, reached by using the XMA probe log as the screen
oracle instead of video -- the route the previous iteration wrote down. Menu in
26.8 s against never-in-378 s for the video rig, guest at 0.92x, capture at
0.08 % silence against the recipe page's own best of 0.31 %. BGM_103's contexts
verify the screen and no ADV context appears afterwards, so the attract loop
never took over.

Three results, two instruments.

NO SEAM: zero runs >= 0.3 s below median-18 dB in 232 s. The port's 3.4 s
near-silence is a property of its authored loop, not of the game.

NOT THE WAVE LENGTH: autocorrelation r at 87.750 s is -0.009 on four independent
windows; the top lag is 61.909 s with a 2x harmonic. Estimator controls recover
87.750 and 60.000 exactly.

61.93 s, INDEPENDENTLY: locating 30 s slices of the capture inside the decoded
summed waves shows playback advancing exactly +5.00 s per 5 s and wrapping at
61.93, from three wraps. Control: slices cut from the wave itself at 10/45/70 s
are found at 10.00/45.00/70.00. Two points mis-lock where the slice straddles a
wrap and they carry the two lowest scores in the table.

Offsets span 0.25..57.18 s of an 87.744 s wave, so the loop is [~0, 61.93) and
the final ~25.8 s is never played -- exactly where bgm-two-stems.md found the
fade-out and trailing silence. The game loops before the fade, which is why
there is no seam.

Also corrects my own '8 of 10 three-chunk regions start mid-stream'. The port
counts 25 three-chunk regions; it is right that both numbers cannot describe the
same set. My audit run was CUT SHORT -- the committed file ends mid-list with no
summary line -- so that was a ratio over an unknown fraction of the population,
and the claim that the defect is specific to multichannel regions is now
unsupported. The ADV verification and the fix's own sweep are unaffected; that
sweep ran to completion and printed its totals.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 09:34:42 +00:00
sylph-decoder
3dbfa320ae formats: drop the 1.5 MB cap that truncated 17 voice regions' first stream
The cause, and the fix, with a disc-wide check.

resolve_movie_voice_region picks start = the predecessor cue's trailer, then
filtered it with 'end - s < 1_500_000' -- 'only within one bank'. ADV's
predecessor sits 3 618 816 B before end, so the filter rejected it and start fell
back to anchor, which is a TOC offset and not a stream boundary. That explains
the shape of the defect exactly: it strikes regions larger than 1.5 MB, which is
why the three-stream multichannel regions are hit and single-stream ones never
are. 17 of 95 resolving movies took the fallback.

ADV's predecessor trailer at 433 425 776 plus 17 040 B of descriptor and padding
is 433 442 816 -- the -238-packet start measured against the decoder, to the byte.

Dropping the cap: unchanged 78, fixed cleanly 17, changed in any other way ZERO.
In all 17 the only difference is a larger first chunk with every later chunk
byte-identical, which is what a corrected start looks like and what pulling in a
neighbouring asset does not.

Regression test pinned to the RUNNING DECODER's byte_sizes rather than to this
crate's own output. That is the point of it: every internal check passed happily
while a third of a stream was missing, so only an external number could have
caught this class of bug.

sylpheed-formats: 136 tests pass, 0 fail (the one still running at commit time is
an unrelated long mesh test).

Exact clips for the other 16 are not independently verified -- the sweep is
strong but ADV is the only one with a decoder measurement behind it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 08:55:47 +00:00
sylph-decoder
3e8235ddbd re: resolve_movie_voice_region starts INSIDE the first stream, 8 of 10 multichannel regions
Found because the port refused to apply my stream assignment and did the
arithmetic instead: the running decoder's three ADV contexts sum to 3 584 000 B
against a resolved region of 3 114 352 -- 15 % too small to hold them. Two spans,
one wrong, and it was the disc side.

The gap is 238 packets exactly (487 424 B), which is what a start offset looks
like; ctx0 declares 632 packets and the resolver's leading chunk has 394.

Verified against the decoder's own byte_sizes, which cannot be fitted to: at
-238 packets to_xma_riffs yields [1294336, 1118208, 1171456], all three exactly.
It is a real boundary and not the end of a sweep -- at -300 the previous asset's
chunks appear while the three ADV sizes stay stable.

Disc-wide: 24 of 24 single-chunk regions start at a boundary; 8 of 10 three-chunk
regions start mid-stream. The defect is specific to the multichannel case.

The audit's per-movie number is an UPPER BOUND, not the clip -- its stopping rule
is the chunk count changing, and to_xma_riffs absorbs a few packets of the
previous asset first (243 reported for ADV against a true 238). Only ADV has
external ground truth.

Consequence: in those 8 movies the leading chunk is a truncated first stream, not
a spurious artefact, and anything measured on it was measured on a fragment --
including this corpus's own chunk-0 level, though the assignment survives because
its ratio test was chosen to be immune to the clipping.

The resolver is NOT patched. Why the predecessor cue's trailer lands 238 packets
into the next asset is unanswered, and a fix guessed from one movie would be
worse than a documented defect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 08:41:22 +00:00
sylph-decoder
8c71520ab8 re: which ADV stream sits where -- settled by level, not by waveform
Completes ask #4. The three chunks were dumped from the resolved voice region
and decoded; the assignment is ctx0 -> FL/FR, ctx1 -> FC with LFE silent,
ctx2 -> BL/BR.

Two instruments failed first and both look like results, so both are recorded.
Envelope correlation with a per-pair lag search returns 0.86-0.95 for EVERY
chunk against EVERY channel, because all six residual channels share the
dialogue's activity timing -- that is an instrument with no resolving power, not
a finding. Sample-level correlation returns about zero, because the chunks do
not start with the movie and the XMA decode's framing offset is unknown.

Level settles it under the same 0.600 gain the bed uses: each stream lands
within 0.5 dB of exactly one residual pair and misses the others by 4-6 dB. The
ratio test is immune to chunk 0 being a clipped tail of ctx0 -- chunk0 - chunk2
is +5.88 dB against FL - BL at +6.18 dB, agreeing to 0.30 dB, where a swap would
be wrong by 11.76 dB.

Structural confirmation: chunk 1 is the only chunk with a digitally silent
channel and LFE is the only output channel with an empty residual (-115.73
dBFS), one to one; and the internal L/R correlations track the residual pairs'
(0.932 vs 0.918, 0.962 vs 0.929).

Worth having on its own: the same 0.600 scales both the movie bed and the voice,
so it is one mixer gain rather than two.

Reach: levels, not waveforms; one boot, one movie; and whether 0.600 is a fixed
constant or a volume setting is still unknown.

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