From 4b62252d94cf8c90aad2090a9d734c813355362a Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sun, 6 Sep 2026 14:01:39 +0200 Subject: [PATCH] docs: handoff for resuming on another machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- docs/agents/HANDOFF-2026-09-06.md | 247 ++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 docs/agents/HANDOFF-2026-09-06.md diff --git a/docs/agents/HANDOFF-2026-09-06.md b/docs/agents/HANDOFF-2026-09-06.md new file mode 100644 index 00000000..49310cf2 --- /dev/null +++ b/docs/agents/HANDOFF-2026-09-06.md @@ -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 `, **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.**