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

textSIMENGINE_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:

shellpython3 "$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"):

shellpython3 "$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.

shell"$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.

shellDRILL=$(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.