Files
Sylpheed/docs/re/movie-binding.md
Sylpheed RE agent aea529e7dc re: S00A.wmv confirmed off the running game -- the movie was never
ambiguous, the sampling was

Continuing last iteration's amber rather than starting something new. The
fix was already named there: stop using 5-second screenshots and record
the display.

Recorded with ffmpeg x11grab at 10 fps and matched every 0.5 s. Across
the 25 consecutive samples from capture 5.0 s to 17.5 s the S00A playhead
is strictly non-decreasing -- 1.0 through 11.0 s, advancing at
essentially real time -- with scores at or above 0.96 and four of them at
0.999 or 1.000 against a runner-up in the 0.78-0.83 range. S00A is the
top match on 23 of the 27 frames carrying signal. So MS00A -> S00A.wmv is
decoded from the manifest AND measured off the game, and the intro begins
about 4.5 s after A on the save slot.

The previous attempt's failure is worth its own METHOD line, because it
did not look like a sampling problem: it looked like weak evidence for
the wrong film. Sparse sampling produced contrast-23 frames, a playhead
that would not join up, and one frame preferring ADV. Sampling does not
weaken a signal gracefully; it turns it into noise shaped like a
different answer.

One aside recorded and not chased: in the S00A 5.5-10 s window, ADV also
scores 0.97-0.99 at its OWN monotone playhead of 33.5-37.5 s. Two films
matching strongly with two consistent playheads is not noise -- it reads
as the boot movie being a trailer cut from the story cutscenes, which
also explains why the sparse run kept flipping between them.
2026-08-28 19:23:57 +00:00

179 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Which movie plays where — the boot intro, the attract loop, the new-game intro
**Status:**`CONFIRMED`. The bindings are **decoded** from the movie manifest,
and the attract movie is independently **measured** from captured frames. One
part — whether playback is skippable — is 🟡 and the corpus contradicts itself
about it; that is written down rather than resolved by assertion.
Answers [MISSION Q9](../port/MISSION.md), and contributes to Q6.
## The manifest names the boot-side slots — decoded
The movie manifest (`dat/tables.pak` entry `0x5b983a08`, schema `0x067025b9`,
[`movie_manifest.rs`](../../crates/sylpheed-formats/src/movie_manifest.rs)) is an
array of slots whose **key is the semantic role**, with a parallel array of value
records in the same order. Its first eight slots are the whole boot-side flow:
| slot key | movie | also bound |
|---|---|---|
| `LOGO1``LOGO4` | `logo1.wmv``logo4.wmv` | — |
| **`ADVERTISE_MOVIE`** | **`ADV.wmv`** | `VOICE_ADV` |
| `STAFF_ROLL` | `SYLPH_HD720p_8M-CBR_2ch.wmv` | subtitle table |
| **`MS00A`** | **`S00A.wmv`** | `SUBTITLE_S00A`, `VOICE_S00A`, a `pwterop_s01a.prt` overlay |
| `MS01A` | `S01A.wmv` | `SUBTITLE_S01A`, `VOICE_S01A` |
The alignment is not in doubt here even though the manifest has valueless slots
elsewhere: `logo1..4` zip 1:1 onto `LOGO1..4`, and `MS00A``S00A.wmv`,
`MS01A``S01A.wmv` are the manifest's own anchor rule.
**The `logo1``logo4` slots are bound to `.wmv` files that are not on the disc**
already established, and the reason the developer splash is a screen, not a video.
## ✅ The boot intro and the attract movie are the same asset
There is **no separate boot-intro slot**. `ADV.wmv` is bound to
`ADVERTISE_MOVIE` — it *is* the advertise movie, and the boot simply plays it
first. For the port that means one video, not two.
### Measured independently, from captured frames
Frames captured 5 s apart during an attract cycle were matched against five
candidate movies by frame signature (32×18 normalised grayscale, movie frames
cropped to the 675 of 720 rows the game surface shows, 1 fps sampling).
`tools/re-capture/frame_match.py`; full table in
[`data/attract-frame-match.txt`](data/attract-frame-match.txt).
**15 of 19 attract frames matched `ADV.wmv`, and the matched timestamps advance
monotonically at the sampling rate** — 39, 45, 56, 62, 75, 80, 85, 92, 102, 108,
113, 119, 131, 137 s — ending at 137 s, which is `ADV.wmv`'s full length, after
which the title returned. That is not a similarity score, it is a playhead.
**The control was run first and behaves the same way.** Frames captured during
the *boot* movie — known to be `ADV.wmv` — matched `ADV` at 116, 121, 129, 134 s.
Its failure mode is worth stating: on near-black frames the correlation collapses
(one control frame scored 0.000, another tied `S13A` 0.984 against `ADV` 0.926).
Those are exactly the 4 attract frames that did not match. **A dark frame carries
no signature; it is not evidence for the runner-up.**
### 🔴 This corrects an earlier claim of mine
Two iterations ago I recorded the attract movie as "≈85 s, so probably not
`ADV.wmv`'s 137 s". That was wrong, and the mistake was arithmetic on an
unobserved start: my sampling began when the movie was already **39 s in**, so
what I timed was the *tail* of it, not the whole thing.
## ✅ The new-game intro is `S00A.wmv`
Slot `MS00A` — the prologue story intro, 93.87 s, with its own subtitle table,
`VOICE_S00A`, and a text overlay. Decoded from the manifest, **not** measured:
Ⓐ on `NEW GAME` hangs the emulator
([`menu-navigation-semantics.md`](menu-navigation-semantics.md)), so this
binding has not been watched happening.
## 🟡 Skippable — the corpus contradicts itself, and I did not settle it
* [`canary-scripted-input-traps.md`](canary-scripted-input-traps.md) states
plainly that "Ⓐ during a movie skips the movie, every time".
* But `boot_menu.sh` / `skip_intro.sh` as they stand **deliberately do not tap
during a movie**, logging `movie … -> waiting it out (tapping breaks the
title)`, and the same document records run G: 88 Ⓐ presses through the boot
left a permanent black screen.
Both cannot be the whole story. **The test**: boot, tap Ⓐ exactly once well
inside the movie, and record whether the title arrives early. One boot, and it
was not run this iteration.
## ✅ What ends the attract movie
Nothing intervenes: it **plays to its end**. The last matched frame is `ADV` at
137 s — the file's full duration — and the title is back on the next sample. So
the attract cycle is: title, idle ~810 s, fade to black, `ADV.wmv` in full, back
to the title (which redraws `PRESS Ⓐ BUTTON`).
## What this contributes to Q6
The manifest is **data the game reads** to sequence the boot side: the slot keys
are in play order and the key *is* the role. That is not the whole driver — it
says what plays, not what decides to advance — but it is the first file-side
piece of Q6's second half, and it means the boot-side asset order does not have
to be authored.
## ✅ Skippable — settled 2026-08-28: **one Ⓐ skips the movie**
The test named above was run, and the answer is unambiguous.
| | |
|---|---|
| baseline: title arrives with **no** input | ~193 s, ~196 s, ~193 s (three boots) |
| **one Ⓐ tapped at ~45 s into the boot** | **title at ~57 s** |
The press is provably the cause and provably singular: Canary's own
`[RE-INPUT] XamInputGetKeystrokeEx reached the driver` counter went **3 → 4**
across the tap, so exactly one keystroke was delivered, and the title arrived
~12 s later instead of ~150 s later.
**And the skipped-to title is fully functional**, which is the part that matters
for the harness folklore: it draws the `PRESS Ⓐ BUTTON` plate (2 408 plate pixels
by the same probe used elsewhere), and a second Ⓐ opened the main menu normally
([`title-after-single-A-skip.png`](captures/newgame-path/title-after-single-A-skip.png)).
### So what is `skip_intro.sh` protecting against?
Not a single tap. The failure recorded in
[`canary-scripted-input-traps.md`](canary-scripted-input-traps.md) is run G —
**88 presses at 4 s intervals through the whole boot** — which left a permanent
black screen. Hammering breaks it; one press does exactly what the button is for.
The scripts' `waiting it out (tapping breaks the title)` comment is **too broad**,
and it costs every scripted boot ~2.5 minutes.
## ✅ `S00A.wmv` watched — measured 2026-08-28
The manifest's `MS00A → S00A.wmv` has now been confirmed against the running
game. The first attempt, with 5-second screenshots, gave an ambiguous partial
playhead; re-run with **`ffmpeg x11grab` at 10 fps** and matched every 0.5 s, it
is unambiguous:
| capture t | `S00A` score @ playhead | `ADV` (runner-up) |
|---|---|---|
| 5.0 s | **0.998** @ 1.0 s | 0.787 @ 126.5 s |
| 6.0 s | **0.999** @ 1.5 s | 0.820 @ 126.5 s |
| 7.5 s | **1.000** @ 3.0 s | 0.836 @ 126.5 s |
| 9.0 s | **0.999** @ 4.5 s | 0.823 @ 126.5 s |
| 12.0 s | **0.998** @ 7.0 s | 0.985 @ 34.5 s |
| 16.0 s | **0.999** @ 10.0 s | 0.886 @ 37.5 s |
| 17.5 s | **0.967** @ 11.0 s | 0.460 @ 87.0 s |
Across the 25 consecutive samples from capture 5.0 s to 17.5 s the `S00A`
playhead is **strictly non-decreasing** — 1.0, 1.5, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0,
4.5, 5.0, 5.5, 5.5, 6.0, 6.5, 7.0, 7.0, 7.5, 8.0, 8.5, 9.0, 9.0, 9.5, 10.0, 10.5,
11.0 — advancing at essentially real time, with scores at or above 0.96. **`S00A`
is the top match on 23 of the 27 frames that carry signal** (contrast > 35;
the other 25 of 52 frames are the transition and are correctly ignored).
So the new-game intro is `S00A.wmv`: **decoded** from the manifest and now
**measured** off the game. It begins ~4.5 s after Ⓐ on the save slot.
### 🟡 An aside worth recording: `ADV.wmv` reuses `S00A` footage
In the window `S00A` 5.510 s, `ADV` also scores 0.970.99 — at its *own*
monotonically advancing playhead, 33.537.5 s. Two different movies both matching
strongly with two consistent playheads is not noise; it reads as **the boot movie
being a trailer cut from the story cutscenes**, `S00A` among them. That would
explain why the sparse first attempt kept flipping between them. Not investigated
further.
### ⚠️ Sparse sampling was the whole problem
The earlier attempt sampled every 5 s and produced contrast 2337 frames, a
playhead that would not join up, and one high-contrast frame preferring `ADV`. The
same question at 0.5 s spacing answers itself at 0.999. **The movie was never
ambiguous; the sampling was.**
### The run was killed before the mission load
An earlier run of this path disappeared at ~145 s with **no crash line in its own
log** — routine `MEM-WATCH rss=1153MB` to the end — so an external kill, not a
guest fault, and the second this session. The `DELTASABER` plates of
[`ui-title-build-map.md`](ui-title-build-map.md) need a mission load and were not
reached.