# Improve a robot with local trials

Play the same opponent, seed and ruleset before and after a change. Inspect
the first decision where behaviour diverges, then repeat across several
seeds and both seat orders. Keep a working baseline so a faster run or one
win does not hide a control, protocol or startup regression.

For a small standalone project, start with the [player kit](player-kit.md):
clone your language’s starter, then run `make match` and `make replay`. It
uses the shared `@clunkyard/cli` npm package and downloads only the common
engine and selected SDK. Without Make, use
`npx @clunkyard/cli match 42 2d classic` and
`npx @clunkyard/cli replay replays/trial-42.json`.
Use `npx @clunkyard/cli` for current tooling and record `--version` with your source commit when comparing trials.
The commands below are for contributors working from the full game repository.

JavaScript and TypeScript projects use the npm SDK under the `clunkyard` import
name, which also works in hosted builds. Use the language starter's `make match` and `make replay`
for its engine and launcher. The npm package also exposes `play(robot, lines)`
for tests driven by recorded protocol messages; a real match exercises the
engine, opponent and robot together.

There are two repository routes:

| Route | Use it for | Requirements |
| --- | --- | --- |
| `node src/cli.js match` | Fast iteration, saved replays and custom opponents | Node and the selected robots' local SDKs/toolchains |
| `node scripts/check-starters.js --house PROJECT` | Building a user project through the hosted profiles and checking its sandbox limits | Linux Docker, matching builder/runtime images, Node |

Run the commands below from the repository root. Local CLI programs are
trusted programs on your computer. The Docker route uses the hosted
supervisor/relay, fixed profile build recipes and isolated runtime.

## Start with a real match

This match needs only Node and the checked-in DSL core:

```sh
node src/cli.js match \
  --bot @bots/dsl/vane/bot.json --bot @bots/dsl/cadence/bot.json \
  --mode 2d --ruleset classic --seed 42 --ticks 240 \
  --replay replays/trial-42.json
node src/replay-tool.js inspect replays/trial-42.json
node src/cli.js replay replays/trial-42.json
```

Open the replay URL printed by the last command. Pause and step through
contacts, aim, scanner direction, battery and accepted controls. Replaying
uses recorded states and does not rerun the robots. See [replay inspection
and export](replays.md) for archives and video.

A downloaded workbench project can replace either manifest:

```sh
node src/cli.js match \
  --bot @/absolute/path/scout/bot.json \
  --bot @/absolute/path/baseline/bot.json \
  --mode 3d --ruleset zero-g --seed 42 --ticks 360 \
  --replay replays/scout-zero-g-42.json
```

The local runner builds workbench manifests with `language` and `entry` for
JavaScript, TypeScript, Python, C, Go, Rust, Java, Vane, Cadence and Glyph.
It uses the installed SDK at `/opt/clunkyard/sdk`; set `CLUNKYARD_SDK` for
another prefix. Compiled output goes in `.clunkyard/` beside the manifest.
Use `node src/cli.js roster` to see the current House pack.

For another profile, build and launch it using its [binding guide](sdk/adapters.md).
A local manifest can supply explicit `build` and `command` arrays; these
trusted local commands run with the repository root as their working
directory, so use absolute project paths. An already built executable needs
only a command. For example:

```json
{
  "name": "Scout native",
  "language": "cpp",
  "protocol": 2,
  "command": ["/absolute/path/scout/bot"],
  "supportedModes": ["2d", "3d"],
  "supportedRulesets": ["classic", "inertial-2d", "gravity-flat", "gravity-hills", "zero-g"]
}
```

The CLI also accepts `--bot 'Scout=/absolute/path/scout/bot'` for a quick
local command. A saved manifest is easier to repeat and declares supported
rulesets explicitly. Hosted builds use the selected language profile's
recipe rather than executing a manifest's arbitrary build command.

## Exercise a user project through the hosted profiles

Use this route for every hosted language, including adapters, compiled
languages, DSLs and ABI 4 guests. Keep the entire project under one directory
with `bot.json`, its entry and any dependencies included as source. Keep
generated executables and local build files under `.clunkyard/` or `build/`;
the checker omits these directories, `.git/`, `__pycache__/` and
`node_modules/` from the bundle. Ordinary files still count toward source
file/size quotas, so move a loose executable out of the source root before
testing. The builder has no network access.

Build the images for your checkout with `make images`, or select an existing
matching release pair:

```sh
node scripts/check-starters.js --house /absolute/path/scout \
  --builder r.k3s.ca/slepp/clunkyard-builder:RELEASE_TAG \
  --runtime r.k3s.ca/slepp/clunkyard-runtime:RELEASE_TAG \
  --rulesets all --seed 42
```

Replace `RELEASE_TAG` with an immutable release tag present in local Docker.
With no image arguments, the script uses the current checkout's Git commit
tag, as produced by `make images`. Both images must already be available.
Use matching images so their installed bindings and runtime libraries agree.

Despite its `--house` name, this option accepts any project directory. It
builds the project's actual files through `sandbox/worker.js` and plays it
against the JavaScript starter. `--rulesets all` exercises every compatible
mode/ruleset the manifest declares; without it, the script checks classic
2D and 3D where supported. It does not accept a custom opponent, replay
output path or tick limit.

A pass requires successful builds, no match failures, at least one shot in
each match and resource use below half the checked limits. A peaceful probe
can therefore fail “never fired” while still speaking valid protocol. Read
`FAIL` details and both pod logs to separate robot failures from platform
startup failures; a referee-origin startup timeout is an infrastructure
failure, not evidence that the tactic exceeded its CPU budget. The script
exits non-zero if any project fails.

This check reports CPU and own time for Standard, operations for DSL and
fuel for WebAssembly. Wall time is printed for information. The frozen
[Season 3 rates](../rates/s3.json) were calibrated for the arena's hardware;
a different local CPU or runtime environment can change headroom. Local
Docker checks are useful compatibility and regression tests. Use arena
trials to confirm competitive headroom under its current limits.

## Repeat a comparison

Keep your baseline and candidate in different directories or at immutable
build paths. Choose a seed list before comparing, change one tactic at a
time and keep the mode, ruleset, objective and tick limit fixed:

```sh
node src/cli.js match --bot @/absolute/path/baseline/bot.json \
  --bot @/absolute/path/candidate/bot.json \
  --mode 2d --ruleset inertial-2d --seed 7 --ticks 360 \
  --replay replays/baseline-first-7.json
node src/cli.js match --bot @/absolute/path/candidate/bot.json \
  --bot @/absolute/path/baseline/bot.json \
  --mode 2d --ruleset inertial-2d --seed 7 --ticks 360 \
  --replay replays/candidate-first-7.json
```

Repeat with several seeds and opponents. Reverse seat order for each seed
because starting positions and robot identity can affect the result. A
fixed seed reproduces the engine's scenario; reproducibility also requires
the same code, configuration and any tactic randomness. Use the SDK's
randomness API where available and control any external randomness yourself.

For a round robin, `node src/cli.js tournament --bot @... --bot @...
--rounds 3 --seed 1000 --out replays/my-tournament` saves a summary and one
JSON replay per heat. Tournament seeds advance by heat; this is different
from the paired same-seed comparison above. The local spectator's
[Compare revisions](comparisons.md) tool runs two installed revisions
against a chosen opponent with balanced seat orders and archives their
identities, statistics and replays.

A useful edit-run loop is:

1. Record the baseline's failures, shots, hits, damage and result on your
   chosen seeds. Keep its source and build identity.
2. Change one decision, such as scan width or reserve battery.
3. Run the same fixtures or memory-session tests for protocol/API mistakes,
   then replay the same real matches. A fixture smoke test is not a match.
4. Inspect the first unwanted turn: was the contact absent, the gun cooling,
   the shot unreachable, or a control rejected? Log a short status/value or
   write diagnostics to stderr.
5. Keep changes that improve the intended behaviour across the trial set,
   then run the hosted-profile check before uploading or rebuilding in the
   arena.

Default CLI matches use `--resources open`; they are convenient functional
trials and do not reproduce hosted Standard cgroup accounting. `--resources
trusted` is a local resource-reporting mode, not a substitute for the
hosted supervisor. The [resource guide](resources.md) explains the classes
and measurements. Replay outcomes alone do not measure CPU headroom.

These commands are implemented by [the CLI](../src/cli.js),
[manifest resolution](../src/roster.js) and
[the hosted-project checker](../scripts/check-starters.js).
