Commit Graph

3 Commits

Author SHA1 Message Date
Sylpheed port agent
3aa74ea029 port: the intro had no dialogue because the voice is a separate asset, and I concatenated it wrongly first
A human play-test heard music under the boot intro and no voices. The obvious
reading -- the 5.1 fold dropped the centre channel -- is wrong. `ADV.wmv` carries
music and effects only; a cutscene's voice is a separate continuous XMA stream in
`sound.pak`, bound to the movie by the manifest in `tables.pak`. Nothing was
dropped. The exporter had never been asked for it, so every fidelity measurement
in AUDIO-VERIFICATION.md would have come back clean.

`audio::export_voice` resolves it with `media::resolve_movie_voice_region` and
never by filename: `RT01A`'s voice lives inside `VOICE_ADV.slb`, so a name match
is correct on exactly the two movies this port would have spot-checked. Decoded,
not authored -- so it runs outside the `authored/audio.json` block.

THE FIRST VERSION CONCATENATED THE REGION'S CHUNKS AND WAS WRONG. It produced
359 s of dialogue for a 137 s movie. Decoding and timing each chunk shows two of
them equal to six decimals and each spanning the whole movie -- HANDOFF Q10's
decoded two-stem shape on a second asset kind -- so they are summed at 1/n. The
error was visible only because the first version recorded the decoded length
against the movie's instead of clamping to it; the clamp `media`'s own doc
comment invites, and which `sylpheed-viewer` applies, would have produced a file
of exactly the right duration containing the wrong audio.

The dropped leading chunk matches no duration in its region and is NOT closed
here. It is the same signature as `BGM_103`'s third sub-wave, already open in
BLOCKED.md, now corroborated on an independent asset kind. Raised with the
Decoder; the manifest names every chunk dropped and its length.

Also in this commit, and separable:

* `--skip-at=SECONDS` -- `--script` structurally cannot press during a movie,
  because `_script_settled` waits while `_player != null`. That is why "does (A)
  skip the intro" had been read out of the source rather than measured.
* MISSION section 6 pins a 5.1->stereo matrix and this exporter has shipped a
  different one since P4 -- the same weighting, 7.65 dB quieter -- and said so
  nowhere. Re-measured with the right instrument (float decode, whole file, count
  the samples that would clamp, not a peak reading): the pinned matrix puts ADV
  at +4.26 dBFS on 4406 samples, while S00A never clips. So the pin overloads one
  movie and the constant is over-broad for the other. NOT changed -- the level of
  a mix is what section 6 reserves to a human. The export now carries a warning
  with the numbers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
2026-08-29 14:51:38 +00:00
Sylpheed port agent
b37ad246ff port: implement MODDING rule 4, and withdraw a red flag that was my own bad measurement
MODDING.md calls base-and-overrides "a design constraint on the exporter today,
not a milestone to add later". Nothing read `data/mods/` at all -- the directory
has existed since the monorepo merge with a .gitkeep and no code path anywhere.
Eight milestones shipped past it.

ExportTree.resolve() now shadows by path, and every read goes through it:
screens, sprites, cues, the music bed, movies. MenuAudio was reading tree.root
directly and would otherwise have made audio the one asset kind a mod could not
touch, for no reason a modder could have guessed. No manifest, no registration
step -- the path IS the registration, which is the whole of the rule. One tree,
not a stack: layering needs a load order and nobody has asked for one, so
data/mods/README.md says that rather than inventing it.

Every shadowed file is printed as it is read. The first version summarised in
_ready, before any asset had been read, so it always said "nothing shadowed yet"
-- a report structurally incapable of reporting anything, which is worse than
none because it looks like an answer.

data/mods/ was NOT gitignored, and that is a hole in a hard rule: a mod is
usually an edited game asset, and this was the one directory a user is invited
to put modified sprites in and git would have taken them. Now excluded except
the README.

Gate: a synthetic 203x43 magenta PNG (nothing disc-derived) at
data/mods/sprites/title/main_menu/ptbtn01.png changes 8501 pixels in a bounding
box of exactly 203x43 at the button's position, and `check` still passes.

RAISED, NOT RESOLVED: MODDING.md says the tree is data/base/, PORT-MISSION.md §3
and the exporter and .gitignore say export/. Both are mission files and only the
human changes a mission.

REFUTATION on Q3's paint-order key: 2 of 16 screens did not match a stable sort
by layer key -- but that was my test. pgloading_eff00.prm carries NO layer key
(layer_source "none"): a primitive with no sprite header and no implied-name
fallback. I sorted keyless first; the decoders put it last, which is right,
since it is the full-screen black quad and HANDOFF's own sentence is that the
fade quad paints last. Completing the rule to "keyless last" gives 16 of 16.
SURVIVES. Recorded because the published claim does not say where a keyless
element goes and there is one in the archive. Separately the tie-break's reach
looks understated: 105 elements share a layer key across 12 of 16 screens, where
HANDOFF characterises the cost as "one element's blend on one screen".

WITHDRAWN, and it was mine: I filed "the runtime mix has no headroom" in red
twice, off a peak reading. Measured properly it is 43 samples at full scale in
5.9 s and 24 in 98.5 s, longest run 0.25 ms -- the disc's own confirm cue on a
transient, possibly only in the 16-bit save. Nothing changed, deliberately:
attenuating would be an unmeasured level decision of the kind I refused for the
loop point. A peak reading is not a clipping measurement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WM5XL4HfrHuxz8RiMWdCMC
2026-08-29 13:41:00 +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