# ADR 0005: CraftOS-PC Harness, Periphemu Bootstrap, and Headless Probes

## Status

Accepted

## Date

2026-06-08

## Context

This repository targets CC:Tweaked on Minecraft 1.21. The Lua we ship runs inside the ComputerCraft runtime: it depends on `os.pullEventRaw`, `peripheral`, `rednet`, `textutils.serializeJSON`, modem channels, and CC-specific globals. Standard Lua cannot execute this code as-is, so a normal local test harness was never a serious option.

Contributors have been running the code in two places:

- In-game on a real Minecraft server, which is slow to iterate on.
- In **CraftOS-PC** (<https://www.craftos-pc.cc/>), a desktop emulator that ships the same ROM/BIOS as CC:Tweaked, supports modem peripherals via `periphemu`, and can run fully headless (`--cli --headless --script <file>`).

CraftOS-PC was the *de facto* local harness for months but lived only as a single line in [`AGENTS.md`](../../AGENTS.md). There was no install guide, no minimum version, and `just install` did not check the binary was present. Two related concerns emerged on top of that:

- `startup/servers.lua` historically called `periphemu.create` four times on computer 0 (a top modem, two `computer` peers at ids 1 and 2 both labelled `Trap`, and a router peer at id 10). In CraftOS-PC GUI mode this opened **four windows** on every launch, and the duplicate `Trap` label plus persistent per-id state across versions caused recurring confusion.
- Headless CraftOS-PC is also a cheap, deterministic *interactive* tool: it boots the emulator, runs an arbitrary Lua snippet against the real CC:Tweaked ROM, prints output, and exits in well under a second. Humans and agents can use it to verify hypotheses about CC:Tweaked behavior *before* writing code or tests. That usage was implicit; no document framed headless exec recipes as the recommended first move when an agent is unsure about CC:Tweaked behavior.

## Decision

### 1. CraftOS-PC as a first-class local dev dependency

- **Minimum version `v2.8.3`** — recent enough to have the current CC:Tweaked ROM, old enough that contributors already on a 2.8.x build will not be forced to upgrade again.
- **Documented install** in [`docs/install-craftos-pc.md`](../install-craftos-pc.md), with a SHA-256-verified macOS flow and pointers to the official Windows/Linux artifacts.
- **Documented upstream navigation** in [`docs/craftos_pc_glossary.md`](../craftos_pc_glossary.md), covering CLI flags, mounts, `periphemu`, save data, and troubleshooting pages.
- **Verified by `just install`** via `check-install`, which checks `craftos`, `jq`, `luacheck`, and `openssl`. `check-craftos` runs `craftos --version` and requires v2.8.3 or newer. Failure prints a one-line pointer to the install guide.
- **Repository-local TrapOS launch.** `just trapos` runs CraftOS-PC with `--directory .craftos`, keeps the macOS `--rom /Applications/CraftOS-PC.app/Contents/Resources` workaround, mounts the repository root read-only at `/trapos`, and mounts each top-level source directory read-only at its ComputerCraft root path.
- **Vanilla launch.** `just craftos` launches CraftOS-PC under `.craftos-vanilla/` with no mounts and no startup, for probes that should not see TrapOS files and for the `just trapos-install` end-to-end install verification.
- **`just ci` is the local verification entry point.** It runs `check-craftos`, `check`, and `test`. Local Git hooks are installed by `just install`; see [ADR-0011](adr-0011-repo-conventions.md) for the commit/push split.

The existing [`AGENTS.md`](../../AGENTS.md) constraint ("Do not run Lua locally or add a test harness unless asked") is reframed rather than removed: there is still no standalone Lua harness, and we are not adding a Busted-style test runner. The harness *is* CraftOS-PC, invoked deliberately.

### 2. Periphemu bootstrap stays minimal

`startup/servers.lua` attaches **only a top modem** under `periphemu`:

```lua
if periphemu then
  periphemu.create('top', 'modem');
end
```

Extra emulated computers are spawned manually from the CraftOS-PC shell when actually needed (e.g. `periphemu create 10 computer` to bring up a router peer for cross-VM testing). The pattern is documented in [`docs/periphemu.md`](../periphemu.md). The `if periphemu then` guard is preserved so in-game behavior is unchanged.

### 3. Headless probes as the canonical hypothesis pattern

Two safe-exec recipes wrap raw `--headless --exec` with `xpcall`, call `os.shutdown()` on success or Lua error, and use `TRAPOS_HEADLESS_TIMEOUT_SECONDS` (default `10`) as a host watchdog:

- `just trapos-exec '<lua>'` — probe against the **TrapOS dev environment**. Mounts of `/apis`, `/programs`, `/servers`, `/startup`, `/tests`, and the repo root at `/trapos` are live, so `require('/apis/eventloop')` and friends work against the current branch. Use this when the question involves repo code.

- `just craftos-exec '<lua>'` — probe against **vanilla CraftOS-PC**. No mounts, no startup scripts. Use this when the question is purely about CC:Tweaked behavior and TrapOS files would be a distraction, or to confirm a behavior is upstream rather than something the dev env layered on.

- `just trapos-install` — drive the full real install (`install-trapos.lua`, which bootstraps ccpm then runs `ccpm update` + `ccpm install trapos` itself) on a fresh ephemeral state. This is the probe to run when changing anything in the install path itself.

Conventions:

- Prefer the safe exec recipes over raw `--headless --exec`.
- Keep snippets minimal and side-effect-free. If a probe reveals a fact worth defending, add a `libtest` case under `tests/` — probes are not a substitute for committed tests.
- LLM agents SHOULD prefer a quick headless probe over speculation when answering "does X work in CC:Tweaked?" or "does my refactor still load?". The cost is one extra emulator boot (~1s); the benefit is grounded answers instead of plausible-sounding ones.

## Consequences

- Contributors must install CraftOS-PC before `just install` succeeds. The install guide makes this a 4-step copy/paste on macOS.
- Headless tests live under `tests/` and are driven by `just test`. See [ADR-0007](adr-0007-test-framework.md) for the runner and timeout layering.
- The macOS install symlinks the binary into `/usr/local/bin`, which makes CraftOS-PC unable to auto-discover the ROM that ships inside the `.app` bundle (`Could not mount ROM`). The `test:` recipe works around this by passing `--rom /Applications/CraftOS-PC.app/Contents/Resources` on Darwin. Linux (AppImage) and Windows (installer) auto-discover correctly.
- `just trapos` uses repository-local save data under `.craftos/config/` and `.craftos/computer/`. This keeps emulator state out of `~/Library/Application Support/CraftOS-PC` during repository work and keeps repo files visible through read-only mounts instead of copying them into the VM save.
- `just repl` is a human-only interactive wrapper around `just trapos --cli`; automation and LLM agents must use `just trapos-exec '<lua>'` or `just craftos-exec '<lua>'` instead.
- `craftos` (GUI) now opens a single unlabelled `Computer 0` window with a top modem attached. Cross-machine testing requires an explicit `periphemu create` call from the shell rather than being implicit on boot — one extra command when you need a peer, no surprise windows or persisted ghost VMs.
- `.craftos-vanilla/` is in `.gitignore` alongside `.craftos/`.
- `just trapos-install` is *not* part of `just ci`: it is network-dependent and slower than `just test`. Run it manually when touching `install-trapos.lua` or ccpm package descriptors.
- Higher CraftOS-PC invocation traffic during agent sessions; cheap enough that this is a good trade.
- The harness version becomes a project-level concern. When CC:Tweaked ships breaking changes that require a newer CraftOS-PC build, we bump the minimum version in [`docs/install-craftos-pc.md`](../install-craftos-pc.md) and `check-craftos` keeps contributors honest.
- No CI integration yet. Running CraftOS-PC headless in GitHub Actions is feasible (the AppImage works on Ubuntu runners) but is out of scope; the contract is local-only for now.

## Future Work

- **API-loading smoke test.** Extend `tests/` with a script that `require`s `/apis/eventloop`, `/apis/net`, `/apis/libtest`, and the router, asserting the wiring loads without errors.
- **CI.** Run `just test` on push using the Linux AppImage.
- **Pinned ROM.** Point CraftOS-PC at a vendored ROM via `--rom` if we ever need to test against a specific in-game version.
- If `tests/` grows a multi-VM scenario, drive peer creation from the test script itself (each `tests/*.lua` already owns its setup) rather than re-adding peers to `startup/servers.lua`.
