Commit Graph

9 Commits

Author SHA1 Message Date
Sylpheed port agent
952c856da5 port: make the documented control sweep executable; it was prose
AUDIO-VERIFICATION.md calls its six-file sweep 'the tool's real specification'
and nothing ran it -- in a tool whose own history is two invented thresholds
caught only by controls. The same document states the principle it was breaking:
a control that does not execute is not a control.

tools/port/check-capture-controls rebuilds five of the six and asserts their
verdicts. The starved capture is gone and is reported MISSING rather than
omitted, and deliberately not synthesised from its published statistics -- a
control fitted to the answer it must give is not a control.

Two things the sweep had to learn to be honest about. check-capture emits TWO
verdicts and the doc's table compresses them; the voice control is PASS on
channels and UNJUDGED on starvation by design, so the sweep asserts the pair. And
a starved file short-circuits before the channel check, recorded as n/a rather
than FAIL -- the check did not run and the check failed are different facts.

My first 'real music bed' control was -ac 6 from a stereo source and FAILED
correctly: an upmix leaves channels silent and byte-identical, which is what the
provenance check exists to catch. The control was wrong, not the tool. Rebuilt
from six non-overlapping spans of real audio. A second attempt used aloop=-1 and
hung ffmpeg indefinitely.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
2026-08-30 04:26:07 +00:00
Sylpheed port agent
78879dce06 port: check-capture refuses a format it cannot read, and accepts the one it nearly rejected
The Decoder is moving to an ALSA `type file` tee, which writes float32. Read as
s16 that yields a plausible-looking file whose only tell is per-channel peaks
alternating exactly -- the two halves of each float landing in alternate
channels. My parser assumed s16 throughout and would have mis-read it
confidently.

An unreadable format now ends the run at PARTIAL (exit 2) rather than PASS:
channels were checked, starvation was not, and the tool says which. A checker
that claims a check it skipped is the shape of every failure this file documents.

AND THE FIRST VERSION OF THE GUARD WAS TOO STRICT -- it rejected one of this
tool's own controls, a six-tone file `ffprobe` correctly calls pcm_s16le, because
the file is WAVE_FORMAT_EXTENSIBLE (tag 0xFFFE) rather than plain PCM. A format
guard that refuses a legitimate capture is the same defect as one that mis-reads
an illegitimate one, pointing the other way. The check turns on wBitsPerSample,
which is what decides the layout; a float tee is 32-bit and still caught.

Control sweep, now the tool's real specification and all of it runnable here:

  real music+SFX bed                        PASS
  voice track, mono, 53% real pauses        PASS
  six distinct tones, PCM and extensible    PASS
  bed with 350 ms holes punched in          FAIL
  the starved capture                       FAIL
  the same tones as float32                 PARTIAL

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
2026-08-29 16:55:19 +00:00
Sylpheed port agent
ba364dba5a port: check-capture needed two numbers -- the rate alone passed a 50%-silent file
The Decoder found a blind spot in the bar I shipped last iteration. Raising the
PulseAudio client buffer keeps cutting the gap RATE while total silence bottoms
out and then doubles -- an over-large buffer starves in a few enormous holes
instead of many small ones. Its 500 ms capture scores 1.3 gaps/s, better than a
genuine music bed at 3.3, while being 50% silence. My 20/s bar passed it.

Same shape as the level table that cannot see a duplicated channel: one number,
blind to the failure next door.

I did not set a bar on their numbers, because I do not hold those files and the
last two bars in this tool were wrong precisely from being invented. Instead I
built a control in that regime -- `bigholes`, a real bed with 350 ms holes
punched in -- and set the rule from four controls I can run:

  real music+SFX bed          1.1% silence,  3.3 gaps/s   PASS
  voice track, mono, pauses  53.2% silence,  0.3 gaps/s   PASS
  bed with 350 ms holes      46.3% silence,  3.2 gaps/s   FAIL
  the starved capture        35.6% silence, 30.9 gaps/s   FAIL

Rate alone cannot separate rows 2 and 3; silence alone cannot separate 1 and 3.
The pair does: fail when >=10% is silent on every channel AND there is at least
one gap per second. Real audio is either mostly not silent, or silent in a few
long stretches -- not both at once.

AND THE REGIME IT STILL CANNOT JUDGE IS PRINTED RATHER THAN PASSED. High silence
with very few gaps is what a real voice track looks like and what an
over-buffered capture looks like; nothing here separates them, so the tool says
UNJUDGED and tells the reader to check against a known source. Inventing a bar
for a regime with no control in it is how the previous two bars came to be wrong.

A CONTROL THAT DOES NOT EXECUTE IS NOT A CONTROL: the tool returned immediately
for single-channel input, so the mono voice track -- one of the four controls --
was never run through the check it was meant to control. Mono now skips only the
duplicate test.

Also recorded: the Decoder has withdrawn "the monitor-sink route cannot be fixed
by configuration". A ~200 ms client buffer is worth a retry BEFORE anyone spends
a session on a Canary rebuild.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
2026-08-29 16:40:24 +00:00
Sylpheed port agent
c184a4f8ab port: check-capture passed a file that was 36% holes -- it now catches starvation
The Decoder diagnosed take 2 as a STARVED capture and I verified it here rather
than take it on trust: 35.6% of frames silent on all six channels, 10482
alternating runs, median burst 13.5 ms and gap 3.9 ms, a 17.4 ms period at 57 Hz.
Their untruncated original reads 39.3% and 10595 runs; the difference is exactly
the truncation and every other number agrees.

So my rebuilt correlator was working correctly on a file that could not carry the
signal. The alarming reading it produced -- that the game may not play the .wmv's
WMA track, so ADV.ogv's audio has been wrong since P4 -- is NOT SUPPORTED by this
capture and is not refuted either. Withdrawn as a concern arising from evidence,
with nothing changed in either direction. It was the most expensive-to-act-on
hypothesis in the port and it came from a file that could not speak to it.

THE REAL DEFECT WAS MINE: `check-capture` tested only for duplicated channels, so
it cleared a recording that was 36% holes. A provenance check that passes the
artefact it was built in response to is not a check.

It now measures starvation, and TWO THRESHOLDS I INVENTED WERE BOTH WRONG:

  counting exact-zero frames -- real audio crosses zero constantly, so a clean
    voice track scored 5947 "gaps" of median 0.0 ms and was called starved. A gap
    is a RUN, not a sample; only runs over 1 ms count.
  gap count and median length -- a genuine music bed shows 454 gaps at a median
    of 1.4 ms, because quiet 16-bit passages really are zero for milliseconds.

What separates them is the RATE: 32.9 gaps/s starved, 3.3 for a real bed, 0.03
for a voice track that is 53% pauses. Bar at 20/s, derived from those controls
rather than chosen and then justified. Controlled both directions: real stereo
bed PASS, six distinct tones PASS, starved capture FAIL. It also reports a `data`
chunk declaring 0 bytes -- what a file copied mid-write looks like, which is what
happened.

VOICE CHANNEL ROLES ARE NOT OBTAINABLE THIS SESSION. Both routes closed: the
monitor sink is starved by construction, and the internal tap at
SDLAudioDriver::SubmitFrame needs a Canary rebuild the Decoder has costed at a
whole session. That is the human's call, not an agent's. The port keeps authoring
with the known recorded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
2026-08-29 16:25:17 +00:00
Sylpheed port agent
6210c2e131 port: my correlation instrument failed its own control -- the negative had to be re-earned
Take 2 is a good file: it passes check-capture (I re-ran it rather than cite the
Decoder's run), carries a screen log, and was recorded with the sink's
channel_map set equal to Canary's own.

BEFORE REPORTING A SECOND NEGATIVE I ASKED WHETHER MY METHOD COULD DO THE JOB,
by building a synthetic mix -- the bed plus the three voice streams -- and
hunting the bed inside it. It failed: r=0.415, against the r>0.8 bar my earlier
negatives were judged against.

So the instrument that produced "the capture contains no ADV audio" could not
have found ADV audio in a mix even when it was certainly there. That conclusion
was right -- the Decoder's tone control proved take 1 corrupt independently --
but it was right BY LUCK and I reported it as measurement. The three controls I
was pleased with tested that the method finds a clean signal in a clean
reference, which was never the task.

REBUILT AND CALIBRATED IN BOTH DIRECTIONS. Band-limit so the target dominates,
then judge on LAG and MARGIN rather than absolute r -- r>0.8 is correct
clean-against-clean and meaningless for a component in a mix.

  bed, 40-180 Hz    in a mix containing it   r=0.663  lag 0.0 s   margin +0.111
  bed, 40-180 Hz    against a voice-only mix r=0.262  lag wrong   margin +0.005
  voice, 300-3000   in a mix containing it   r=0.810  lag 0.0 s   margin +0.248
  voice, 300-3000   against the bed alone    r=0.358  lag wrong   margin +0.005

A 20-50x separation in the discriminating statistic. Written up as
AUDIO-VERIFICATION.md section 6, retraction included.

THE NEGATIVE NOW STANDS ON SOMETHING. All six of take 2's channels, against both
targets, sit in the known-absent regime: margins 0.000-0.017, lags scattered from
-72 to +255 s. Take 2 contains neither the movie's WMA bed nor the cutscene
voice.

Two captures, differently configured, the second provably free of the channel-map
fault, with a screen log saying the movie was on screen, and neither carries
either source. Handed back: a capture path still losing the mix, or the guest not
emitting these sources during the movie, and only one side of the wall can tell
those apart. If it is the second it reaches the port directly -- the export's
movie audio comes from the .wmv's WMA track.

Also noted: the message gives 253.3 s, the file is 318.539 s. The screen log
agrees with the file, so it is a mis-stated number, but a length quoted in a
provenance claim should match the artefact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
2026-08-29 16:15:59 +00:00
Sylpheed port agent
f23180c4eb port: a capture must pass a provenance check before anyone analyses it
The 6-channel capture I spent an iteration refuting was corrupt, and the Decoder
found the cause: PulseAudio remapping between two mismatched channel maps, where
a 6-channel remap SILENTLY DROPS AND DUPLICATES. Its control -- six channels, six
tones, same sink, same parec -- came back 400/3200/200/800/800/200 for an input
of 400/800/200/1600/3200/6400. Two source channels gone entirely.

So the negative was right, and the byte-identical pair I reported was the thread
that unravelled it. Worth recording precisely, because it nearly went unreported:
it began as an idle look at two channels whose peak AND RMS matched to six
decimals, and it only became evidence because a coincidence at six decimals is
cheaper to hash than to explain.

`tools/port/check-capture` makes that check one command -- split the file, hash
every channel, fail on any duplicate pair -- and AUDIO-VERIFICATION.md gains a
section 5 saying to run it FIRST, every time, plus the two conditions the same
incident produced: start the recorder before the process, and log what was on
screen against the recording's own clock so a miss is diagnosable.

Controlled both directions, because a checker nobody controlled is what this
incident is about: six distinct tones PASS; the remap's own output pattern FAILS
naming all four pairs; the corrupt capture FAILS on ch2 == ch5.

THE KNOWN-BAD CONTROL IS THE POINT. All six of its channels report a peak of
-18.063656 dB, identical to six decimals, while containing three duplicate pairs.
A level check cannot see this failure. That is why the tool hashes rather than
measures, and why the corrupt capture's "plausible per-channel levels" were never
evidence. The tool says of itself that it is necessary, not sufficient.

Withdrawn with the file, both the Decoder's: "all six channels carry signal", and
the non-zero-surround observation offered as weak support for 5.1. Unaffected:
the three-XMA-context concurrency result, read from the emulator's log rather
than the audio path, on two independent boots.

The corrupt file is dropped from the exchange so the next agent cannot pick it up
and repeat the work. Nothing in the export changed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
2026-08-29 16:07:19 +00:00
Sylpheed port agent
d6252481e1 port: P6 -- the menu has sound, and the BGM I "chose" was decoded all along
The three Static.slb cues and the menu bed now export to Ogg Vorbis and play.
`sylpheed_formats::media` does the assembly; nothing in port/ has heard of XMA.

Three things this milestone got wrong before it got right, all recorded in
docs/port/DECISIONS.md because the corrections are the useful part:

1. The cue offsets were a Rust `const` in the exporter. They are MEASURED, not
   decoded -- a measured value compiled into the exporter is a measurement
   wearing the costume of a decoded field, and nobody deletes it because nobody
   can see it. They are authored/audio.json now.

2. I picked BGM_001 and wrote a careful `why` calling the choice arbitrary. The
   menu's music is BGM_103, and it is in HANDOFF at 0fd8e69 -- the exact commit
   BLOCKED.md says that row was reconciled against. Not stale: wrong when
   written. I had summarised a negative without its reach, so "the TABLES cannot
   say which BGM a screen plays" became "it is not on the disc". One word of
   scope was the whole answer, and the export failed only because BGM_001
   without its .slb extension hashes to nothing. That is luck, not design.

3. The comment above the BGM sum argued for unity gain "because halving is a mix
   decision nobody made". It clipped at +1.8 dBFS. 1/n is the smallest constant
   that provably cannot clip -- the same reasoning video.rs already carried for
   its 5.1 downmix, in this repository, unread.

Unsettled and shipped as such: media::sound_bank_riffs returns THREE sub-waves
for BGM_103.slb where HANDOFF Q10's census says exactly two (the third is the
leading headerless region slb.rs emits for the voice path). The exporter sums all
three and writes a manifest warning, because which bytes belong together is the
decoders' question, not this exporter's -- and dropping one would destroy the
evidence, since a corrected export looks exactly like a correct one. Raised with
the Decoder; row in BLOCKED.md.

The gate is a null control, not a peak reading. A master-bus WAV that is
non-silent proves nothing -- the bed alone would look identical. So the same
scripted walk was run with <- in place of <v>, which fires no cue (Q5, measured),
and the difference is one 0.55 s burst at t=1.10 s and silence everywhere else.
The first attempt at that control returned bit-identical zero and I nearly filed
it as "cues never reach the bus": both runs ended at 1.115 s and the first press
lands at 1.17 s. A null result from an instrument that was not running is not a
null result.

Refutation attempt: HANDOFF Q8's three cue durations. They looked attackable --
0.133/0.172/0.169 s per packet, no shared rate -- but an XMA1 packet carries a
variable number of 512-sample frames, and the three come to 50.0/32.3/95.3
frames. Measured off the decoded Ogg: 0.533, 0.344, 1.016 s, every published
digit. SURVIVES, with its reach stated -- it confirms the assembly path and my
transcription, not the event bindings, which only an oracle can retake.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WM5XL4HfrHuxz8RiMWdCMC
2026-08-29 12:29:13 +00:00
MechaCat02
7e643c242c audio: actually install the capture path I kept deferring
The audio work was three parts and I shipped two. The transcode-fidelity method
and the pinned 5.1 downmix landed; the null sink -- the only one that answers
"what does the GAME play" -- I deferred to "the next natural rebuild window" and
then rebuilt both images four times without doing it.

pulseaudio-utils is now in both, with tools/audio-capture wrapping it: a null
sink is a real device as far as an application is concerned, so Canary and Godot
open it normally and parec records what they emit.

This unblocks the decoder's Q8. The cue-to-event bindings are currently a name
match against the authors' own identifiers -- a plausible guess, not a
measurement -- and capturing what the game plays on a menu move converts them.

`audio-capture run` reports the peak level and warns when the capture is silent,
because silence is the failure that looks like success: a WAV of exactly the
right duration, full of zeroes, because the application opened a different sink.
A duration check alone passes it, which is how a confident wrong number gets
made.
2026-08-29 13:41:02 +02:00
MechaCat02
9fbb352ef0 monorepo: one repository for the decoders, the port and the corpus
Merges the Godot port into the reverse-engineering repository, preserving both
histories -- 1019 commits of corpus plus the port's 31, brought in by subtree
merge and then moved into place so git can follow each file across the rename.

The reason is not tidiness. The two-repo split forced the exporter to depend on
the decoders by pinned revision, and that created a whole class of failure that
now disappears: a sha reachable only from a topic branch, orphaned by a
squash-merge, breaking a fresh checkout silently at build time. It also forced a
live read-only mount of one agent's working tree into another's container, which
is why a contract file could move mid-iteration. With a path dependency, a
decoder change and the exporter change it requires land in the same commit or
not at all.

Canary stays separate: it is a fork tracking upstream.

New structure for the long term:

  docs/game/     how the game is NAVIGATED -- menus, modals, prompts, alerts,
                 and in-game flight. Written so nobody rediscovers it. Mostly
                 open questions on purpose; the in-game tutorials are the
                 resource for the flight half.
  docs/port/MODDING.md
                 modding as a constraint on the exporter TODAY, not a later
                 feature: one logical asset in one file (the disc splits nearly
                 everything, and resolving that is the exporter's job), names a
                 person recognises, PNG/OGG/OGV/JSON only, base-and-overrides so
                 re-exporting is always safe, provenance in every file.
  data/base + data/mods
                 generated tree and drop-in overrides, both gitignored
  exchange/      transient inter-agent files, deliberately outside history
  docs/agents/   the team protocol

Both the README and the navigation doc lead with the correction that cost the
most: the oracle is the real game under Xenia Canary. Reborn's renderer is a
hypothesis under test, it has been wrong, and treating it as ground truth
propagated into three documents and both agents before a human caught it.

Scripted modding stays possible without being built: no screen name is hardcoded
in GDScript and there is no native code in port/, which is what Godot Mod Loader
needs to be able to substitute behaviour later.
2026-08-29 11:34:46 +02:00