The reference renderer was a revision behind for three consecutive diff runs and
nothing said so.
After the pin bump, `build-reference-cli` reported success at rev 5414db3 and
`verify-screen` showed main_menu jumping 3/255 -> 72/255. The natural reading was
that the port had regressed. It had not: the port was right and the REFERENCE was
stale. The shared CARGO_TARGET_DIR still held a `sylpheed-cli` built from
8b6dbcf, cargo said "Finished in 0.13s" and left it there. A clean target dir
built a binary resolving ptframe1 to (440,108) t=62; the shared one still said
(620,108) t=16.
The old check -- does `screen list` run -- cannot catch this. A stale binary runs
perfectly.
So: build into `$CARGO_TARGET_DIR/reference-cli/$rev`, keyed by the pin, where a
new revision has no artifacts to reuse. Then verify the binary AGAINST export/:
both come from the same pin, so if the CLI resolves ptframe1's rest differently
from what the exporter wrote, the two halves of the verification are not the same
revision and it fails loudly. It compares the two rather than asserting a
literal, so it stays true when the pin moves again.
docker/bin is baked into the image, so this needs an image rebuild to reach
PATH; until then invoke the repo copy by path.
Two tooling fixes, both needed before P1 can diff anything.
`build-export` was calling `sylpheed-export --disc ... --out export`, which P0
broke when it moved the exporter behind `export` / `check` subcommands. It is a
regression from 8dd0577 and this is the fix. It now also runs `check` after
exporting, because `export` writes a tree and `check` is the only thing that
says the tree is readable by something other than the program that wrote it.
`build-reference-cli` is new. `sylpheed-cli screen render` is the reference the
Godot port gets diffed against, and the only copy of it in the container is
/reborn/target/release, which has two problems: the release binary predates the
`screen` subcommand entirely, and /reborn is a LIVE mount of the other agent's
working tree -- it moved from e81dcad to f5e7426 during a single iteration. A
reference renderer that runs different decoders than the exporter puts a free
variable in every pixel diff, so this builds the CLI from the same pinned
revision the exporter uses and drops it on the persistent target volume.
Both are COPY'd into the image at Dockerfile:76, so neither takes effect in a
running container until the image is rebuilt. The reference CLI is already built
into the target volume by hand, so P1 is not blocked in the meantime.
`sylph-port remote` never returned. Two bugs, one behind the other.
The glob `/sylph-home/port/.claude/**/*.jsonl` ran under `sh`, which has no
globstar, so it stayed literal and matched nothing -- the lookup could not have
succeeded however long it waited.
Fixing that exposed the real problem: the session transcript records every
command run inside the container, including this lookup, so grepping it matched
our own pattern string back and returned a truncated URL. The container log is
the right source and has no such feedback loop.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`loose` exited 126 with "File name too long": the entrypoint ended in
`exec "$@"`, so the whole loop-task.md prompt was handed to exec as a command
and the markdown was tried as a filename.
The launcher was modelled on the RE container's but the entrypoint was written
fresh and simpler, and it left out the half that matters -- the block that turns
a `/loop ...` argument into a Claude Code invocation. Ported it over, with the
two gates that make an unattended run possible: seeding ~/.claude.json so the
first-run wizard does not stop on the theme picker, and claude-autonomous, which
answers the Bypass Permissions disclaimer that has no config key to skip it.
Also mounts the host's Claude credentials, which the container had no way to
reach, and passes SYLPH_AUTONOMOUS=1 from `loose`.
Verified: container stays up, display :97 ready, /reborn mounted read-only,
Remote Control registered, Claude Code started in /work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pin was 4.3, chosen without checking; 4.7.2 is the current stable (released
2026-08-18). Getting this right matters more here than usual because the project
diffs Godot screenshots against a reference renderer, so the engine version is
part of the measurement.
No Godot MCP server. Not because they are bad -- the mature ones need a LIVE
editor plus a WebSocket plugin and a Python server, which is a daemon, an editor
process and a second runtime added to an unattended loop. And their headline
feature, scene-tree introspection and node manipulation, is built for someone
hand-authoring scenes in the editor. This port GENERATES screens from exported
JSON: the agent writes a loader, not a scene tree, so the feature that justifies
the complexity does not apply. What it actually needs to verify itself -- run
headless, screenshot, diff against sylpheed-cli screen render -- is already a
bash job.
Third-party skill packs are the opposite trade: pure context, no runtime. Worth
revisiting, deliberately not installed now, because a skill is instructions
injected into an agent running with approvals disabled -- a supply-chain
decision, not a default -- and because P0/P1 need no advanced GDScript. The
agent may propose one, naming the milestone it unblocks, for a human to vendor
and review.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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 (8b6dbcf), 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>