Files
Sylpheed/docker/agent/AGENT.md
MechaCat02 64f9d15610 docker: a container the autonomous RE agent can be turned loose in
Builds and runs both halves of the project -- Canary as the oracle, Reborn as
the port -- plus the dynamic-RE toolkit. Claude Code runs with
--dangerously-skip-permissions as an unprivileged `agent` user, because that
flag is refused under root.

Capped at half the machine, computed at launch: --cpus nproc/2, --memory half
of MemTotal with --memory-swap equal to it (no swap escape hatch -- a swapping
build thrashes the host, which is the failure the cap exists to prevent), and
build parallelism derived INSIDE the container from available memory rather
than core count, since a full-parallel build of this tree has OOM-killed the
host outright.

Three things the old box got wrong are fixed rather than reproduced: a real
toolchain (so rebuild_canary.sh's hand-relinking is obsolete), numpy and Pillow
(whose absence silently disabled every image oracle and looked like a logic
bug), and a display owned by PID 1 (so Xvfb no longer "dies on its own every
few minutes" -- it was being reaped because nothing owned it).

Verified end to end, not by inspection: the image builds, sylph-doctor is green,
`build-canary` links xenia_canary inside the container, and that binary then
runs -- guest memory and the JIT code cache appear in /dev/shm within 4 s,
1 205 log lines, gmem.py reads guest RAM, pad.py drives the file pad, and
screenshot captures the display.

Five environment defects found and fixed on the way, each of which fails in a
way that points somewhere else entirely:

  * /dev/shm is `noexec` under Docker. Xenia maps its JIT code cache out of an
    shm file, so it died with "Unable to allocate code cache generated code
    storage / Cannot initalize processor" -- which reads as an address-space
    clash, not a mount flag. Now `--tmpfs /dev/shm:rw,exec`.
  * An unknown xenia flag HANGS rather than errors: ParseLaunchArguments calls
    ShowSimpleMessageBox before logging is initialised, and that SDL dialog
    blocks on XIfEvent forever. `--audio` (which the RE notes recommend) is not
    a cvar in this tree; the symptom was a 10x10 window and an empty log.
  * Named volumes come up root-owned unless their mount point exists in the
    image, so the first cmake configure failed on pkgRedirects.
  * Ubuntu 24.04 ships its own uid-1000 account, colliding with the host user.
  * Ubuntu's spirv-opt has no --canonicalize-ids, so the shader step dies ~500
    objects in; the launcher mounts the host's LunarG SDK instead of baking one
    in, which also keeps shader output byte-identical to a host build.

Known limits, stated rather than papered over: on an NVIDIA host without the
NVIDIA Container Toolkit there is no hardware Vulkan (/dev/dri alone does
nothing for NVIDIA), and under lavapipe the emulator runs correctly but was not
observed to reach a rendered frame within a couple of minutes. gdb needs `sudo`
inside the container because the host's yama ptrace_scope outranks SYS_PTRACE.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 23:05:48 +02:00

3.0 KiB

Notes for an agent working inside this container

Read this before starting a dynamic-RE run. Everything here is something that already went wrong once.

The container fixes three old traps for you

  • The display outlives the turn. Xvfb and openbox are children of PID 1, not of your shell. The old "Xvfb dies on its own every few minutes" note is gone — you no longer have to wrap a whole session in one blocking foreground call to keep it alive.
  • The toolchain is real. tools/re-capture/rebuild_canary.sh exists because the old box had no cmake/ninja/clang and only runtime sonames, so it hand- relinked object files. Do not use it here. Use build-canary.
  • numpy and Pillow are installed. entities2.py, flight_probe.py and the image oracles work. Their absence used to look like a logic bug.

Method (the part that matters more than the tooling)

  • Measure the oracle; never infer it. A session with zero Canary runs is a red flag.
  • Trace upstream to where data first goes wrong, rather than patching the symptom you can see.
  • Try to refute before believing. Record demotions rather than editing them away — docs/re/README.md has the /🟡/ convention, and a withdrawn result is more useful than a quietly deleted one.
  • A probe that never performs the action will "prove" the action does not exist. The "targeting is automatic" conclusion came from a sweep that only ever tapped once; target select is Ⓐ pressed twice.
  • Do not poll faster than the guest updates — it manufactures a clean curve out of noise. rate-curve-aliased-BAD.csv is committed as the bad example.

Running the emulator

run-canary                       # correct audio/pad/display flags baked in
pad.py tap A ; pad.py dpad down  # scripted input (--hid=file, no uinput)
screenshot ~/shots/now.png       # cropped to the GAME surface, not the window
python3 tools/re-capture/gmem.py find hex:820af844 400
  • One emulator at a time. run-canary enforces it with a lockfile.
  • Boot is slow cold, ~25 s once the shader/code caches are warm — so a launch-and-dump fits in a single call.
  • Screens: classify by whole-image statistics (screen_id.py), not named pixels. Named-pixel oracles are only valid while the game image sits at a known place, and nothing errors when it moves.

Verifying your own work

  • Reborn's disc-gated tests self-skip without SYLPHEED_DISC. A green run with it unset means almost nothing. build-reborn test wires it up for you.
  • Prefer a headless self-verify over "it compiles": sylpheed-cli mesh render, screen render, save info all produce checkable artifacts.
  • 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.

Reporting

State what you measured, what you assumed, and what you could not settle. If a result is withdrawn, say so and keep the reasoning — that is the corpus's whole convention, and the reason its numbers can be trusted.