docs: handoff for resuming on another machine
Written to be read cold by a session with no context: what is established versus what is someone's claim, which of the four actors the gate actually binds, and what is in flight but unpushed. The transferable part is §7 -- one failure shape recurred five times in two days across two agents and the assistant: a property inferred from something ADJACENT to it rather than tested directly. Protection from a settings page, reachability from a DNS record, a label count from a grep that matched the function definition, Gitea's host from a DNS lookup, and a clippy version from rustfmt output parity. The last is the sharpest: rustfmt 1.8.0 and 1.9.0, nine months apart, both emit 774 hunks here, so formatting parity carries no information about which clippy ran -- and the conclusion drawn from it (that CI's green was cached or ungated) was wrong in a way that would have sent someone chasing a phantom.
This commit is contained in:
247
docs/agents/HANDOFF-2026-09-06.md
Normal file
247
docs/agents/HANDOFF-2026-09-06.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# Handoff — 2026-09-06
|
||||
|
||||
**For a fresh session on a different machine.** Written to be read cold: it
|
||||
assumes you know nothing about what happened, and it says what is *established*
|
||||
versus what is *someone's claim*.
|
||||
|
||||
---
|
||||
|
||||
## 1. What this project is
|
||||
|
||||
Two things, and they are easy to confuse:
|
||||
|
||||
* **The long game** — *Project Sylpheed: Arc of Deception — Reborn*, a clean-room
|
||||
native port of an Xbox 360 game in Rust + Bevy. Values and behaviour come from
|
||||
the original by **observation and static RE only** — never copied decompiled
|
||||
code. The oracle is **the real game running in Xenia Canary**, never any
|
||||
renderer of ours.
|
||||
* **The work of 2026-09-04/06, which is what this document is about** — moving
|
||||
the project's *working surface* onto a self-hosted Gitea, and getting CI to
|
||||
produce an answer for the first time.
|
||||
|
||||
If you only read one other file, read [`PROTOCOL.md`](PROTOCOL.md).
|
||||
|
||||
## 2. The actors — four, and only two are constrained
|
||||
|
||||
| | what it is | identity | constrained by the gate? |
|
||||
|---|---|---|---|
|
||||
| **the human** | directs everything; the only approver and merger | `fabi` | n/a — is the gate |
|
||||
| **the Pi agent** | supervisor, runs on the Pi beside Gitea | `sylph-pi <pi@sylpheed.local>`, **no Gitea account** | ❌ has `gitea admin`; can mint tokens, edit rules |
|
||||
| **you** (this session) | runs on the x86_64 desktop, holds the push credential | commits as the human ⚠️ | ❌ same carve-out, different mechanism |
|
||||
| **the two loop agents** | Decoder (disc→meaning) and Port (disc→playable) | `sylph-decoder`, `sylph-port`, Write not Admin | ✅ |
|
||||
|
||||
⚠️ **The looping agents are stopped and must stay stopped** until their images
|
||||
carry the Gitea MCP and their issues are ready. Starting them early gives them a
|
||||
brief telling them to read notifications and open issues with no tool that can.
|
||||
|
||||
📌 The two *supervising* agents are the ones the gate does not bind. That is
|
||||
written into `GITEA-SETUP.md` Phase 2 deliberately. Neither has a distinct Gitea
|
||||
identity; both operate through the human's credential or an unlinked git author.
|
||||
**That is a known, unresolved wart**, not an oversight.
|
||||
|
||||
## 3. The machines
|
||||
|
||||
| | | |
|
||||
|---|---|---|
|
||||
| **desktop** `fabi-Hyrican-PC` | x86_64, 12 core, 15 GB | agent containers, the repo clone, **the only push credential** |
|
||||
| **the Pi** `raspberrypi.fritz.box` | aarch64, on the LAN | **runs Gitea** (published through a VPS), the CI runner, and the supervising agent |
|
||||
| **Gitea** `git.mc02.dev` | 1.25.5 | resolves to a hosted address — that says nothing about the origin, which is the Pi |
|
||||
|
||||
**File transfer between them is by hand.** The Pi agent has no push credential,
|
||||
so its work arrives as `git bundle` over `scp`, which the human runs. This is the
|
||||
weakest link in the setup: four round trips on 2026-09-04, each needing a
|
||||
password twice, and once a guessed filename that was wrong. A `write:repository`
|
||||
token on the Pi scoped to `pi/*` branches would remove it — **a deliberate
|
||||
decision, deliberately not taken yet.**
|
||||
|
||||
⚠️ **`CARGO_BUILD_JOBS=4`** and limited `-j`. A full-parallel build has
|
||||
OOM-crashed the desktop. One emulator process at a time; Canary runs muted.
|
||||
|
||||
## 4. Where things stand
|
||||
|
||||
### Merged to `main`
|
||||
Nothing since `59649824`. **`main` is protected** — `enable_push=false`, 1
|
||||
approval required, merge and approvals both whitelisted to `fabi` only. Verified
|
||||
behaviourally: a real push was refused with `pre-receive hook declined`, as the
|
||||
repository owner.
|
||||
|
||||
### Open pull requests
|
||||
|
||||
| | head | what | why not merged |
|
||||
|---|---|---|---|
|
||||
| **#10** | `agents/gitea-mcp` @ `a3d99ada` | the Gitea surface: MCP wiring, `gitea-protect`, the runbook, two PROTOCOL rules | waiting on the human. Merge-on-merits: docs and tooling, no `.rs` |
|
||||
| **#14** | `fix/clippy-lints` @ `d8807c4f` | 73 clippy lints → 0 across four crates, `Closes #13` | same |
|
||||
|
||||
🔴 **Both need the admin override to merge.** `fabi` authored them and is the
|
||||
only whitelisted approver, and Gitea bars self-approval — so they can never reach
|
||||
one approval. `block_admin_merge_override` is `false` *deliberately* so that door
|
||||
stays open. Do not tick it.
|
||||
|
||||
### Open issues
|
||||
|
||||
Nine work items, all `state/approved`, awaiting the loop agents:
|
||||
|
||||
```
|
||||
#1 F1 measure (decoder) ←blocks— #2 F1 implement (port)
|
||||
#3 F2 disc gain (decoder) ←blocks— #4 F2 playback gains (port)
|
||||
#5 F3 title cue (decoder)
|
||||
#6 OPTIONS re-propose (port)
|
||||
#8 F5/F6 findings (decoder) ←blocks— #7 F5/F6 port work (port)
|
||||
#9 f6-out-of-sample residue (decoder)
|
||||
```
|
||||
|
||||
Four infrastructure issues:
|
||||
|
||||
* **#11** `state/proposed` — WASM. **Work exists past its shape; see §6.**
|
||||
* **#12** `state/proposed` — rustfmt: 774 hunks, deliberately deferred.
|
||||
* **#13** `state/approved` — clippy; `#14` closes it.
|
||||
* **#15** `state/proposed` — the lint gate floats `@stable`.
|
||||
|
||||
### Unmerged branches
|
||||
|
||||
```
|
||||
agents/gitea-mcp 11 → PR #10
|
||||
fix/clippy-lints 16 → PR #14
|
||||
auto/frame-blend-draw-path 495 the Decoder's corpus — returns via #8
|
||||
auto/port-p6-audio 366 the Port's work — returns via #7
|
||||
```
|
||||
|
||||
The last two are the reason `#7` depends on `#8`: `port/scripts/boot.gd` cites
|
||||
`docs/re/` pages that exist on **neither** its own branch nor `main`.
|
||||
`tools/port/check-citations --for-merge` counts **19** such citations.
|
||||
|
||||
## 5. CI — it produced its first answer on 2026-09-05
|
||||
|
||||
Before that: **23 runs cancelled, 2 waiting, zero successes.** The workflow
|
||||
described GitHub's hosted fleet (`windows-latest`, `macos-latest`, and a
|
||||
`--target x86_64` cross-compile) on a one-runner aarch64 instance, so the run
|
||||
never reached a terminal state — the checks were *unfinished*, not red.
|
||||
|
||||
Now, three jobs, all terminal:
|
||||
|
||||
| job | state | |
|
||||
|---|---|---|
|
||||
| **Native — linux** | **green**, three runs running | `check` `build` `test` `clippy` all pass on aarch64; **207 passed, 0 failed, 14 ignored, 30 suites** |
|
||||
| WASM — Web | red | **#11** |
|
||||
| Formatting | red | **#12** — 774 hunks, identical on `main` |
|
||||
|
||||
The workspace being portable to ARM was unknown before this and is now
|
||||
established. The disk exhaustion that broke the test link is retired: 46 GB
|
||||
reclaimed, `/` at 55%.
|
||||
|
||||
⚠️ **`/var/lib/docker` is still on the Pi's 117 GB SD card while a 916 GB SSD sits
|
||||
at 16%.** This will recur. The fix is `data-root` in `daemon.json` plus a Docker
|
||||
restart, and it wants a moment when every container going down is fine.
|
||||
|
||||
## 6. 🔴 What is in flight and NOT pushed
|
||||
|
||||
**The WASM work.** The Pi agent has `ba6c5da`, bundle at
|
||||
`/tmp/sylph-wasm-compile.bundle` **on the Pi**, base `d8807c4`. It makes
|
||||
`Check WASM compile` exit 0. #11 turned out to be **three stacked blockers**,
|
||||
each invisible until the previous was gone:
|
||||
|
||||
1. `getrandom` needs `--cfg getrandom_backend="wasm_js"` **and** the feature —
|
||||
its own error says either alone is insufficient;
|
||||
2. `sylpheed-formats` declared `tokio` as a **normal** dependency it never used,
|
||||
dragging `tokio/full` → `net` → `mio`, which does not build for wasm32;
|
||||
3. `bevy_egui` needs `--cfg web_sys_unstable_apis`.
|
||||
|
||||
**Two things the human must decide before it lands:**
|
||||
|
||||
* **#11 is `state/proposed` and this goes past its stated shape.** It was written
|
||||
around the getrandom error alone. The tokio removal is a change to another
|
||||
crate, not CI config — look at that one specifically.
|
||||
* **It will not turn the job green.** It unblocks two steps that have never run:
|
||||
`jetli/trunk-action@v0.5.0` and `trunk build --release`. The action's bundled
|
||||
`dist/index.js` names `x86_64-unknown-linux-gnu` once and `aarch64` **never**,
|
||||
so it will fetch an x86_64 binary onto the aarch64 runner. Upstream *does*
|
||||
publish `trunk-aarch64-unknown-linux-gnu.tar.gz` — so this is "the action
|
||||
cannot find it", not "trunk is unavailable on arm". Replacing the install step
|
||||
means picking a version and a method: a decision, not a slip-in.
|
||||
|
||||
## 7. The method lessons — the most transferable part
|
||||
|
||||
One failure shape recurred **five times in two days**, across two agents and the
|
||||
human's assistant. Every instance is: **a property inferred from something
|
||||
adjacent to it, rather than tested directly.**
|
||||
|
||||
| what was inferred | from what | how it failed |
|
||||
|---|---|---|
|
||||
| `main` is protected | the settings page | merging ignores the push whitelist; both agents could have approved each other |
|
||||
| the desktop can't reach Gitea | `curl` being refused | that was a *permission prompt*, not the network |
|
||||
| the tool creates 12 labels | `grep -c '^mklabel'` | one match was the function **definition** |
|
||||
| Gitea is not on the Pi | a DNS lookup | it is, published through a VPS |
|
||||
| CI's clippy == mine | **identical rustfmt output** | rustfmt is output-stable by design; clippy moves lints between groups |
|
||||
|
||||
The last one is the sharpest. `rustfmt 1.8.0` and `1.9.0`, nine months apart,
|
||||
both produce **774** hunks on this tree — so formatting parity carries *no*
|
||||
information about which clippy ran. From it I concluded CI's green must be cached
|
||||
or ungated ("the frozen splash again"). It was neither: `collapsible_else_if` is
|
||||
`warn` on 1.92.0 and **`allow`** on 1.98.1, which is what the runner has.
|
||||
|
||||
> **The check that worked, every time, was counting the same thing with the same
|
||||
> tool against a baseline.**
|
||||
|
||||
Three rules now in `PROTOCOL.md`, each earned:
|
||||
|
||||
* **A finding reaches `main` before the code that cites it.**
|
||||
* **A check may only soften against a condition it can test** — *can this branch
|
||||
tell the difference between "not yet" and "no longer"?*
|
||||
* **If you are writing the softening in the same commit as the check, the thing
|
||||
you want is an issue, not a flag.**
|
||||
|
||||
And the ordering constraint that is not obvious: **the cheap-looking fix for #12
|
||||
is the expensive one.** A whole-tree reformat before #7 and #8 return would put a
|
||||
conflict in every file of 861 commits and make the reviews those items exist to
|
||||
enable unreadable. Measured: 133 files carrying 81% of the debt cannot collide;
|
||||
the real blocker is **21 files**. Land #7/#8 first, then sweep once.
|
||||
|
||||
## 8. Resuming — concrete first steps
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git switch fix/clippy-lints # PR #14's head, d8807c4f
|
||||
python3 tools/gitea-protect --verify # expect: protection holds. rc=0
|
||||
```
|
||||
|
||||
Then, in order of what is actually blocking:
|
||||
|
||||
1. **Merge #10 and #14** — human, with the admin override. Everything else is
|
||||
downstream: the loop agents clone `main`, which has none of this.
|
||||
2. **Decide #11's scope**, then fetch `/tmp/sylph-wasm-compile.bundle` from the
|
||||
Pi and push it.
|
||||
3. **Decide the trunk-action replacement** (§6).
|
||||
4. **Then Phase 7** of [`GITEA-SETUP.md`](GITEA-SETUP.md): start the **decoder
|
||||
alone**, watch one full iteration — notifications polled, a PR rather than a
|
||||
bare push, no merge button — before starting the port.
|
||||
|
||||
### Credentials, all on the desktop, all `chmod 600`
|
||||
|
||||
```
|
||||
~/.sylph-git-credentials write:repository — the push credential
|
||||
~/.sylph-gitea-token-decoder the decoder agent's, four scopes
|
||||
~/.sylph-gitea-token-port the port agent's, four scopes
|
||||
~/.sylph-claude-token long-lived Claude auth for the containers
|
||||
```
|
||||
|
||||
`~/.sylph-gitea-api-token` (`write:issue`) lives **on the Pi**, because that is
|
||||
where `tools/gitea-setup` runs. Never paste any of them into chat.
|
||||
|
||||
⚠️ **This desktop was updated to `rustc 1.98.1` on 2026-09-06** to match the
|
||||
runner. Anything verified here *before* that ran on 1.92.0 — the fmt counts (774)
|
||||
and test counts (207/0/14) matched CI exactly and therefore carry; a clippy
|
||||
result from before does not.
|
||||
|
||||
## 9. Standing constraints
|
||||
|
||||
* **Never commit game content**, under any directory name. 545 MB reached a
|
||||
branch on 2026-09-04 under a name the ignore list did not happen to mention.
|
||||
`.gitignore` now describes the *shape* (`/export*/`, media by extension).
|
||||
* **Agents never merge**, never push to `main`, never rewrite history. One
|
||||
force-push was authorised, once, to fix commit authorship before merge — a
|
||||
recorded exception, not a precedent.
|
||||
* **The Explorer shows static data only** — the ISO, the embedded PE, savegames.
|
||||
Never anything generated by a Sylpheed run.
|
||||
* **Never self-screenshot**; ask the human to capture. Ask before a
|
||||
watch-and-verify `--ui` launch.
|
||||
* **Never judge emulator crash or stability from a Bash-launched run.**
|
||||
Reference in New Issue
Block a user