Sets a new mission: boot the real disc through developer splash -> intro video -> title -> main menu -> submenus in Godot 4, interactively, with no gameplay, no 3D and no emulator. docs/port/MISSION.md defines it -- eight gated milestones, each finished by an ARTIFACT rather than by compiling, plus the Ready Room as an explicitly gated stretch goal with a one-iteration probe that decides go/no-go. GP_READY_ROOM is 1106 entries with 6 recoverable names and is ISL-scripted, so it is either a week or a quarter, and the agent must not start it on its own authority. Architecture, per the user's decision: the Godot project is INDEPENDENT of the Rust viewer and never reads a disc format. An offline Rust exporter converts the disc into open formats; Godot reads only those. No GDExtension, no Rust in the Godot project, and sylpheed-viewer is off limits -- it stays the human's verification tool with its static-data rule intact. docs/port/FORMAT.md specifies the open format, versioned, because modding is the port's second goal and that makes the layout a deliverable rather than a temp directory: JSON over XML (Godot parses JSON natively; its XMLParser is SAX), names never hashes, provenance in every generated file, and unknowns listed rather than guessed. The discipline the whole thing rests on is the derived/authored split. `export/` is regenerated wholesale and never hand-edited; `authored/` is hand-written and survives a re-export. Three things this milestone needs are NOT on the disc in any decoded form -- which button does what, paint order, and menu sound cues -- so they live in `authored/` with a stated `why`. Deleting an authored entry because the exporter can now emit it IS the measure of progress. `export/` is gitignored: it is generated from the user's own disc and this stays a clean-room repo. Container: adds a pinned Godot 4 (windowed under Xvfb for screenshots, plus a headless wrapper). ffmpeg already carries libtheora, which is the video target -- Godot 4 plays only Ogg Theora natively and the disc's ADV.wmv is WMV3/WMA Pro. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
172 lines
8.8 KiB
Markdown
172 lines
8.8 KiB
Markdown
# Primary objective — a playable menu shell in Godot
|
||
|
||
**Status:** active, set 2026-08-28. This supersedes "work the RE backlog" as the
|
||
agent's primary objective. Reverse engineering continues, but it is now *in
|
||
service of* this goal: an item earns attention because the port needs it.
|
||
|
||
## What we are building
|
||
|
||
A Godot 4 project that boots to an interactive main menu using the player's own
|
||
disc, through the sequence the real game uses:
|
||
|
||
```
|
||
developer logo splash → intro video → title / PRESS Ⓐ → main menu → submenus
|
||
```
|
||
|
||
No gameplay. No 3D. No emulator. The success condition is a human pressing a
|
||
d-pad and Ⓐ and moving through those screens with the right art, the right
|
||
animation, the right music and the right transitions.
|
||
|
||
**Ready Room is a gated stretch goal.** See §6 — do the probe first, then stop
|
||
and report rather than committing to it.
|
||
|
||
## 1. The two halves, and the wall between them
|
||
|
||
The Godot project **never reads a disc format**. It reads only open formats. The
|
||
conversion happens offline, in Rust, and the wall between the two is the point:
|
||
|
||
```
|
||
disc ──▶ crates/sylpheed-export ──▶ export/ ──▶ port/ (Godot 4)
|
||
(Rust, uses sylpheed-formats) JSON + PNG + OGG reads ONLY
|
||
+ Ogg Theora open formats
|
||
```
|
||
|
||
* **No GDExtension. No Rust in the Godot project.** If Godot needs something it
|
||
cannot read, the exporter's job is to emit it in a form Godot can, not to
|
||
bridge the two runtimes.
|
||
* **`crates/sylpheed-viewer` is not involved.** The Explorer is the human's tool
|
||
for exploring and verifying the RE work. It stays Rust/Bevy, it keeps its
|
||
static-data rule, and it is neither a dependency of nor a dependent on the
|
||
port. Do not refactor it "to share code with" the port.
|
||
* `sylpheed-formats` is shared, because it is the decoder and there must be one.
|
||
|
||
## 2. The format is a deliverable, not a dump
|
||
|
||
The second goal of this port is **modding**. That makes the exported layout part
|
||
of the product, not a temp directory:
|
||
|
||
* **JSON, not XML.** Godot parses JSON natively with `JSON.parse_string`; its
|
||
`XMLParser` is a SAX-style API that would need a hand-written binding for every
|
||
schema. JSON is also what every external tool a modder might reach for already
|
||
speaks.
|
||
* **Names, never hashes.** A file is `screens/title/main_menu.json`, not
|
||
`0x90822a39.json`. Where the disc's own name was never recovered — the six
|
||
`*2D` archives and `GP_READY_ROOM`, see [the atlas](../re/disc-atlas.html) —
|
||
emit a stable synthetic id **and record in the file that the real name is
|
||
unknown**. A modder must be able to tell a recovered name from one we invented.
|
||
* **A tree that means something.** `screens/`, `sprites/`, `audio/music/`,
|
||
`audio/sfx/`, `video/`, `text/<lang>/` — organised by what a person is looking
|
||
for, not by which archive it came out of.
|
||
* **Provenance in every generated file.** Source archive, entry index, and the
|
||
exporter version. This is what keeps the export auditable against the disc
|
||
instead of drifting into an unverifiable fork.
|
||
|
||
The schema lives in [FORMAT.md](FORMAT.md) and is **versioned**. Changing it is a
|
||
deliberate act with a version bump, not a silent edit.
|
||
|
||
## 3. Derived vs authored — keep these apart
|
||
|
||
This is the discipline that matters most, and it is the corpus's own rule applied
|
||
to a new artefact.
|
||
|
||
| | `export/` | `authored/` |
|
||
|---|---|---|
|
||
| produced by | running the exporter | a human or the agent, by hand |
|
||
| contains | what the disc says | what we decided |
|
||
| edited by hand | **never** | always |
|
||
| committed to git | **no** (see §4) | yes |
|
||
| when regenerated | overwritten wholesale | untouched |
|
||
|
||
Some things this milestone needs are **not on the disc in any form we have
|
||
decoded**, and they must live in `authored/` with a stated reason:
|
||
|
||
* **Which button does what.** `ptbtn01…05` are sprites with baked-in labels. A
|
||
human can read "NEW GAME" off the image; there is no field that says so. The
|
||
mapping button → GamePart id is an authored guess until code says otherwise.
|
||
* **Paint order.** Solved at runtime, unsolved from the file — the declaration
|
||
table is provably not it. For these six screens, author the z-order and mark it
|
||
authored. Do **not** let a hand-tuned order leak into `export/` and later get
|
||
mistaken for a disc fact.
|
||
* **Sound-cue bindings.** Which cue fires on move / confirm / back.
|
||
|
||
Every `authored/` entry carries a `why` field. When a later iteration decodes the
|
||
real answer, the authored entry is deleted and the exporter emits it instead —
|
||
and that transition is the measure of progress.
|
||
|
||
## 4. Do not commit game assets
|
||
|
||
`export/` is generated from the user's own disc and **must be gitignored**. The
|
||
repository stays a clean-room port: code, schemas, authored mappings and
|
||
documentation. Anyone building it supplies their own disc. If you find yourself
|
||
committing a PNG of a game sprite or a transcoded video, stop.
|
||
|
||
## 5. Milestones, each with a gate
|
||
|
||
Do these in order. A milestone is done when its **artifact** exists, not when the
|
||
code compiles.
|
||
|
||
| | Milestone | Gate (the artifact) |
|
||
|---|---|---|
|
||
| **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 screenshot from Godot beside `sylpheed-cli screen render` of the same build — they should agree |
|
||
| **P2** | Keyframe animation | The main menu's buttons slide in. **Measure the time unit first** (§7) |
|
||
| **P3** | Splash → title, with the transition | Godot runs the two screens back to back unattended |
|
||
| **P4** | Intro video | `ADV.wmv` plays in Godot with audio (§7) |
|
||
| **P5** | Main menu: d-pad navigation, focus states, Ⓐ into a submenu, B back | A human clicks through it |
|
||
| **P6** | Audio — menu BGM and move/confirm SFX | Sound on the gate in P5 |
|
||
| **P7** | New-game intro video after NEW GAME | Plays, then returns to a defined state |
|
||
| **S1** | *Gated* — Ready Room. Run the probe in §6 and **stop** | A written go / no-go |
|
||
|
||
## 6. The Ready Room probe — do this early, it is one iteration
|
||
|
||
`GP_READY_ROOM.pak` is the largest UI archive on the disc, 1 106 entries, and
|
||
only **6 of its names resolve**. It is also ISL-scripted. That could mean a week
|
||
or a quarter, and one cheap test tells you which.
|
||
|
||
**The probe:** our screen catalog enumerates bundles by **content**, not by name
|
||
— `is_build` / `is_composable` read the bytes. Unrecoverable *paths* therefore do
|
||
not necessarily mean unrenderable *screens*.
|
||
|
||
```bash
|
||
sylpheed-cli screen list "$SYLPHEED_DISC/dat/GP_READY_ROOM.pak"
|
||
sylpheed-cli screen render --build <n> "$SYLPHEED_DISC/dat/GP_READY_ROOM.pak" /tmp/rr.png
|
||
```
|
||
|
||
Report: how many builds it finds, whether any composite looks like a Ready Room,
|
||
and whether the screens are 2D at all or whether the room is 3D with a UI overlay
|
||
— because if it is 3D, this milestone is out of scope by definition and the
|
||
answer is no-go, not "try harder".
|
||
|
||
**Then stop and write the go/no-go.** Do not start Ready Room work on your own
|
||
authority.
|
||
|
||
## 7. Two decisions already made, and one measurement owed
|
||
|
||
**Video: transcode to Ogg Theora.** `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 **keep the exact command in the exporter's config** so it can be re-run
|
||
with different settings. Theora at 720p is not great; if the result is visibly
|
||
poor, say so and propose the FFmpeg-GDExtension fallback — do not silently adopt a
|
||
dependency. Only `ADV.wmv` and the one new-game intro are in scope; the disc holds
|
||
3.3 GB of video and transcoding all of it is not this milestone.
|
||
|
||
**Screens: `GP_TITLE.pak` is the whole title-side tree.** Build 4 is the title
|
||
with the animating wordmarks, build 5 the five-button main menu, builds 6/8/9 are
|
||
submenus, and the developer splash is the `palogo` bundle in the same archive.
|
||
Buttons are identifiable as data: element kind `0x3002`, ordered top-to-bottom by
|
||
resting Y, each paired with an `f`-suffixed highlighted variant. You do **not**
|
||
need the undecoded navigation graph to make a vertical menu work.
|
||
|
||
**The measurement owed: what is a keyframe time?** Values run 16…269 on the title
|
||
build. 60 Hz frames would make the title intro ~4.5 s, which is plausible and
|
||
untested. Settle it against a capture before building animation on it — this is
|
||
exactly the kind of number that silently poisons everything downstream.
|
||
[`ui-resting-pose.md`](../re/structures/ui-resting-pose.md) already establishes
|
||
that a keyframe is the **start of a ramp**, not a pose that is held.
|
||
|
||
## 8. What is explicitly out of scope
|
||
|
||
3D, gameplay, HUD, missions, the emulator, save/load, localisation beyond English,
|
||
and any archive outside `GP_TITLE`, `tables.pak`, `sound.pak` and `dat/movie/`
|
||
— except for the Ready Room probe in §6.
|