This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docker/agent/Dockerfile
Sylpheed RE agent b5a193839c 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>
2026-08-28 17:11:35 +02:00

166 lines
9.2 KiB
Docker

# Autonomous RE agent container for Project Sylpheed.
#
# Builds and runs BOTH halves of the project — Xenia Canary (C++/CMake/Ninja)
# as the behaviour oracle, and Sylpheed Reborn (Rust/Bevy) as the port — plus
# the dynamic-RE toolkit that drives the emulator and reads its guest memory.
#
# Three things here exist because their absence cost the previous agent real
# hours, and they are load-bearing rather than nice-to-have:
#
# 1. A REAL toolchain. The old box shipped runtime sonames only (libgtk-3.so.0
# but no libgtk-3.so), no cmake/ninja/clang and no libstdc++fs, so a full
# build was impossible and `tools/re-capture/rebuild_canary.sh` had to
# hand-relink object files. With -dev packages present that script is
# obsolete; use `build-canary`.
# 2. numpy and Pillow. Their absence silently disabled entities2.py,
# flight_probe.py and every image oracle, and the failure looked like a
# logic bug rather than a missing package.
# 3. A display that outlives the turn. Xvfb kept dying "on its own every few
# minutes"; it was being reaped because nothing owned it. Here it is a
# child of PID 1 and lives exactly as long as the container.
#
# Clang is pinned to 19 to match the host that produced the checked-in build
# caches (Ubuntu clang 19.1.1).
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
TZ=Etc/UTC
# ── System packages ──────────────────────────────────────────────────────────
RUN apt-get update && apt-get install -y --no-install-recommends \
# toolchain
build-essential cmake ninja-build pkg-config git curl wget ca-certificates \
clang-19 lld-19 llvm-19 libc++-19-dev libc++abi-19-dev \
# Canary: GTK window, SDL input/audio, Vulkan, compression
libgtk-3-dev libsdl2-dev liblz4-dev libvulkan-dev libx11-xcb-dev \
libxcb1-dev libxrandr-dev libssl-dev libfuse2t64 \
# Shader toolchain: the GPU build shells out to `glslangValidator` and the
# SPIR-V tools to compile xenia's own shaders. Missing them does not fail
# configure — it fails ~500 objects in, as a Python FileNotFoundError.
glslang-tools spirv-tools spirv-headers \
# Vulkan runtime — lavapipe (software) plus the real ICDs for /dev/dri
mesa-vulkan-drivers vulkan-tools libvulkan1 libgl1-mesa-dri libglx-mesa0 \
# Reborn / Bevy: audio, input, windowing
libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev \
libx11-dev libxi-dev libxcursor-dev libxinerama-dev libxext-dev \
# headless display + window manager + the screenshot path
xvfb x11-utils x11-xserver-utils openbox xdotool imagemagick ffmpeg \
# dynamic RE
python3 python3-numpy python3-pil python3-pip \
gdb strace ltrace binutils file xxd ripgrep jq unzip zip p7zip-full \
procps psmisc lsof less nano tini sudo \
# expect drives Claude Code's one-time interactive gates for an
# unattended run — see bin/claude-autonomous.
expect \
&& rm -rf /var/lib/apt/lists/*
# Pin the unversioned tool names to 19 so CMake, and anything that shells out to
# `clang`, agree with what the caches were built by.
RUN for t in clang clang++ lld ld.lld llvm-ar llvm-ranlib llvm-nm clang-cpp; do \
src="/usr/bin/${t}-19"; \
[ -e "$src" ] && update-alternatives --install "/usr/bin/${t}" "$t" "$src" 200 || true; \
done
# duckdb reads the static-analysis database (sylpheed.db); it is not packaged.
# PEP 668 marks the system env externally-managed, and this image has no other
# 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 \
&& npm install -g @anthropic-ai/claude-code \
&& npm cache clean --force \
&& rm -rf /var/lib/apt/lists/*
# ── The agent user ───────────────────────────────────────────────────────────
# NOT root, and not negotiable: Claude Code refuses --dangerously-skip-permissions
# when it has root privileges. uid/gid 1000 matches the host account so files
# written into the bind-mounted repos keep the right ownership.
ARG AGENT_UID=1000
ARG AGENT_GID=1000
# Ubuntu 24.04 ships its own `ubuntu` account at uid/gid 1000, so the common
# case — matching a host user who is also 1000 — collides with it. Remove the
# stock account first; nothing in this image uses it.
RUN if getent passwd "${AGENT_UID}" >/dev/null; then \
userdel -r "$(getent passwd "${AGENT_UID}" | cut -d: -f1)" 2>/dev/null || true; \
fi; \
if getent group "${AGENT_GID}" >/dev/null; then \
groupdel "$(getent group "${AGENT_GID}" | cut -d: -f1)" 2>/dev/null || true; \
fi; \
groupadd -g "${AGENT_GID}" agent \
&& useradd -m -u "${AGENT_UID}" -g "${AGENT_GID}" -s /bin/bash -d /sylph-home/re agent \
&& mkdir -p /sylph-home/re /work \
&& chown -R "${AGENT_UID}:${AGENT_GID}" /sylph-home \
&& echo 'agent ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/agent
COPY bin/ /usr/local/bin/
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/* /usr/local/bin/entrypoint.sh
USER agent
WORKDIR /work
# ── Rust ─────────────────────────────────────────────────────────────────────
# CARGO_TARGET_DIR deliberately points OUTSIDE the bind-mounted repo: the host
# also builds Reborn, and sharing target/ makes the two invalidate each other's
# incremental state on every switch.
ENV RUSTUP_HOME=/sylph-home/re/.rustup \
CARGO_HOME=/sylph-home/re/.cargo \
CARGO_TARGET_DIR=/sylph-home/re/target-container \
PATH=/sylph-home/re/.cargo/bin:/usr/local/bin:/usr/bin:/bin
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --default-toolchain stable --profile minimal \
--component clippy --component rustfmt \
&& rustup target add wasm32-unknown-unknown
# trunk serves the Reborn viewer's wasm build; the release binary avoids a
# ten-minute `cargo install`.
RUN curl -fsSL https://github.com/trunk-rs/trunk/releases/download/v0.21.4/trunk-x86_64-unknown-linux-gnu.tar.gz \
| tar -xz -C /sylph-home/re/.cargo/bin trunk
# Create the volume mount points HERE, owned by `agent`. Docker seeds an empty
# named volume from whatever the image has at that path — including ownership —
# but if the path does not exist it creates a root-owned directory instead, and
# the first write fails with something as unhelpful as
# "CMake Error: Unable to (re)create the private pkgRedirects directory".
RUN mkdir -p /sylph-home/re/target-container /sylph-home/re/canary-build /sylph-home/re/.claude
# ── Runtime environment ──────────────────────────────────────────────────────
# DISPLAY :98 and HOME /sylph-home/re are what tools/re-capture/*.sh already
# assume; keeping them means the existing toolkit runs unmodified.
ENV HOME=/sylph-home/re \
DISPLAY=:98 \
SCREEN_GEOMETRY=1280x720x24 \
PROJECT_DIR=/work \
XENIA_PAD_FILE=/tmp/xenia_pad.txt \
XENIA_BUILD_DIR=/sylph-home/re/canary-build \
SDL_AUDIODRIVER=dummy \
LIBGL_ALWAYS_SOFTWARE=1 \
PATH=/work/Syplheed-Reborn/tools/re-capture/bin:/work/Syplheed-Reborn/tools/re-capture:/sylph-home/re/.cargo/bin:/usr/local/bin:/usr/bin:/bin
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"]
CMD ["bash"]