port: make the Godot menu shell the agent's primary objective

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>
This commit is contained in:
Sylpheed RE agent
2026-08-28 17:11:35 +02:00
parent 5cdff5e515
commit b5a193839c
6 changed files with 507 additions and 60 deletions

View File

@@ -55,6 +55,28 @@ python3 tools/re-capture/gmem.py find hex:820af844 400
* A Bevy system-parameter conflict is invisible to the type checker and panics
at startup. If you touch viewer systems, *run the binary*, don't just build it.
## The Godot port
The primary objective — see `docs/port/MISSION.md`. Two binaries are installed:
```bash
godot-headless --path port/ --script res://tools/check.gd # no display needed
DISPLAY=:99 godot --path port/ # windowed, under Xvfb
screenshot ~/shots/godot.png # then capture it
```
* **Godot is pinned to one version** in the Dockerfile. An engine bump changes
rendering, and this project diffs Godot's output against a reference renderer —
so do not upgrade it to fix a bug without saying that is what you did.
* **`sylpheed-cli screen render` is the reference.** When Godot draws a screen,
render the same build with the CLI and compare. Where they disagree, one of
them is wrong; say which, and why, rather than tuning until they match.
* **ffmpeg has libtheora**, which is the transcode target for video. Keep the
exact command in the exporter config — a modder who dislikes the quality should
be able to re-run it, not reverse-engineer what you did.
* **Never commit anything under `export/`.** It is generated from the user's own
disc and gitignored. Code, schemas, `authored/` mappings and docs only.
## Reporting
State what you measured, what you assumed, and what you could not settle. If a

View File

@@ -68,6 +68,26 @@ RUN for t in clang clang++ lld ld.lld llvm-ar llvm-ranlib llvm-nm clang-cpp; do
# Python consumer to protect, so installing into it is the honest simple option.
RUN pip3 install --no-cache-dir --break-system-packages duckdb
# ── Godot 4 ──────────────────────────────────────────────────────────────────
# The port's runtime (docs/port/MISSION.md). Two binaries, deliberately:
#
# godot the editor/windowed build, run under Xvfb, for screenshots
# that can be diffed against `sylpheed-cli screen render`
# godot-headless --headless, for script-only checks that need no display
#
# The official builds are self-contained binaries, not a package, so they are
# fetched rather than apt-installed. Pinned: an engine version bump changes
# rendering, and this project compares screenshots against a reference renderer.
ARG GODOT_VERSION=4.3
RUN cd /tmp \
&& curl -fsSLO "https://github.com/godotengine/godot/releases/download/${GODOT_VERSION}-stable/Godot_v${GODOT_VERSION}-stable_linux.x86_64.zip" \
&& unzip -q "Godot_v${GODOT_VERSION}-stable_linux.x86_64.zip" \
&& mv "Godot_v${GODOT_VERSION}-stable_linux.x86_64" /usr/local/bin/godot \
&& chmod +x /usr/local/bin/godot \
&& printf '#!/bin/sh\nexec /usr/local/bin/godot --headless "$@"\n' > /usr/local/bin/godot-headless \
&& chmod +x /usr/local/bin/godot-headless \
&& rm -f "Godot_v${GODOT_VERSION}-stable_linux.x86_64.zip"
# ── Node + Claude Code ───────────────────────────────────────────────────────
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \

View File

@@ -1,44 +1,91 @@
Work the Project Sylpheed reverse-engineering backlog, one item at a time.
Build the Godot menu port of Project Sylpheed, one milestone at a time.
## Read these first, every iteration, before proposing anything
## Your objective
They are short on purpose, and they are the reason this prompt is short:
`Syplheed-Reborn/docs/port/MISSION.md` — read it every iteration. It defines the
milestones (P0…P7 plus a gated S1), the gate each one must pass, and the two
decisions already made. Work the **lowest unfinished milestone**; do not skip
ahead because a later one looks more interesting.
1. `Syplheed-Reborn/docs/re/REFUTED.md`**claims already tested and dead.**
If your idea is on that list, it is finished; pick another. Grep it for your
nouns before you design anything.
2. `Syplheed-Reborn/docs/re/METHOD.md` — the traps this corpus has already paid
for. Most wasted iterations are one of these repeated.
3. `Syplheed-Reborn/docs/re/INDEX.md` — what is already **decoded**. Do not
re-derive a row that is already ✅. Re-deriving a known format is not a
finding; asking whether its values *resolve* is.
4. `Syplheed-Reborn/docs/re/BACKLOG.md` — the open items. It is long; skim the
`##` headings and open only the one you pick.
5. `Syplheed-Reborn/docker/agent/AGENT.md` — the container's tooling.
Reverse engineering has not stopped, but it is no longer the goal. An RE item
earns attention when the port is blocked on it, and the write-up still goes in
`docs/re/` under the usual convention.
**These files are the memory.** If a finding, a dead end or a trap lives only in
your context, it is lost at the end of the run. Write it down where the next
iteration will find it — that is what makes this a corpus rather than a
transcript.
## Read these first, every iteration
Short on purpose, and the reason this prompt is short:
1. `docs/port/MISSION.md` — the objective, the milestones, what is out of scope.
2. `docs/port/FORMAT.md` — the export schema. It is **versioned**; changing it is
a deliberate act with a version bump, not a silent edit.
3. `docs/re/REFUTED.md` — claims already tested and dead. Grep it for your nouns
before designing anything.
4. `docs/re/METHOD.md` — the traps this corpus has already paid for.
5. `docs/re/INDEX.md` — what is already decoded. Re-deriving a ✅ row is not a
finding.
6. `docker/agent/AGENT.md` — the container's tooling.
`docs/re/disc-atlas.html` is the map of how the assets reference each other —
useful when you need to find what feeds what.
**These files are the memory.** A finding that lives only in your context is lost
when the container dies.
## Each iteration
1. **Pick one item** from `BACKLOG.md`, preferring the one whose "first step" is
cheapest and most decisive. If you are mid-item, continue it rather than
starting another.
2. **Do the smallest experiment that could settle it**, and try to *refute* your
hypothesis before believing it. Run the known-positive through any new filter
first; a filter that fails its own control is dead, not tuneable.
3. **Write the result down** in `docs/re/` under the ✅/🟡/❔ convention, with the
evidence and the *reach* of any negative. A withdrawn result is a real
result — record it, with the reasoning.
* If you **refuted** something, add a line to `REFUTED.md`.
* If you were bitten by a general trap, add a line to `METHOD.md`.
* If you **closed** a format, update its `INDEX.md` row.
4. **Commit** to `auto/<topic>`, one logical change per commit.
5. **Publish**: `push-work`. Your branch must leave the container or the work
dies with it. See "Publishing" below.
6. **Say plainly what you did not settle**, and stop the iteration.
1. **Pick the lowest unfinished milestone** from MISSION.md. If you are mid-item,
continue it rather than starting another.
2. **Build the smallest thing that reaches its gate.** The gate is an *artifact*
a validating JSON file, a screenshot, a human-clickable build — never "it
compiles".
3. **Keep derived and authored apart.** `export/` is regenerated wholesale and is
never hand-edited; `authored/` is hand-written and survives a re-export. If you
are tempted to hand-fix a file under `export/`, the fix belongs in the exporter
or in `authored/`. Every `authored/` entry carries a `why`.
4. **Write down what you learned** — in `docs/port/` for port decisions, in
`docs/re/` for anything about the disc.
* Refuted something → a line in `REFUTED.md`.
* Bitten by a general trap → a line in `METHOD.md`.
* Closed a format → update its `INDEX.md` row.
5. **Commit** to `auto/<topic>`, one logical change per commit.
6. **Publish**: `push-work`. Every iteration that produced a commit.
7. **Say plainly what you did not settle**, and stop.
## Hard rules
* **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.
* **No Rust in the Godot project, no GDExtension.** If Godot cannot read
something, the exporter emits it differently. The wall between the two halves is
the design, not an inconvenience.
* **Do not touch `crates/sylpheed-viewer`.** The Explorer is the human's
verification tool, it is independent of the port, and it keeps its
static-data-only rule. Do not refactor it to share code with the exporter.
* **Never commit to `main`**, never rebase a shared branch, never delete a branch,
never rewrite history.
* **Do not touch another agent's worktree.** `git worktree list` first; branches
marked `+` are checked out elsewhere.
* **One emulator at a time** — `run-canary` enforces it with a lockfile.
* **Measure the oracle; never infer it.** If you need to know what the real game
does, run it. This applies with full force to the keyframe time unit and to any
transition timing.
* **Do not improvise around a blocker.** If a milestone needs a decision only the
user can make — a new dependency, a scope change, the Ready Room go/no-go —
write what you found, note it in MISSION.md, and move to the next thing you can
actually finish.
## Verifying
* `build-reborn test` wires up `SYLPHEED_DISC`; without it the disc tests
self-skip and a green run means almost nothing.
* `sylpheed-cli screen render` is the reference renderer. When Godot draws a
screen, diff it against the CLI's composite of the same build — they should
agree, and where they do not, one of them is wrong and you must say which.
* Godot runs headless in this container (`godot --headless`), and
`screenshot` captures the windowed one under Xvfb.
* A regenerated artifact that comes out byte-identical is strong evidence a
change was additive. When it does change, check that every diff line pairs.
## Publishing
@@ -47,35 +94,13 @@ transcript.
decision. Run it **every iteration that produced a commit** — not at the end of
some longer arc, which is exactly when a container dies.
If it reports no credentials, say so in your reply and continue working; do not
improvise another route out (no remote rewrite, no credential helper of your
own, no alternate transport). A push that is blocked is a blocked push.
## Hard rules
* **Never commit to `main`**, never rebase a shared branch, never delete a
branch, never rewrite history.
* **Do not touch another agent's worktree.** `git worktree list` first; branches
marked `+` are checked out elsewhere.
* **One emulator at a time** — `run-canary` enforces it with a lockfile.
* **Measure the oracle; never infer it.** An iteration that reasons about the
game without running it is a red flag unless it is a pure static-format task.
* **Verify with an artifact**, not with "it compiles": `build-reborn test` (it
wires up `SYLPHEED_DISC` — without it the disc tests silently self-skip and a
green run means almost nothing), `sylpheed-cli mesh render` / `screen render` /
`save info`, a screenshot. A regenerated artefact that comes out
byte-identical is a strong check that a change is additive; when it does
change, check that every diff line pairs exactly.
## When you are blocked
If an item needs something the container cannot do — hardware Vulkan, a decision
only the user can make — **do not improvise around it**. Write what you found,
note the blocker in `BACKLOG.md`, and move to the next item.
If it reports no credentials, say so in your reply and continue working. Do not
improvise another route out: no remote rewrite, no credential helper of your own,
no alternate transport. A push that is blocked is a blocked push.
## Pacing
One experiment plus its write-up is a good iteration; a marathon is not. Stop
One milestone step plus its write-up is a good iteration; a marathon is not. Stop
with a clean commit, a push, and an honest list of what is still open.
An emulator session must fit inside ONE turn — a Stop hook kills xenia when the