Crucible · Intermediate · Tutorial 2 of 3
Six ways to misconfigure a sandbox.
None of them log anything.
The Behavior Sandbox spawns factions of AI agents, has them see each other, fight and complete quest objectives. It reads like a small simulation, and it is — but the interesting part is what it quietly declines to model, and how a typo in a faction name produces a war that never starts rather than an error you could act on.
Two different files
A scenario (*.crucible.json) is a repeatable playtest setup: where you spawn,
what gravity is, and what static or physics props are in the world. Nothing in it thinks.
A sandbox (*.sandbox.json) is the opposite: no props, just factions, AI agents
and an optional quest, on a bare ground plane. This tutorial is mostly about the second.
Why the distinction matters
They clear the world and rebuild it, so you run one or the other. A sandbox will not spawn your scenario's crates, and a scenario will not give you anything that moves on its own.
Both are throwaway. Neither ships. They exist so you can watch something happen and decide whether it looked right.
Every misconfiguration in the sandbox has a plausible-looking outcome.
Verified: a misspelled relation gives you peace, a misspelled behaviour gives you a motionless agent, a misspelled objective type gives you a ten-second timer, and a misspelled faction id gives you a quest that completes instantly. Nothing warns. If a run looks calm, that is not evidence it is configured.
Scenarios
What actually spawns
A scenario spawn is the placed-object vocabulary you already know — primitive, colour, position, rotation, scale — plus four runtime fields.
| Field | Default | What it does |
|---|---|---|
count | 1 | Copies, laid out along +X only. |
spacing | 2 | Gap between those copies. |
physics | false | False gives a static body you can stand on; true gives a rigid body you can knock over. |
mass | 1 | Only meaningful when physics is true. |
The static and physics paths build genuinely different things. A static entity gets a
trimesh collider — exact, concave, and correct to stand on. A physics entity gets a
convex one, because the physics engine will not simulate concave shapes; so a spawned
archway you can walk through as scenery becomes a solid lump the moment you set
physics: true.
Two smaller things worth knowing. count is clamped to a minimum of one, so
"count": 0 spawns one object rather than none. And scale on a physics entity is
applied to the mesh and collider rather than the body, which is the correct way to do it but
means the two paths respond to scale through different machinery.
Factions
Hostility is symmetric
Each faction declares relations toward the others —
friendly, neutral or hostile. Only one of those words
does anything, and it does it in both directions.
| You declare | You get |
|---|---|
red → blue: hostile. Blue says nothing. |
Mutual war. Blue is hostile to red too. |
red → blue: friendly. blue → red: hostile. |
War. Hostile wins; friendliness is not protection. |
| Neither declares anything. | Peace. Missing means neutral. |
red → red: hostile. | Peace. A faction can never fight itself. |
red → blue: enemy | Peace, silently. Only the exact word hostile counts. |
red → ghosts: hostile, no such faction. | Accepted without complaint. Ids are never checked. |
The symmetry is the one to internalise. There is no such thing as a one-way aggressor here — you cannot set up a predator that hunts a prey faction which ignores it, because declaring the hunt makes the prey hostile back. If you want that asymmetry you get it from behaviour, not from relations: give the prey a low health pool so it crosses the flee threshold on the first hit.
Perception
A flat cone, and no walls
An agent sees a hostile if it is within sightRange and inside
sightAngleDeg of forward. That is the entire model.
| Situation | Seen? |
|---|---|
| Dead ahead, 6 m away, range 12 | Yes |
| Directly behind | No — outside the 100° cone |
| Ahead, 20 m away, range 12 | No |
| 50 m straight up | Yes. |
| Behind a solid wall, 6 m away | Yes. |
Both surprises come from the same two simplifications. There is no raycast anywhere in the visibility check — geometry between two agents has no effect on whether they see each other. And the whole calculation is flattened onto the ground plane, with height discarded before the distance is measured, so an agent directly overhead sits at a flat distance of zero and counts as seen at any altitude.
Neither is a defect. The sandbox exists to watch faction logic and behaviour states play out on an open plane, and it is built for exactly that. But it does mean the sandbox cannot answer questions about cover, stealth, elevation or blocked sightlines, because it is not modelling any of them — and a run that looks like a stealth test is measuring something else entirely.
Behaviours
Four names, two behaviours
The contract documents four behaviours: idle, patrol,
guard, wander. Measured over one second of ticks with nobody hostile in
the world:
| Behaviour | Moved in 1s at speed 3 |
|---|---|
idle | 0.000 m |
guard | 0.000 m — identical to idle |
patrol, no waypoints | 0.000 m |
partol (typo) | 0.000 m |
patrol with a waypoint | 3.000 m |
wander | moves, randomly |
guard and idle are the same code path. The two names exist to
let you say what you meant, which is worth something in a file someone else will read, but they
produce byte-identical behaviour. Do not expect a guard to defend a position more stubbornly
than an idler; nothing distinguishes them.
And three of the six rows above give you a motionless agent — but only one of the
three is a spelling mistake. The other two are things you asked for. A patrol with
an empty waypoint list falls through to the same branch as an unrecognised name, so "my
patroller isn't patrolling" has two quite different causes that look the same.
Combat
Combat is arithmetic
Perceive, close the distance, attack once a second until something dies. There is no roll anywhere in it.
| Behaviour | Verified |
|---|---|
| Damage per hit | Exactly attackDamage. No variance, no accuracy, no projectile. |
| First hit on contact | Immediate — the cooldown starts expired. |
| Subsequent hits | Once per second. |
| Death | Health ≤ 0 removes the agent from the live list at once. |
| Flee threshold | ≤ 25% of starting health, boundary included. At 26% it still advances. |
Because nothing is random, a fight's outcome is decided before it starts — who wins follows from health, damage and who closed first. That is the point: it makes a sandbox run repeatable, so when you change one number you are looking at the effect of that number rather than at noise.
The flee rule has a detail worth holding on to. Fleeing is a reaction to a visible threat, not a health state: an agent at one percent health with nobody in its cone goes back to patrolling quite happily. It only runs while it can see what it is running from — which also means it stops running the moment the threat leaves its cone, and, since the retreat direction is recomputed every frame as "away from that agent", it runs in a straight line with nothing to stop it leaving the ground plane entirely.
Quests
Quests, and four silent typos
A quest is a list of objectives that complete one at a time, in order.
Two types are supported: elapsed and eliminate_faction.
Sequencing is strict, and it has a consequence people miss: an objective that is already satisfied still waits its turn. If objective three is "eliminate the ghosts" and there are no ghosts, it does not fire early — it fires the instant objectives one and two are done.
★ The timer restarts at every objective. Two objectives of one second each take two
seconds, not one, because seconds is measured from the previous objective's
completion rather than from the start of the quest. The data contract's own documentation says
"since the quest started"; the code disagrees, and the code is what runs.
| What you write | What you get |
|---|---|
"type": "eliminate_fation" |
A timer. Unknown types fall through to elapsed, using whatever seconds holds — 10 by default. |
"target": "ghosts", no such faction |
Completes on the first tick. Nobody alive is indistinguishable from nobody at all. |
| Both mistakes at once | A ten-second wait that looks like a quest. |
| No objectives at all | The quest never completes, and never says so. |
factions block by eye — nothing in the tool will do
it for you.Tier two complete
What you now know
Check yourself
- Scenarios spawn props; sandboxes spawn agents. Each clears the world, so you run one or the other.
- Static spawns get trimesh colliders, physics spawns get convex ones — the same mesh collides differently.
- Hostility is symmetric: one declaration makes a mutual war, and hostile beats friendly. There is no one-way aggressor.
- An unrecognised relation word, and an id for a faction that does not exist, both give you peace, silently.
- Perception is range plus cone, flattened, with no line-of-sight test. Agents see through walls and see straight up.
guardandidleare identical, and a patrol with no waypoints joins them. Standing still is the universal failure mode.- Combat has no randomness: exact damage, immediate first hit, 1s cooldown. Flee is ≤25% health and a visible threat.
- Quest objectives run in order, the timer restarts at each one, unknown types become timers, and a bad faction id completes instantly.
Next, at advanced: running all of this without watching it. Batch runs across every scene in a profile, the report formats, and the performance history that tells you whether last week's change cost you anything.
