> For the complete documentation index, see [llms.txt](https://docs.kouch.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kouch.dev/docs/agents/testing.md).

# Testing rigs: how agents verify Kouch without the owner's hands or eyes

The owner's monitors are usually off while agents work, and Steam Input only activates for a process Steam started. So every check here is built to run unattended and to leave evidence (PNGs, logs, JSON) that an agent reads back before claiming anything. `CLAUDE.md` has the commands; this file has the rigs, their traps, and who may touch what.

## Owner's-machine rules that apply to every rig

* **Never start, restart or close the owner's Steam** (it asks for their account). Before any `steam://` launch, check that `steam.exe` is running and no `steamwebhelper` window is titled "Sign in…".
* **Never drive an instance the owner is testing.** Read-only CDP evals only; reproduce interactively in the mock or an isolated build on its own port.
* **Nothing we run makes a sound** (owner, 2026-09-29). Every headless Chrome gets `--mute-audio` (the shared drivers do). Every real Kouch started for a check gets `--mute-audio` in `WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS`. The Kouch Lab wrapper's agent routes are muted, and the owner's `rc`/`dist` routes aren't. The Deck is muted during tours (the scripts restore the owner's setting).
* **One heavy job at a time.** A Windows release build, a Linux Docker build and a Deck profile at once pushed the PC into a memory reap (2026-09-29). Check `docker ps` and free memory before starting a build. Never spawn one process per folder (a `Start-Job` per Temp folder once made 3,600 PowerShell processes).
* **Back up any existing user config before touching it, and restore it after:** emulator configs, Steam controller settings, the Deck's `shortcuts.vdf` and power settings, the second PC's `settings.json`.
* **`R:\Emulation` is read-only.** Add it as a library root only in temp configs.
* **Disk:**

  * the Linux target volume's `debug/` grows to \~80 GB;
  * Chrome profiles are \~150 MB each;
  * agent clones grow to \~100 GB.

  Clean with the recipe in memory `disk-space-hogs`, never an agent's clone but your own.

## 1. The mock in a plain browser (fastest)

`npx vite --port <port>` in `app/ui` serves the UI with the in-memory mock transport (see `CLAUDE.md` → UI → Transports).

* **Use your own port per session.** 1420 is the Steam route's devUrl; A uses 1430, C 1441/1446, B 1451, central 1450.
* **URL switches:**
  * `?setup=skip` skips the first-run wizard;
  * `?mock=<scenario>` sets up a state: `pads-0|1|4|8|16`, `invite`, `online`, `rich-chat`, `avatar-items`, `broken-frame`, `discord-me`, `friends-40`, `no-emulators`, `no-games`, `offline`, `no-pads`, `no-workshop`, …, grep `scenario ===` in `ipc/transport.ts`;
  * `?perftour=1&perftourhz=90&perftourfriends=fake` runs the perf tour in the browser.
* **Keys** go only to the focus manager, exactly as on the real transport (C, `c273b2c`). Before that, the mock routed keys through `nav:input` and hid two "keyboard does nothing" bugs.
* **`window.__kouchMock`** (dev builds) sends pad input and invokes mock commands, e.g. `__kouchMock.invoke("settings_set", {display:{scheme:"white"}})` for White.

### Drivers

* **`scripts/ui-shoot.mjs <w> <h> <outdir> "<steps>"`**: a muted headless Chrome with exact viewports. `SHOOT_BASE=http://localhost:<port>` picks your server. Its steps:

  * keys: `key:<Key>[x<n>]`, `hold:<Key>:<ms>`, `wait:<ms>`;
  * capture: `shot:<name>`, `fit:<name>` (fit metrics), `align:<name>` (near-miss metrics);
  * page: `url:<path>`, `css:--k-text-scale=1.5`, `eval:<urlencoded js>`;
  * real mouse: `click:<urlencoded selector>`, `drag:<a>:<b>`, `mousehold:<sel>:<ms>`.

  It deletes its Chrome profile on exit.
* **`scripts/ui-sweep.mjs`**: every screen × size × scheme through the fit/align metrics, plus the Deck checks with `--deck 1`.
  * Pass `--out <tmp> --md <tmp>/M.md` for trial runs. Without `--md` it rewrites the shared `docs/UI_SWEEP.md` (C's).
  * A blank capture usually means a starved Chrome, so rerun that cell; use `--jobs 2` when others are sweeping.
  * **Never pull the clone that serves the mock during a sweep.** Vite serves files live, so a pull mid-run mixes versions across cells (2026-09-30). Serve from a clone kept for sweeps, and pull it only between runs.
  * Failing cells print as `<size>-<scheme> <cell>: <problems>` (not "FAIL"): count the lines that don't end in `: ok`.
  * Bands: ≤ 0.12, except on a capped, centred fit scope (`data-fit-capped`, the setup wizard), which may reach 0.30 when its two largest bands are within 0.06 of each other.
* **`scripts/ui-crawl.mjs`**: keyboard-only and mouse-only reachability of every screen, layer and sheet, plus long tasks per transition.
  * It also checks that arrows move focus the way they point on screen (`keyboard-order`): within a group, Down lands lower, Up higher, and Left and Right to that side. A wrap from the far end is fine, and so is the sheet X, which is last on purpose.
* Read the PNGs back (the Read tool shows images) before saying anything looks right.

## 2. The real app under Steam: the "Kouch Lab" shortcut (owner's PC)

Steam Input needs a real window plus Steam as the parent under a flagged app id (Early Test 0). Until Kouch's own app id exists, agents use a non-Steam shortcut that runs `target\steamlaunch\kouch-lab-steam.cmd` (source `scripts/kouch-lab-steam.cmd`; copy it over with CRLF endings after editing).

* **Two shortcuts, as of 2026-09-30:**

  * **"Kouch Dev"** (rungameid `14018254881689698304`) runs the wrapper;
  * **"Kouch Lab"** (rungameid `16412741866954424320`) points straight at the owner's `dist\windows\Kouch.exe`, for playing their own copy.

  The owner retargets these. Read each shortcut's Exe in `shortcuts.vdf` before launching (`node scripts/steam-shortcut.mjs list …`). Never edit `shortcuts.vdf`: Steam must be closed for that, and we never close Steam.
* **Every agent run with a Kouch window goes through here** (owner, 2026-09-30), so the owner's pads work in it and they can use it while it runs.
  * `verify` gives an isolated debug build: build it with `CARGO_TARGET_DIR=%TEMP%\kouch-verify-target cargo build -p kouch-app` from your clone, and run your vite on 1420 without `VITE_MOCK`.
  * `rc` gives an installed release build.
  * No-Steam instances only for a second instance Steam can't give (the other side of a LAN pair), or checks without a window.
* **Take turns through `target\steamlaunch\in-use.txt`:** one line with session, purpose, start and expected end. Read it before touching `next-test.txt`; if another session holds it, wait or ask kouch central. When done: `app_exit`, `dist` back into `next-test.txt`, then delete the file.
* **Put the route back to `dist` when you're done.** The owner launches the wrapper shortcut too. A route an agent left behind once gave them the muted `verify` build with its temp config, when they expected their own Kouch with sound (2026-09-29). `dist` runs their copy with sound, their config, and a read-only CDP port.
* **`target\steamlaunch\next-test.txt` picks the route:**

  * `app`: the debug `target\debug\kouch-app.exe`, UI from vite 1420, which must run *without* `VITE_MOCK`;
  * `verify`: an isolated debug build in `%TEMP%\kouch-verify-target`, config/data in `%TEMP%\kouch-verify`;
  * `rc`: an installed release build (the folder named in `%TEMP%\kouch-smoke-rc-path.txt`, CDP 9231), exactly as testers run it;
  * `dist`: the owner's `dist\windows\Kouch.exe`;
  * `rcperf`: a release build with the perf tour;
  * anything else is `kouch-lab` arguments.

  `app`, `verify` and `rcperf` are muted.
* **CDP** is 9223 (9231 for `rc`). `window.__TAURI_INTERNALS__.invoke(cmd, args)` from an eval calls any shell command: the quickest way to read a raw error a toast hides.
* **Steam as administrator:** the owner usually runs Steam elevated, so what it starts is elevated. Agents can't `taskkill` it, but `invoke("app_exit", {})` over CDP ends it cleanly. During a game, that quits the game gracefully first (force after the profile's timeout).
* **Logs:** `target\steamlaunch\kouch-lab-steam.log` and `%LOCALAPPDATA%\kouch\logs\kouch.<date>.log` (the verify route logs under `%TEMP%\kouch-verify\data\logs`).

## 3. An isolated build beside the owner's (no Steam)

Single-instance is keyed by the Tauri identifier, so a second real Kouch can run next to a stuck or elevated one:

* **Build:** `TAURI_CONFIG='{"identifier":"app.kouch.verify","build":{"devUrl":"http://localhost:1421"}}' CARGO_TARGET_DIR=<temp> cargo build -p kouch-app`.
* **Run:** `KOUCH_NO_STEAM=1 KOUCH_CONFIG_DIR=<tmp>/config KOUCH_DATA_DIR=<tmp>/data WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS="--remote-debugging-port=9224 --mute-audio" <exe>`, with vite on 1421.
* **In the test config:**
  * Never finish its first-run wizard (it would pick the owner's real `R:\Emulation`); use `/?setup=skip`.
  * Set `exports.copy_to_clipboard=false` first, since an export would overwrite the owner's clipboard.
  * Set emulators to windowed: with the monitors off, exclusive fullscreen stalls.
* Covers everything except Steam Input and Steam motion.

## 4. The dev virtual controller

Build with the `dev-vpad` feature (debug builds have it; `linux-build.ps1 bundle -DevVpad` for the Deck) and run with `KOUCH_DEV_VPAD=1`. A pad then listens on `127.0.0.1:26790`; drive it with `kouch-lab vpad "<cmds>"` or `scripts/deck/deck-tools.sh vpad`.

* **Commands:** `connect pro|touchpad|standard`, `press <btn> [ms]`, `hold`/`release`, `stick l|r x y [ms]`, `trigger`, `gyro pitch|yaw|roll <dps> <ms>`, `tilt`, `shake <g> <hz> <ms> [x|y|z]`, `rest`, `disconnect`.
* **Traps:**
  * Its first A on a Home hero joins and would press the hero (fixed for re-seats, `ca44c36`; quit any launched game with `session_quit {mode:"graceful"}`).
  * Motion carries a little noise on purpose (clean motion made a reference emulator's pointer NaN).
  * Pitch from gyro alone is pulled back by the accelerometer; use `tilt`.

## 5. Real emulators

The private test profiles in `profiles/local/` (plain JSON `.kod`; `alpha-emulators.kod` is the zipped tester pack) run end to end on the isolated or verify route:

1. import;
2. install from the profile's source;
3. scan `R:\Emulation\roms\<system>` (read-only root);
4. launch;
5. check the DSU client registers;
6. read the emulator's own screenshot file back;
7. open the Quick Menu;
8. quit gracefully.

* **Always:**
  * fingerprint the emulator's own AppData before and after;
  * keep its config in Kouch's user data;
  * build profiles in UTF-8. PowerShell 5.1's `Get-Content` reads UTF-8 as ANSI (`6Ã—` for `6×`), so use Node or `-Encoding UTF8`.
* **System files** (keys, firmware) reach an emulator only by the owner dropping their own files onto Kouch (ADR 0020). Agents never read, copy or fetch them.
* Motion in a real game: `docs/EARLY_TESTS.md` "Motion in a real game under Steam" (Steam caps accel at ±2 g, so the shake mapping is profile-driven).

## 6. Steam Deck (Game Mode)

The owner's Deck OLED (SteamOS, gamescope, 90 Hz), reachable over ssh on the LAN. Set `KOUCH_DECK=deck@<address>` for the scripts; the address is in memory `deck-game-mode-rig`. Accounts: the Deck is signed in as **haruka**, never the owner's main account (one device per account, or Steam kicks the other off).

* **Deck side (`~/kouch-dev`):**
  * `run-gamemode.sh` (a copy is in `scripts/deck/`) is what the non-Steam shortcut "Kouch dev test" runs;
  * `gm-app.txt` names the AppRun of an extracted build (`depot.<sha>/squashfs-root/AppRun`);
  * the flags `gm-tour.flag` (perf tour + exit), `gm-inspect.flag` (WebKit inspector on 127.0.0.1:9222), `gm-friends.txt` (`fake`) and `gm-games.txt` pick what the launch does;
  * logs go to `gm.out`/`gm.err`, and Kouch's own log to `~/kouch-dev/data/logs`.
* **Build for it:** from a clone outside Dropbox, `$env:KOUCH_SOURCEMAP='1'; scripts/linux-build.ps1 bundle -DevVpad`. The output lands in `target/linux-bundle-dev/`, not `linux-bundle/`. Keep `app/ui/dist/assets` (the source maps) per build for profiling. Then `scp` the AppImage over and run `--appimage-extract` into `~/kouch-dev/depot.<sha>` (the extracted tree starts \~0.5 s faster than the squashfs).
* **Scripts (`scripts/deck/`):**

  * `deck-tour.sh`: tours per build, muted;
  * `deck-ab.sh`: runtime JS/CSS A/Bs;
  * `deck-profile.sh`: WebKit timelines of chosen phases;
  * `deck-tools.sh`: eval, vpad, mute, kill.

  All kill leftovers first: Steam refuses a launch while anything from the last one lives (`WaitingPrevProcess`), including a netplay stand-in under Steam's reaper.
* **Reading numbers:** see `docs/agents/PERF.md`.
* **When Deck testing ends:** restore the Deck's original `shortcuts.vdf` (backup in `~/kouch-dev/backup/`, Steam closed), remove the "Kouch dev test" shortcut, and restore the power settings (`backup/power-20260928-0603`).

## 7. The second Windows PC and the three accounts

Three Steam accounts on three devices let us test what one account can't: chat, invites, lobby joins and online play.

| Device            | Steam account  | Who drives it                                        |
| ----------------- | -------------- | ---------------------------------------------------- |
| Owner's main PC   | bo (the owner) | the owner; agents only read                          |
| Second Windows PC | Airi           | the Remote Control session "kouch second windows pc" |
| Steam Deck        | haruka         | central, over ssh                                    |

* **The shared kit is a Nextcloud folder both PCs sync, `I:\nextcloud\whatever\kouch-test`:**
  * `install-kouch.ps1` installs to `%LOCALAPPDATA%\Programs\Kouch` (never `%LOCALAPPDATA%\Kouch`, which is Kouch's data folder);
  * `kouch-via-steam.cmd` is the launcher its "Kouch test" shortcut runs;
  * `setup-netplay-test.ps1` adds a stand-in emulator and the "Netplay Test" game;
  * `send-logs-to-main-pc.ps1` sends its logs back;
  * the installer, `Kouch_0.1.0_x64-setup.exe`, is replaced each round.
* **Messaging:** the second PC's session receives messages (`SendMessage` to its bridge address) and writes progress to `from-second-pc\status.md`. Its reports are `from-second-pc\retest-<sha>.md` plus `retest<N>-*.png`.
* **A round:**
  1. Build with `scripts/build-release.ps1 -Installer`.
  2. Smoke-test it, muted.
  3. Copy the installer into the kit.
  4. Message the numbered items to check. The second PC backs up `settings.json`, installs, starts from Steam, confirms the build hash in About, marks each item FIXED / STILL / NEW / N/T, and restores its settings.
  5. Route what's open to A/B/C.
* **Online checks:**
  1. haruka hosts: `netplay_host {game_id}` over the Deck's inspector (`deck-tools.sh eval`).
  2. The second PC writes a `ready-…` line when it's waiting.
  3. central sends `netplay_invite_friend {steam_id}` for Airi's id.
  4. The second PC acts: presses the toast's Hold to join, joins from the chat card, and so on.

## 8. The owner's own copy

The owner runs `C:\Users\GabrP\Documents\kouch\dist\windows\Kouch.exe`: that's this same Dropbox checkout.

1. Build in a clone outside Dropbox (`C:\dev\kouch-gate` for central).
2. Smoke-test it, muted, with temp config/data and `KOUCH_NO_STEAM`.
3. Only if the owner's `Kouch.exe` isn't running: rename the old `dist\windows` to `windows.prev-<stamp>` (agents can't delete there) and copy the new one in.

Never overwrite it while it runs.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.kouch.dev/docs/agents/testing.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
