# SimEngine agent guide

> Use this guide when a coding agent (Claude Code, Codex, Cursor, Copilot, Gemini CLI or any agent with a shell) builds, runs or changes a SimEngine project. It is written for agents first and humans second.

Last updated: 2026-10-29. Engine state: pre-release, M1 foundations done, M2 in progress.

## Read this first

- **SimEngine is a local C++20 / Vulkan 1.3 game engine driven from the command line.** There is no hosted API, no API key, no account and no MCP server. Every tool is a local executable that prints one JSON document per command.
- **The repository is not public yet.** It opens with the first distributable milestone (MIT OR Apache-2.0). Until then, this guide documents the interface so you can evaluate it; you can only run it with access to a checkout.
- **You cannot ship a game with SimEngine today.** What works: seeded headless scenarios of point entities, offscreen PBR capture to PNG, a Windows scene playground and offline cooking of a strict static glTF/GLB subset. What does not exist yet: supported physics or collisions, a game loop, input for gameplay, audio, textured materials, shadows, terrain or orbital propagation. Do not promise or attempt these.
- **The runtime never calls an LLM.** You drive development; the engine never waits for you.

## How you work with SimEngine

The loop the engine is optimized for. Every step is a command you run and a document you parse.

1. **Discover**: `simengine_host --capabilities` prints the scenario schema, units, frames, the components and commands of exactly the compiled-in modules, limits, the determinism scope, every exit and error code, and an example scenario valid for this build. Read it instead of guessing from documentation.
2. **Author**: edit versioned text. A scenario (`simengine.scenario`, version 1) declares a seed, a tick schedule, entities, scheduled commands and expectations. Every position states its value, unit and frame.
3. **Run headless**: `simengine_host --run scenario.json --out RUN_DIR` loads, steps and checks expectations, then writes the result and artifacts.
4. **See**: `simengine_capture --scene scene.sescene --out-dir CAPTURE_DIR` renders offscreen through Vulkan and writes a PNG plus a manifest. A black, empty or background-only image is a failure (exit 12), never a pass.
5. **Prove**: a change is done only when its expectations pass, the `final_state_sha256` changed as intended and the capture shows what you meant. Compiling, surviving one tick or printing a success boolean is not acceptance.

## Setup

From a SimEngine checkout, read `AGENTS.md` first, then use the maintained, rerunnable setup scripts that humans and CI also use:

- **Linux**: `./tools/setup.sh --install-missing` detects the environment, installs only missing recorded prerequisites (Ubuntu/Debian), configures and builds the `linux-release` preset, runs the test suite and the host smoke check.
- **Windows 11**: install Visual Studio 2022 (C++ x64), CMake 3.28+, Ninja and Python 3.12+, then run `tools/setup.ps1`. No Vulkan SDK is needed: the pinned Khronos headers are fetched and the loader is opened at run time.

Expect zero failures among the selected CTest tests; report skips separately. The final smoke check prints one JSON summary naming the Vulkan device, or a structured `BLOCKED` outcome on a machine without a Vulkan 1.3 driver. Report failures verbatim with the exit code. Do not work around a failure silently.

## Implementation contract

1. **Start from capabilities.** Run `--capabilities` before writing a scenario. Use only the commands, components and modules it lists; a disabled module's commands do not exist and are rejected.
2. **Keep your project outside the engine checkout.** Never edit engine sources, tests or `examples/` to get a pass.
3. **Be explicit.** Positions are `{ "value": [x, y, z], "unit": "m", "frame": "world" }`. Frames are right-handed, `+y` up. Durations are nanoseconds (`"unit": "ns"`).
4. **Validate before you run.** `--load FILE` validates all-or-nothing and prints the initial state; `simengine_capture --check-scene FILE` validates a scene without a GPU.
5. **Use new or empty output directories.** Existing content is never overwritten.
6. **Classify outcomes by exit code, then by `error.code`.** Do not parse stderr prose. Diagnostics on stderr are JSON Lines (`simengine.diagnostic`, version 1).
7. **Do not retry blindly.** A failed expectation (exit 12) is a result, not a flake: read which expectation failed and why. Timeouts (exit 11) mean your `--max-wall-ms` bound was reached.
8. **Missing GPU is BLOCKED, not PASS.** Exit codes 3 to 5 mean no usable Vulkan device. Report `BLOCKED_ENVIRONMENT`; never fake a capture.
9. **Report evidence.** Finish with the commands run, exit codes, `final_state_sha256` values, PNG paths and what SimEngine could not do.

## Scenario format (simengine.scenario v1)

This is the scenario of the `first_scene` example, abridged:

```json
{
  "schema": "simengine.scenario",
  "schema_version": 1,
  "name": "first_scene",
  "seed": 20261027,
  "time": { "tick_duration": { "value": 16666667, "unit": "ns" }, "ticks": 120 },
  "frame": { "id": "world", "kind": "cartesian", "handedness": "right", "up_axis": "+y" },
  "entities": [
    { "id": "table", "position": { "value": [0, 0, -1], "unit": "m", "frame": "world" } },
    { "id": "ball",  "position": { "value": [2, 0.25, 1.5], "unit": "m", "frame": "world" } }
  ],
  "generators": [
    { "kind": "scatter_box", "id_prefix": "barrel_", "count": 3,
      "min": { "value": [1.5, 0, -3.5], "unit": "m", "frame": "world" },
      "max": { "value": [3.5, 0, -2], "unit": "m", "frame": "world" } }
  ],
  "commands": [
    { "at_tick": 60, "command": "simengine.entity.set_position", "version": 1,
      "args": { "entity": "ball", "position": { "value": [0.25, 1, -1], "unit": "m", "frame": "world" } } }
  ],
  "expect": [
    { "name": "ball_on_table", "entity": "ball", "quantity": "position",
      "value": { "value": [0.25, 1, -1], "unit": "m", "frame": "world" },
      "tolerance": { "value": 0, "unit": "m" } }
  ]
}
```

The full file is at `/simengine/examples/first_scene/scenario.json`. Without the optional `point_mass` module, motion is a sequence of scheduled teleports; with it (`-DSIMENGINE_MODULES=point_mass`), entities gain `point_mass.body` (velocity) and the scene `point_mass.world` (gravity), with the commands `point_mass.apply_impulse` and `point_mass.set_velocity`. Always confirm with `--capabilities`.

## Run artifacts

`simengine_host --run FILE --out DIR` writes into DIR:

- `scenario.json`: a byte copy of the input
- `initial_state.json` and `final_state.json` (`simengine.scene_state`); the result carries the SHA-256 of the final state
- `trace.jsonl` when `--trace-every N` is set
- `result.json`: the same `simengine.host.result` document printed to stdout

Two runs of the same scenario on the same build and configuration give the same `final_state_sha256`. Cross-platform bitwise determinism is not promised.

## Interactive control: --session

`simengine_host --session [--max-wall-ms MS]` reads JSON Lines requests on stdin and writes one `simengine.session.response` per request on stdout. Operations: `load`, `step`, `inspect`, `command` (with dry run and an expected state version), `expectations`, `capabilities` and `quit`. Use it when you need to step and inspect without re-running a whole scenario.

## The first_scene example and its skills

`examples/first_scene` is the reference project. Its driver `first_scene.py` calls only the public tools, in separate processes, and verifies what they write:

- `python first_scene.py new DEST` creates a project (and copies the three skills into `DEST/.claude/skills`)
- `python first_scene.py check --project P --build B` validates engine compatibility, scene and scenario without a GPU
- `python first_scene.py run --project P --build B` runs the full journey: load, run twice, capture, verify, manifest
- `python first_scene.py verify [MANIFEST]` re-verifies a finished journey from its files

Expected outcome for an unchanged project: `PASS`, 3 of 3 expectations, three PNG captures and a `manifest.json`. The driver stops with `incompatible_engine` (exit 21) if the engine reports interface versions the skills were not written for.

Agent skills (load them; they are the procedures this guide summarizes):

- [simengine-create-project](/simengine/skills/simengine-create-project/SKILL.md): create, check, run and verify a project
- [simengine-modify-scene](/simengine/skills/simengine-modify-scene/SKILL.md): add an object, move it, add an expectation, prove the change
- [simengine-run-capture-diagnose](/simengine/skills/simengine-run-capture-diagnose/SKILL.md): run, capture, and diagnose failures by exit code

## Exit codes

`simengine_host` (also in the JSON `exit_code`; `error.code` names the failure):

| Code | Name | Meaning |
| --- | --- | --- |
| 0 | ok | Success |
| 1 | internal_error | Unexpected exception inside the host |
| 2 | usage_error | Invalid command line |
| 3 | vulkan_loader_unavailable | No Vulkan loader library on this machine |
| 4 | vulkan_driver_unavailable | Loader present, no driver supporting Vulkan 1.3 |
| 5 | no_vulkan_device | Instance created, zero physical devices |
| 6 | no_suitable_device | Devices exist, none meets the requirements |
| 7 | backend_initialization_failed | Other instance or device creation failure |
| 8 | validation_errors | Backend opened, the validation layer reported errors |
| 9 | runtime_self_check_failed | Backend fine, core runtime self-check failed |
| 10 | scenario_invalid | Validation failed; nothing ran |
| 11 | scenario_timeout | The wall-time bound stopped the run or session |
| 12 | scenario_expectation_failed | Run completed, an expectation failed |
| 13 | scenario_io_error | Input unreadable or output directory unusable |
| 14 | simulation_fault | A tick produced non-finite state (rolled back) |
| 15 | module_startup_failed | Optional modules could not be initialized |

Codes 3 to 5 all mean "no usable Vulkan device". A process killed by a signal prints no document; your supervisor must classify it as a crash. Capture exit codes are in the [CLI reference](/simengine/cli.md).

## What not to do

- Do not invent commands, flags, components or modules. If `--capabilities` does not list it, it does not exist in this build.
- Do not edit engine code, tests or examples to make a check pass.
- Do not treat "it compiled" or exit 0 from a partial step as done.
- Do not fake or reuse a capture when the GPU is unavailable.
- Do not add an LLM call, network call or API key to a game or to the runtime.
- Do not claim physics, collisions, input, audio, textures or a game loop: they are not supported yet.

## Links

- [llms.txt](/simengine/llms.txt) and [full context](/simengine/llms-full.txt)
- [CLI reference](/simengine/cli.md)
- [Copy-ready agent prompt](/simengine/agent-prompt.txt)
- [What SimEngine is](https://indiega.me/simengine) and [why it is an AI game engine](https://indiega.me/ai-game-engine)
