Skip to content

Writing Custom Scenarios

Custom scenarios let you reproduce specific real-world failure modes, model your deployment environment, or simulate conditions that aren’t covered by the built-in library. A scenario is a single JSON file — no code required.

Every scenario starts at a real-world position. Pick the coordinates of your test environment: a city centre, a warehouse district, an airport approach path, or wherever your app is expected to run.

{
"$schema": "https://sensorchaos.com/schema/scenario/1.0",
"name": "My First Scenario",
"description": "GPS accuracy degrades, then signal is lost for 30 seconds.",
"version": "1.0",
"duration_s": 120,
"tags": ["gnss", "loss", "navigation"],
"baseline": {
"location": {
"lat": 48.8566,
"lng": 2.3522,
"accuracy_m": 5,
"satellites": 12
},
"update_intervals_ms": { "location": 1000 }
},
"timeline": []
}

Validate it before adding events:

Terminal window
sensorchaos validate my-scenario.json

The timeline is a list of sensor states indexed by time. The engine linearly interpolates between keyframes, so you only need to mark the moments that matter — not every second.

Add a normal start, a gradual degradation, a complete outage, and a recovery:

"timeline": [
{
"at_s": 0,
"location": { "lat": 48.8566, "lng": 2.3522, "accuracy_m": 5, "satellites": 12 },
"note": "Normal fix"
},
{
"at_s": 20,
"location": { "accuracy_m": 40, "satellites": 5 },
"note": "Accuracy degrading"
},
{
"at_s": 40,
"location": { "lat": null, "lng": null, "accuracy_m": null, "satellites": 0 },
"note": "Signal lost"
},
{
"at_s": 70,
"location": { "lat": 48.8566, "lng": 2.3522, "accuracy_m": 200, "satellites": 3 },
"note": "Weak recovery"
},
{
"at_s": 100,
"location": { "lat": 48.8566, "lng": 2.3522, "accuracy_m": 8, "satellites": 11 },
"note": "Full fix restored"
}
]

Key points:

  • Partial updates — only include the fields that change. accuracy_m and satellites above inherit lat/lng from the previous keyframe.
  • Signal loss — set lat, lng, accuracy_m to null and satellites to 0. The app receives no location callbacks during this window.
  • Interpolation — between at_s: 0 (accuracy 5 m) and at_s: 20 (accuracy 40 m), the engine smoothly ramps accuracy on each 1-second tick.

Run it on a connected device:

Terminal window
sensorchaos run ./my-scenario.json

Step 3 — Add sensor data (multi-sensor scenarios)

Section titled “Step 3 — Add sensor data (multi-sensor scenarios)”

For scenarios that also inject IMU, barometer, or magnetometer data, add those fields to the baseline and override them in the timeline. These scenarios require the in-process agent (--agent).

"baseline": {
"location": { "lat": 51.5074, "lng": -0.1278, "accuracy_m": 6, "satellites": 10 },
"accelerometer": [0.0, 0.0, 9.81],
"gyroscope": [0.0, 0.0, 0.0],
"magnetometer": [20.0, -5.0, -44.0],
"barometer": [1013.0],
"update_intervals_ms": { "location": 1000, "sensors": 50 }
}

Then override sensors in specific keyframes:

{
"at_s": 45,
"location": { "lat": null, "lng": null, "accuracy_m": null, "satellites": 0 },
"accelerometer": [0.3, 0.0, 9.81],
"gyroscope": [0.0, 0.0, 0.12],
"note": "GPS lost, vehicle turning right — yaw from gyro"
}

Run with the agent:

Terminal window
sensorchaos run ./my-scenario.json --agent

Increment lat/lng across keyframes while keeping accuracy_m low and satellites high. The falsely confident accuracy is what makes spoofing dangerous.

{ "at_s": 0, "location": { "lat": 25.197, "lng": 55.274, "accuracy_m": 5, "satellites": 12 } },
{ "at_s": 60, "location": { "lat": 25.215, "lng": 55.310, "accuracy_m": 6, "satellites": 11 } },
{ "at_s": 120, "location": { "lat": 25.380, "lng": 55.520, "accuracy_m": 5, "satellites": 10 } }

Accuracy oscillation (multipath / urban canyon)

Section titled “Accuracy oscillation (multipath / urban canyon)”

Alternate accuracy between good and bad values across many short keyframes:

{ "at_s": 0, "location": { "accuracy_m": 5, "satellites": 11 } },
{ "at_s": 8, "location": { "accuracy_m": 120, "satellites": 6 } },
{ "at_s": 16, "location": { "accuracy_m": 8, "satellites": 10 } },
{ "at_s": 24, "location": { "accuracy_m": 90, "satellites": 5 } }

Alternate between null and valid location states:

{ "at_s": 0, "location": { "lat": 59.33, "lng": 18.07, "accuracy_m": 6, "satellites": 9 } },
{ "at_s": 20, "location": { "lat": null, "lng": null, "accuracy_m": null, "satellites": 0 } },
{ "at_s": 40, "location": { "lat": 59.33, "lng": 18.07, "accuracy_m": 30, "satellites": 4 } },
{ "at_s": 60, "location": { "lat": null, "lng": null, "accuracy_m": null, "satellites": 0 } },
{ "at_s": 80, "location": { "lat": 59.33, "lng": 18.07, "accuracy_m": 8, "satellites": 9 } }

Keep GPS fully intact and only corrupt the magnetometer — tests compass-dependent features in isolation:

{ "at_s": 0, "magnetometer": [20.0, -5.0, -44.0] },
{ "at_s": 30, "magnetometer": [85.0, 40.0, -20.0] },
{ "at_s": 60, "magnetometer": [210.0, 180.0, 90.0] },
{ "at_s": 90, "magnetometer": [20.0, -5.0, -44.0] }

Pair your custom scenario with exit-code assertions for automated testing:

Terminal window
sensorchaos run ./my-scenario.json \
--assert-max-position-error-km 0.5 \
--assert-no-crash \
--report report.json \
--exit-code-on-fail

The --assert-max-position-error-km flag measures the maximum reported position error relative to the baseline coordinates. If the app correctly rejects spoofed positions, the error stays near zero.

Always validate custom scenarios before committing them or sharing them with your team:

Terminal window
sensorchaos validate ./my-scenario.json

For a full schema reference including all fields and constraints, see Scenario Format.