Commit Graph

8 Commits

Author SHA1 Message Date
Sylpheed port agent
7d494359d1 video: state the 5.1 downmix instead of inheriting it, and write atomically
A real defect in the P4 output, found by the human on ADV.wmv and widened by the
RE agent to the whole disc: the disc ships 28 movies in 5.1 WMA Pro (every
cutscene, INCLUDING both movies this port needs) and 69 already in stereo. A
bare `-ac 2` therefore does two different things and records neither -- stereo
passes through, and 5.1 is folded by ffmpeg's DEFAULT matrix. How loudly
centre-channel dialogue sits against the music is a content decision, and it was
being made by accident and could move under an ffmpeg upgrade.

Now stated: ITU-R BS.775, LFE dropped, normalised by 1/(1+2*sqrt(1/2)) = 0.4142.
It appears in the recorded command, so the manifest determines the output.

MEASURED rather than chosen by taste, and the measurement is the interesting
part: the explicit matrix and ffmpeg's inherited default differ by a residual of
-91 dB -- about one LSB at 16-bit -- with peak and mean agreeing to 0.1 dB. So
ffmpeg's default IS this matrix, and the audio does not change; what changes is
that the manifest now says which matrix. The UNnormalised textbook form was
measured too and clips at 0.0 dBFS, which is why the scaling is there.

The filter is applied only to 6-channel sources, probed per file with ffprobe,
so a stereo source is never run through a matrix referencing channels it lacks.

Also: encode to a temp name and rename on success. ffprobe read a mid-write
.ogv as 33 s against a 137 s source -- no error, no warning, the exact shape of
catastrophic truncation. The filesystem is shared with the RE agent, so that is
a race, not an edge case, and a half-written file must never be visible under
its final name.

tools/verify-video-audio answers the second of the three questions
docs/AUDIO-VERIFICATION.md separates: does GODOT route the audio. An
AudioEffectRecord on the Master bus writes Godot's own mixed output to a WAV
from a headless run, so "no sound card" was never the obstacle I claimed. It
deliberately checks non-silence and level only -- a difference-signal RMS
against the source is inconclusive without cross-correlation alignment and an
agreed downmix, and would produce a confident wrong number.
2026-08-29 09:04:47 +00:00
Sylpheed port agent
753d62a08f FORMAT v3: rotation, and the focus record -- the ring the port could not reach
Pin bumped to the TAG formats-pin-2026-08-29 (76653ca), applying the policy the
previous commit wrote. What I wanted from it: `UiBuild` gained a public
`records` map. Without it a leaf was unreachable through the public API --
parse_build sorted T8aD children into `sprites` and `.rat` children into a
PRIVATE map -- so the focus ring, which lives inside ptbtn0Nf.rat, a record the
parent bundle declares NO element for, could not be located at all. My exporter
was writing 19 of build 5's 21 sprites and I could not see why.

v3 carries two new things.

ROTATION. `rotation_deg`, decoded at keyframe +12, and the game DRAWS it --
confirmed twice by the RE agent on different screens with different elements:
the title's ptloop sweeps declare +30/-45 and a GPU capture submits them at
+30.26/-45.28, and the focus ring ramps 0 -> 360 with everything else constant,
caught mid-spin in a capture. Rotation is about the DECLARED PIVOT, measured.
The comparison renderer does not draw it yet, so a rotation disagreement means
sylpheed-cli is behind, not that the port is wrong. Sign is still an assumption.

THE FOCUS RECORD. A focused button is not a sprite swap: ptbtn0Nf.rat declares
the spinning ring AND the bright label, and since the parent declares no element
for the record, the leaf is the only source of placement for both. v2's single
focus_sprite could not carry the ring at all and drew the highlight 7 px
off-centre by inheriting the base position. That -7,-7 is load-bearing: the f
label is 13 px larger per axis and -7 keeps the two concentric.

Checked against the game, not against the other renderer: rendering main_menu
with OPTIONS focused changes the region x 504..703, y 399..448. The RE agent
measured the same difference in the live capture at x 505..703, y 397..446 --
independently, from the other side. Ring, label and underline all land; the only
visible residual is the ring's spin PHASE, which is exactly the one thing
neither of us has resolved (its second keyframe is untimed, and the screen-level
rule for that is not established to apply inside a leaf). Listed in `unresolved`
rather than invented.

verify-screen is unchanged at 16/16 -- rotation has no effect at rest on these
screens, as predicted.
2026-08-29 09:04:47 +00:00
Sylpheed port agent
f0b050cfe1 port: P4 -- the intro video plays inside the boot sequence
The exporter transcodes ADV.wmv and S00A.wmv to Ogg Theora and records the exact
ffmpeg command in the manifest, per MISSION §6, so a modder who dislikes the
quality re-runs one line rather than reverse-engineering what was done.

Quality was MEASURED, not judged: SSIM against the decoded source over a 10 s
sample is 0.9863 / 0.9896 / 0.9924 at -q:v 6 / 8 / 10, and at 200 % zoom on the
reel's hardest case -- fine serif text and soft gradients over near-black, where
Theora breaks first -- q8 is indistinguishable. So MISSION §6's permitted
FFmpeg-GDExtension fallback is NOT needed and is NOT being proposed. No new
runtime dependency.

-ac 2 because the source is 6-channel WMA Pro; that downmix is a decision, so it
lives in the recorded command rather than in prose.

Encoding is cached on a .cmd sidecar holding the command and the source size --
any change to either re-encodes. export/ is still regenerated wholesale; this is
derived state validating derived state, not a hand-edit, and without it every
re-export pays ~4 minutes to produce a byte-identical file.

The player renders INTO the design SubViewport. Parenting it to the Boot node
played the movie to the window instead, and every captured frame came out black
-- which is worth more than a capture-bug note: a movie outside the 1280x720
design space is outside the coordinate system every screen is expressed in.

(A) skips a movie, because Q9 measured that (title at 57 s vs a 193 s baseline).

NOT VERIFIED, and stated as such: audible playback. This container has no audio
device and Godot falls back to the dummy driver. The Vorbis stream exists, is
2-channel and decodes; whether Godot emits it is unconfirmed.
2026-08-29 08:44:25 +00:00
Sylpheed port agent
ab3ca243c5 export: reach the splash by authored entry index, and key names by entry
There were TWO splash screens and this port had neither. Entries 11/14 are the
developer logos; entries 10/13 are the SQUARE ENIX publisher wordmark, the first
thing the boot shows, which nothing in this project had noticed.

They have no .rat layout child so `is_build` cannot see them, and the RE agent
established that no CONTENT rule can either: design size fails (every extra
composable bundle sampled is 1280x720, same as every screen) and element count
fails (fragments run 2..15 elements in GP_OPTIONS/GP_SAVE_LOAD, the splash
halves are 3 and 7 -- the ranges overlap).

So `screen_builds` is `is_build` plus an authored allow-list of ENTRY INDICES,
each carrying a `why` that says it is a locator and not a claim. An allow-list
rather than a loosened predicate because this is safe in GP_TITLE and would not
be in general: there, widening adds exactly four bundles and all four are real
screens, zero fragments.

screen_names.json is rekeyed from enumeration ordinal to pak entry. Widening
renumbers ordinals, and a name that moves when the enumeration rule changes is
not a name -- the file always called the entry the stronger locator, and it is
now the only stable one. The two unnamed plates therefore renamed build_10/11 ->
build_12/15; they were always locators, and now they locate the right thing.

All 16 export and validate.
2026-08-29 08:27:41 +00:00
Sylpheed port agent
2135d20984 deps: bump the pin to 1a82ade -- the menu screens get their background
WHAT I WANTED FROM IT: 0ed33bc, "a RATC child's name is stated, not inferred".
A child was named by scanning backwards for the last printable run of bytes
before its magic. For `pteff05.t32` the three trailing payload bytes are
38 41 58 = `8AX`, which beat the real name, so the child registered under a name
no element declares and resolved to no sprite.

`pteff05.t32` is the full-resolution background of all five menu screens.

Every menu render this port has produced has been missing its background, and
P1 wrote that up as a fact about the disc -- "the bundle carries zero RATC
children for either, so there is no texture on the disc to export". That is
retracted in docs/DECISIONS.md rather than edited away.

The re-export is contained: six new sprites and nothing else. pteff05 on
main_menu/extras and their JP twins, pteff04 on both titles. Each gains a
`sprite` line and moves `layer_source` from "implied" to "sprite" -- the layer
key is now read from the file instead of the decoders' table of keys measured
off the running game, which is the ratchet turning the right way.

pteff05.png is 1280x720; ptbase.png, which had been carrying the background
alone, is 640x360 drawn at 200 %.

Measured against the live capture rather than the other renderer: settled
main_menu RMSE 8.05 % -> 5.92 %. The reference renderer was missing the same
element for the same reason, so no renderer-vs-renderer diff could have found
this -- the third time a capture has caught something both renderers agreed on.
2026-08-29 08:04:06 +00:00
Sylpheed port agent
959b43cb53 deps: bump the sylpheed-formats pin 47f423f -> b436e5a for the rest() fix
What I wanted from it: `ui_layout::rest()`. At 47f423f a trailing run of
identical keyframes was always treated as the exit, so an element with no exit
animation rested at its invisible pre-roll -- `ptframe1`/`ptframe2`, the main
menu's circuit bracket, which a capture of the running game plainly shows.

Pinned at b436e5a rather than 5e6cf0d where the fix was written, because
b436e5a is where it carries its disc-wide check: 30 of 13 991 elements move, 4
become visible, 0 become invisible. The rule shipped is the RE agent's, not the
condition this port proposed -- mine was too loose and would have erased the
word PAUSE on `pgptitle.rat`, whose trailing run is two identical TRANSPARENT
frames. A trailing run is the hold exactly when it is VISIBLE.

The re-export shows the change was contained: two files differ, and within them
exactly four `rest` blocks -- ptframe1/ptframe2 on both main menus, t=16 alpha
0x00 at the pre-roll position becoming t=62 alpha 0xff at the arrived one.
Every diff line pairs. The other ten screens are byte-identical, no sprite
changed, and `pteff02` correctly did not move.
2026-08-28 21:44:08 +00:00
Sylpheed port agent
f6fc269478 export: P0 — GP_TITLE's screens and sprites into the open tree
`sylpheed-export export` reads `dat/GP_TITLE.pak`, enumerates its twelve screen
builds, and writes each as one `sylpheed.screen/2` document with its sprite PNGs
beside it. `sylpheed-export check` validates that tree against docs/FORMAT.md
with no disc in hand — the P0 gate is "validates against FORMAT.md", which is
not something anyone can confirm by reading, so it is a program.

Two readings from FORMAT v1 turned out to be wrong and are corrected here rather
than carried:

* The focus sprite does NOT come from the element's `opt ` link. That was
  measured and refuted upstream, and this export shows why plainly: on the main
  menu `opt ` chains ptloop01 -> ptloop02 -> ptbtn01, two decorations and then a
  button. The highlight pairs by sprite NAME instead (ptbtn01.t32 <->
  ptbtn01f.t32), which is the convention HANDOFF blesses and which resolves all
  five main-menu buttons. The raw link is still exported, renamed `opt_link` so
  nothing downstream mistakes it for navigation.
* There are TWO modulate colours in different byte orders, and they multiply.
  v1's single `#rrggbbaa` could not carry both and silently dropped the alpha
  that every fade ramps. They are now `tint_rgba` and `fade_argb`, with the byte
  order in the key name, because getting it backwards is silent and reads as an
  art bug rather than a parse bug.

The exporter takes exactly one authored input: `authored/screen_names.json`,
because the disc does not name its builds and "build 5 is the main menu" is a
measurement (HANDOFF Q2), not a field. Every name it applies is stamped
`name_source: "authored"` with the evidence in `name_why`, and `check` rejects
an authored name that has no `why` — so the derived tree stays honest about
which of its fields is a decision.

Sprites are per screen, not a flat pool: `main_menu` and `extras` both ship a
`ptbase.t32` and they are different pictures.

Checked, not assumed:

* two exports of the same disc are byte-identical;
* five mutations of a valid main_menu.json — a broken paint_order permutation,
  a dangling focus_sprite, a reversed buttons list, a `#rrggbbaa` colour and an
  invented name_source — are each caught with a specific message.

`t` stays raw. Q1 is answered, but the seconds conversion is measured off the
running game and its own finding flags the frame-rate measurement as the part
worth re-testing; if the game presents at 60 Hz every duration halves. One
constant, at P2, in a file that says it is a decision.
2026-08-28 18:43:52 +00:00
MechaCat02
38541c153b scaffold the Godot port, as its own repo with its own agent
The port is deliberately separate from the reverse-engineering project: its own
repository, its own clone, its own container. Two writers in one working tree
means files change under whoever is mid-edit and a `git add -A` by one sweeps up
the other's work -- which happened today in the Reborn tree, so this is set up
not to repeat it.

The wall: Godot never reads a disc format. An offline Rust exporter converts the
user's disc into JSON + PNG + Ogg, and the Godot project reads only that. No
GDExtension, no Rust in port/. Beyond the practical reason -- Godot cannot read
IPFB, RATC, T8aD, XMA or WMV -- there is the design one: modding is a goal, and
if the runtime reads the original formats then modding means reverse
engineering, whereas if it reads JSON it means opening a file.

The decoders come from sylpheed-formats PINNED BY REVISION (47f423f), not
vendored and not reimplemented. `sylpheed_formats::media` in particular already
owns every case where one playable thing is not one archive entry: entries that
span segment files, banks with several sub-waves, and the cutscene voices, which
are one continuous XMA stream chunked into VOICE_*.slb entries whose boundaries
do NOT match the cues. That last one is the easiest thing in this project to get
subtly wrong, so the mission says outright not to re-derive it.

docs/MISSION.md is the objective (P0-P7, each gated by an artifact rather than
by compiling). docs/BLOCKED.md lists what cannot proceed until the RE agent
answers Q1-Q10, and says plainly that none of it may be guessed -- this agent
has no emulator and no oracle, so a value it invents is indistinguishable from a
decoded one a month later.

The container is deliberately small: 3 cpus / 4 GB against the RE container's
6 / 7, and an image with no C++ toolchain, no Vulkan stack and no emulator. Two
full-size containers do not fit on this box beside a desktop.

Its launcher sets the git identity through GIT_AUTHOR_*/GIT_COMMITTER_* rather
than writing [user] into .git/config -- the config route captures every commit
made in that tree, including a human's, which is how six of today's commits
ended up attributed to the RE agent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 17:56:44 +02:00