The RE agent escalated this rather than picking, correctly: folding centre into L/R changes how dialogue sits against the music, which is an aesthetic judgement about the game and not a container detail. Decided: explicit ITU fold, centre at -3 dB, recorded in the manifest with the rest of the transcode command. Pinned rather than defaulted because a default is a decision nobody made -- invisible in the output and free to change between ffmpeg versions.
189 lines
9.2 KiB
Markdown
189 lines
9.2 KiB
Markdown
# Primary objective — the menu shell, running in Godot
|
||
|
||
**Status:** active, set 2026-08-28.
|
||
|
||
Build a Godot 4 project that boots the player's own disc through the sequence the
|
||
real game uses, and let a person move through it:
|
||
|
||
```
|
||
developer logo splash → intro video → title / PRESS Ⓐ → main menu → submenus
|
||
```
|
||
|
||
No gameplay. No 3D. No HUD. No emulator. Done means a human presses a d-pad and
|
||
Ⓐ and moves through those screens with the right art, animation, music and
|
||
transitions.
|
||
|
||
## 1. You are one of two agents
|
||
|
||
A **container agent** does the reverse engineering, in the
|
||
[Syplheed-Reborn][reborn] repository. It runs the emulator; you do not. You build
|
||
the port from what it publishes.
|
||
|
||
The contract is `docs/port/HANDOFF.md` in that repository. **Read it before
|
||
assuming any value is on the disc.** Every answer there is one of three things,
|
||
and the distinction decides what you do:
|
||
|
||
| | meaning | what you do |
|
||
|---|---|---|
|
||
| **decoded** | a field on the disc, with a disc-wide check | read it in the exporter |
|
||
| **measured** | not on the disc, but the running game does *this* | put it in `authored/`, cite the finding |
|
||
| **undecodable** | looked for, provably not there | put it in `authored/`, say it is a decision |
|
||
|
||
If HANDOFF.md does not answer something you need, **say so and move to another
|
||
milestone**. Do not guess and do not reverse engineer it yourself — you have no
|
||
emulator and no oracle, so a guess here is indistinguishable from a fact and will
|
||
be believed later.
|
||
|
||
[reborn]: https://git.mc02.dev/fabi/Syplheed-Reborn
|
||
|
||
## 2. The wall
|
||
|
||
The Godot project **never reads a disc format**. No IPFB, no RATC, no T8aD, no
|
||
XMA, no WMV. If Godot cannot read something, the exporter's job is to emit it
|
||
differently — not to bridge the gap at runtime.
|
||
|
||
* **No GDExtension. No Rust in `port/`.**
|
||
* The decoders come from `sylpheed-formats`, **pinned by TAG**:
|
||
`sylpheed-formats = { git = "...", tag = "formats-pin-2026-08-29" }`.
|
||
|
||
Pin a tag, never a bare sha. A sha reachable only from an `auto/*` branch is
|
||
orphaned when that branch is deleted or — worse — **squash-merged**, because
|
||
squash creates *new* commits: `main` looks like it contains the work while the
|
||
pin becomes unreachable and this project stops building for a fresh checkout.
|
||
A tag is a permanent ref, it says what it is in `Cargo.toml`, and it fails
|
||
loudly at *fetch* rather than silently at build.
|
||
* **Do not float the pin** to a branch. It would not do what it sounds like:
|
||
Cargo resolves a git dependency once and writes the sha into `Cargo.lock`, so
|
||
floating gives you staleness you cannot see instead of staleness you can read.
|
||
* Bump deliberately, as its own commit, saying what you wanted from the new
|
||
state. The RE agent tags when it lands something you need and tells you over
|
||
the message channel — that is how you stay current without floating.
|
||
|
||
**In particular, do not reimplement media assembly.** `sylpheed_formats::media`
|
||
already handles the cases where one playable thing is not one archive entry: a
|
||
`.pak` entry that spans segment files, a bank 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, so *a `.slb` need not hold
|
||
the track its name claims*. That last one is the single easiest thing in this
|
||
project to get subtly wrong. Use `resolve_movie_voice_region`.
|
||
|
||
## 3. Derived vs authored
|
||
|
||
| | `export/` | `authored/` |
|
||
|---|---|---|
|
||
| produced by | the exporter | you, by hand |
|
||
| contains | what the disc says | what we decided |
|
||
| hand-edited | **never** | always |
|
||
| in git | **no** — gitignored | yes |
|
||
| on re-export | overwritten wholesale | untouched |
|
||
|
||
Tempted to hand-fix a file under `export/`? The fix belongs in the exporter or in
|
||
`authored/`. Every `authored/` entry carries a `why`.
|
||
|
||
When the RE agent later decodes something you had authored, **delete the authored
|
||
entry** and let the exporter emit it. That deletion is the measure of progress.
|
||
|
||
## 4. Never commit game assets
|
||
|
||
`export/` is generated from the user's own disc and is gitignored. Code, schemas,
|
||
`authored/` mappings and docs only. If you are about to commit a sprite PNG or a
|
||
transcoded video, stop.
|
||
|
||
## 5. Milestones
|
||
|
||
A milestone is done when its **artifact** exists, not when the code compiles.
|
||
|
||
| | Milestone | Gate |
|
||
|---|---|---|
|
||
| **P0** | Exporter skeleton; one screen and its sprites to `export/` | `export/screens/title/main_menu.json` validates against FORMAT.md and the PNGs open |
|
||
| **P1** | Godot renders that screen statically at 1280×720 | A Godot screenshot beside `sylpheed-cli screen render` of the same build — they should agree, and where they do not, say which is wrong |
|
||
| **P2** | Keyframe animation | Buttons slide in. **Blocked on HANDOFF Q1** (the time unit). Do not invent it |
|
||
| **P3** | Splash → title, with the transition | Both screens back to back, unattended |
|
||
| **P4** | Intro video | `ADV.wmv` plays with audio (§6) |
|
||
| **P5** | Main menu: navigation, focus states, Ⓐ into a submenu, B back | A human clicks through it |
|
||
| **P6** | Audio — menu BGM and move/confirm SFX | Sound on the P5 gate. **Looping is blocked on HANDOFF Q10** |
|
||
| **P7** | New-game intro video after NEW GAME | Plays, then returns to a defined state |
|
||
|
||
Work the lowest unfinished milestone. When one is blocked on an RE answer, say so
|
||
in `docs/BLOCKED.md`, and take the next milestone that is not.
|
||
|
||
## 6. The video problem
|
||
|
||
`ADV.wmv` is **WMV3 video with WMA Pro audio**, 1280×720 at 30 fps, 137 s. Godot 4
|
||
plays only **Ogg Theora** natively.
|
||
|
||
Transcode with ffmpeg, and **record the exact command in the export manifest** so
|
||
a modder who dislikes the quality can re-run it rather than reverse-engineer what
|
||
you did. Theora at 720p is not great; if the result is visibly poor, **say so and
|
||
propose** the FFmpeg-GDExtension fallback — do not adopt a runtime dependency on
|
||
your own authority.
|
||
|
||
Only the boot intro and the one new-game intro are in scope. The disc holds
|
||
3.3 GB of video; transcoding all of it is not this milestone.
|
||
|
||
### The downmix is decided: pin it explicitly
|
||
|
||
**Human decision, 2026-08-29.** The cinematics are 5.1 (see
|
||
[`movie-audio-channels`][mac] for the disc-wide split — 28 surround, 69 stereo,
|
||
and *both* movies this milestone needs are surround). Fold to stereo with an
|
||
**explicit matrix**, not ffmpeg's default:
|
||
|
||
```
|
||
-af "pan=stereo|FL=0.707*FC+1.0*FL+0.707*FLC+0.707*BL+0.707*SL|FR=0.707*FC+1.0*FR+0.707*FRC+0.707*BR+0.707*SR"
|
||
```
|
||
|
||
Centre at −3 dB into both channels, which is the standard ITU fold and keeps
|
||
dialogue sitting correctly against the music. Record the full command in the
|
||
manifest, per the rule above.
|
||
|
||
Pinned rather than left to the default because a default is a decision nobody
|
||
made: it is invisible in the output, it can change between ffmpeg versions, and
|
||
it silently alters how speech sits in the mix. Adjust the matrix if it sounds
|
||
wrong — but adjust it *deliberately*, as a commit.
|
||
|
||
[mac]: https://git.mc02.dev/fabi/Syplheed-Reborn/src/branch/main/docs/re/structures/movie-audio-channels.md
|
||
|
||
## 7. Out of scope
|
||
|
||
3D, gameplay, HUD, missions, save/load, localisation beyond English, the Ready
|
||
Room, and any reverse engineering. If you want an answer the disc has not given
|
||
you, that is a request to the container agent, not a task for you.
|
||
|
||
## 8. Tooling policy — MCP servers and third-party skills
|
||
|
||
Surveyed 2026-08-28. **No Godot MCP server, for now**, and the reason is not
|
||
that they are bad:
|
||
|
||
* The mature ones ([godot-ai][ga], and most of the field) need a **live Godot
|
||
editor** running with a plugin that talks WebSocket to a Python server. This
|
||
agent is headless in a container; that is a daemon, an editor process and a
|
||
second language runtime added to an unattended loop, all of which can fail in
|
||
ways that look like a port bug.
|
||
* Their headline feature is **scene-tree introspection and node manipulation** —
|
||
built for someone hand-authoring scenes in the editor. This port *generates*
|
||
its screens from exported JSON at runtime. The agent writes a loader, not a
|
||
scene tree, so the feature that justifies the complexity does not apply here.
|
||
* What the agent actually needs to verify its work already exists:
|
||
`godot-headless` to run the project and `screenshot` to diff against
|
||
`sylpheed-cli screen render`. The verification loop is the valuable part, and
|
||
it is a bash job.
|
||
|
||
**Third-party skill packs** ([godot-claude-skills][gcs], [GodotPrompter][gp],
|
||
[Godot-Claude-Skills][rcs]) are the opposite trade: pure context, no runtime, no
|
||
daemon. They are worth revisiting. They are **not installed now** because a skill
|
||
is *instructions injected into an agent running with approvals disabled*, which
|
||
is a supply-chain decision and not one to make by default — and because P0/P1 are
|
||
a Rust exporter and a static sprite draw, which need no advanced GDScript.
|
||
|
||
**If you want one, propose it**: name the pack, say which milestone it unblocks,
|
||
and let a human vendor and review it. Do not install from a marketplace on your
|
||
own authority.
|
||
|
||
Revisit this if GDScript quality becomes the bottleneck — most likely at P2,
|
||
where keyframe ramps meet tweens.
|
||
|
||
[ga]: https://github.com/hi-godot/godot-ai
|
||
[gcs]: https://github.com/alexmeckes/godot-claude-skills
|
||
[gp]: https://github.com/jame581/GodotPrompter
|
||
[rcs]: https://github.com/Randroids-Dojo/Godot-Claude-Skills
|