Docs /Agent skills
Run, capture, diagnose
Agent skill: run and capture a project, then diagnose failures from exit codes and structured errors.
Run, capture and diagnose
This skill ships with the SimEngine first_scene example. It uses only public
tools: simengine_host (headless scenario CLI), simengine_capture (offscreen render
and PNG), tools/run_scenario.py (out-of-process run supervisor) and
examples/first_scene/first_scene.py (the example driver that chains them and
verifies what they write).
Compatible versions
Scenario schema simengine.scenario schema_version 1, capabilities 1, scene state 1,
host result simengine.host.result 1, simulation contract 1, module contract 1, capture
manifest simengine.capture.manifest 1, scene/asset formats 1, example driver 1 and
simengine.example_manifest 1. Exit codes below are the ones those versions document
(simengine_host --help, simengine_capture --help, first_scene.py --help).
Variables
SIMENGINE_ROOT=/absolute/path/to/SimEngine
SIMENGINE_BUILD=$SIMENGINE_ROOT/build/linux-release
PROJECT=/absolute/path/to/my_first_scene
Prefix each command with them if your shell tool does not keep variables between calls.
Run
The full journey (two runs, posing, three captures, verification), then an independent re-verification from the files:
python3 "$SIMENGINE_ROOT/examples/first_scene/first_scene.py" run --project "$PROJECT" --build "$SIMENGINE_BUILD"
python3 "$SIMENGINE_ROOT/examples/first_scene/first_scene.py" verify --project "$PROJECT"
Simulation only (no GPU needed; the render check is reported NOT_RUN, never passed, and a
later verify of that run reports "render": "NOT_RUN"):
python3 "$SIMENGINE_ROOT/examples/first_scene/first_scene.py" run --project "$PROJECT" --build "$SIMENGINE_BUILD" --skip-capture
Each run prints one JSON line (simengine.example_result: outcome, exit_code,
failure, final_state_sha256, expectations, captures, checks) and writes
$PROJECT/runs/<scenario>-NNN/:
| Path | Content |
|---|---|
manifest.json |
The build/run/capture manifest: build identity and CMake settings, dependency graph, enabled and disabled modules with absence checks, every step (argv, exit code, wall time, peak RSS where the OS reports it), simulation digests and expectations, posing, captures, checks, warnings, not_covered |
logs/NN-<step>.stdout / .stderr |
each engine process’s JSON result and its JSON Lines diagnostics |
run-1/, run-2/ |
simengine_host --run artifacts: scenario.json, initial_state.json, final_state.json, trace.jsonl, result.json |
scenes/<pose>-<view>.sescene |
the scene posed from a simulation state |
captures/<pose>-<view>/ |
<scene_id>.png and <scene_id>.capture.json (device, timings, validation, content check) |
Look at the PNGs yourself; a passing content check only means the image is not black or empty.
Use the engine tools directly
When you need more detail than the driver gives, call the same tools it calls. Each prints one JSON document on stdout (also on failure) and JSON Lines diagnostics on stderr.
"$SIMENGINE_BUILD/apps/host/simengine_host" --load "$PROJECT/scenario.json" > "$PROJECT/runs/load.json"
"$SIMENGINE_BUILD/apps/capture/simengine_capture" --check-scene "$PROJECT/scene.sescene" > "$PROJECT/runs/check-scene.json"
python3 "$SIMENGINE_ROOT/tools/run_scenario.py" --host "$SIMENGINE_BUILD/apps/host/simengine_host" --scenario "$PROJECT/scenario.json" --out "$PROJECT/runs/supervised-$(date +%Y%m%d%H%M%S)" --timeout 60
run_scenario.py classifies the outcome (completed, invalid, timeout,
expectation_failed, fault, crash, supervisor_timeout, …) even when the host
crashes. simengine_host --run FILE --out NEW_DIR --trace-every 1 records a state digest
per tick to find the first tick where two runs diverge. simengine_host --report shows
the Vulkan device and the module registry.
Diagnose: exit codes and what to do
first_scene.py (failure.code; failure.hint repeats the action):
| Exit | Code | Meaning → action |
|---|---|---|
| 0 | — | Everything passed. Report digests, expectations and PNG paths. |
| 1 | internal_error |
Driver defect → keep the run directory, report it with the message. |
| 2 | usage_error |
Fix the command line (first_scene.py --help). |
| 20 | build_not_found |
Build first (tools/setup.sh / tools/setup.ps1), or fix --build. |
| 21 | incompatible_engine |
Different builds for host and capture, or a version this skill does not know → stop and report. |
| 22 | module_missing |
Scenario needs an optional module → build with -DSIMENGINE_MODULES=<id> or use a core-only scenario. |
| 23 | scenario_invalid |
failure.detail.errors[]: fix each at its JSON pointer path (line, column, expected, found). Nothing ran. |
| 24 | scene_invalid |
Fix the scene/asset line in failure.detail (code, file, line). |
| 25 | pose_mapping_failed |
Entities without a same-named scene instance, a non-+y frame or a non-yaw orientation → fix the project. |
| 26 | run_failed |
failure.detail.host_exit_code: 11 timeout (fewer ticks or a larger --timeout), 13 I/O, 14 simulation_fault (non-finite state; rolled back), 15 module startup, negative = crash → report crashes. |
| 27 | expectation_failed |
Compare failure.detail.failed[].expected with .actual; fix the command or the expectation, never the tolerance alone. |
| 28 | nondeterministic |
Same build, same input, different digests: an engine defect → keep both run directories and report. |
| 29 | capture_failed |
failure.detail.error.code: validation_errors (8) or render_failed (11) → engine defect, report; capture_content_invalid (12) → the camera sees nothing, fix camera/target. |
| 30 | capture_check_failed |
PNG or manifest does not verify independently → report; do not regenerate silently. |
| 31 | project_invalid |
Malformed project.json or a non-empty --out directory. |
| 77 | blocked_environment |
No usable Vulkan 1.3 device (capture exit 3–6). Simulation verified; rendering BLOCKED_ENVIRONMENT, not passed. Install a GPU driver or Mesa lavapipe. |
simengine_host exit codes (in logs/*.stdout, error.code): 2 usage_error,
10 scenario_invalid, 11 scenario_timeout, 12 scenario_expectation_failed,
13 scenario_io_error (input unreadable, or --out not new/empty), 14
simulation_fault, 15 module_startup_failed; 3–9 concern --report only.
simengine_capture exit codes: 3–6 no usable Vulkan device, 7
backend_initialization_failed, 8 validation_errors, 10 scene_invalid, 11
render_failed, 12 capture_content_invalid, 13 output_write_failed.
Diagnosis drill
Practise the loop on a new, uniquely named copy: break one unit and see the located error. Keep that copy and its diagnostics for inspection; existing projects are preserved.
DRILL=$(python3 - "$PROJECT" <<'EOF'
import pathlib, shutil, sys, tempfile
project = pathlib.Path(sys.argv[1]).resolve()
drill = pathlib.Path(tempfile.mkdtemp(prefix=project.name + ".drill-", dir=project.parent))
shutil.copytree(project, drill, dirs_exist_ok=True, ignore=shutil.ignore_patterns("runs", ".git"))
print(drill)
EOF
)
python3 - "$DRILL/scenario.json" <<'EOF'
import json, sys
path = sys.argv[1]
scenario = json.load(open(path, encoding="utf-8"))
scenario["entities"][0]["position"]["unit"] = "cm" # the schema accepts meters only
json.dump(scenario, open(path, "w", encoding="utf-8"), indent=2)
EOF
set +e
python3 "$SIMENGINE_ROOT/examples/first_scene/first_scene.py" check --project "$DRILL" --build "$SIMENGINE_BUILD" > "$DRILL/check.json"
status=$?
set -e
python3 - "$DRILL/check.json" "$status" <<'EOF'
import json, sys
result = json.load(open(sys.argv[1], encoding="utf-8"))
error = result["failure"]["detail"]["errors"][0]
assert int(sys.argv[2]) == 23 and result["failure"]["code"] == "scenario_invalid", result["failure"]
assert error["code"] == "unit_mismatch" and error["path"] == "/entities/0/position/unit", error
print("diagnosed:", error["code"], "at", error["path"], "line", error["line"], "expected", error["expected"], "found", error["found"])
EOF
printf 'Diagnosis files retained in %s\n' "$DRILL"
Report failures verbatim (code, step, message, the engine’s own error object). Change
only the project; never edit SimEngine sources, tests or tolerances to get a pass, and
never describe a BLOCKED_ENVIRONMENT or NOT_RUN render as passed.