Arena validation
A saved arena can be structurally correct and still be unusable. A door can name a real wall and sit four units away from it. A region can be walled off with no doorway, so the demonstration agent never enters it and the run quietly measures less than the researcher intended. Neither fault stops the configuration loading, and neither is visible in the 3D view without looking for it.
backend/validation.py checks arena geometry on the server before a
configuration is stored.
Two validators, on purpose
There are two validation layers and they do not overlap.
| Layer | Runs in | Covers |
|---|---|---|
ExperimentValidator | Godot, before load_experiment | Structure, types, required fields, material references, id uniqueness |
validation.py | Python, before a save is stored | Geometry: placement, attachment, boundaries, reachability |
The split is deliberate. Two validators checking the same rules drift apart, and the failure mode is the worst kind: the API accepts a configuration the simulator then rejects, so the error surfaces far from its cause. When adding a check, decide which layer owns it first.
What is checked
Errors block a save.
- A door further than two units from the wall it names
- A door wider than its wall is long
- A region or wall extending past the arena boundary
- A start position inside a wall
openandstatedisagreeing on the same door- A bridge structure naming a region that does not exist
Warnings are recorded but do not block a save.
- A region that cannot be reached from the start position
- A region where a wall cuts off most of its open floor
- An elevated region with no elevation set, or the reverse
- A stimulus positioned outside the arena
- Use of a region id the movement code matches literally
Reachability
The obvious implementation builds a graph from doors and walks it. That is wrong here. Two regions that simply share an edge are connected even with no door between them, and a door graph reports them as sealed.
Instead the floor is rasterised at half-unit resolution. A cell is blocked if it
falls inside an enabled inner wall, unless it also falls within an open doorway
on that wall. A flood fill runs from the configured start position, and any
region with no reached cells is unreachable. Rotated walls are handled using the
same local-space transform StatisticalAgentPlugin uses for collision, so the
check and the movement code agree about where a wall is.
Running this against the MCSF preset found that the bridge region could not be
entered from the start position. wall_inner_east has no doorway and
wall_bridge_south blocks the route from the exit slope. That region is the one
the paradigm is built around: elevated, wire mesh, risk semantic type, with
bridge_light aimed at it.
Door state
Arena3D decides how to draw a door from state, falling back to
default_state. StatisticalAgentPlugin decides whether the agent can pass
from open. Nothing keeps the two in step when a configuration is loaded.
A door written as state: "closed" with open: true therefore renders shut and
lets the agent walk through it. The validator reports the mismatch, and the
arena editor writes state, default_state and open together from one
control so the fault cannot originate there.
Where it runs
validate_experiment in backend/app.py calls it, so geometry is checked on
every experiment save, on project creation, and on every preset the server
offers through /api/templates. A preset that fails is dropped from the
template catalogue rather than offered and then failing at load.
It can also be run directly, and exits non-zero on errors, so it works in CI:
python backend/validation.py # the default preset
python backend/validation.py path/to/file.json # any configuration
Reserved region names
StatisticalAgentPlugin._resolve_bridge matches the region ids bridge,
bridge_slope_entry, bridge_slope_exit, corridor_a and corridor_c
literally. A custom arena cannot have working bridge behaviour unless it reuses
those exact names, and a region a researcher happens to name bridge inherits
crossing restrictions it was never given deliberately.
The validator warns when a reserved name is used, and the editor shows the same
warning while drawing. Removing the coupling needs a change in the plugin,
reading semantic_type or an explicit field rather than matching ids.
AI Attribution: This page was drafted with AI assistance from the implemented
backend/validation.pysource. The team should review it against the code.