Game guide
The HADR game is a deterministic, lockstep, fire-only dispatch simulation. Contacts report fires around a city; you run intake, reconcile the reports into beliefs, and dispatch fire trucks to save lives and buildings. The engine owns hidden truth, emits imperfect evidence, accepts typed vehicle commands, and scores outcomes. It never owns your reports, incidents, reconciliation policy, prompts, or model calls. The typed control surface is documented separately in the API guide.
Objective and measures
Protect people and buildings from fire within the tick limit. The engine reports three measures at the end of an episode:
casualties: the fixed occupants of every building that collapsed.buildings_lost: the number of collapsed buildings.tokens: always reported asnull. Runtime token cost is participant self-reported and never measured by the engine.
There is no composite score.
Four layers
The engine keeps four values strictly separate; the boundary is enforced in code, not by convention:
- Hidden true state: real building, fire, and occupant state that you never see directly during play.
- Imperfect contacts: the public evidence delivered to you. A report’s location or claim may be stale, exaggerated, wrong, or a false alarm.
- Filed agent beliefs: incidents your code derives from contacts. These live entirely in participant code; the simulator hosts none of them.
- Typed actions:
dispatchandnext_tick, the only ways you change the world.

dispatch requires an opaque incident_id, logs and echoes it, and never validates it against any store: filed incidents are your beliefs, and the simulator never sees them. Deterministic reconciliation sits between stateless intake and model-driven dispatch so that reruns are reproducible and auditable.
World mechanics
- Buildings have fixed occupants and a status of
normal,burning, orcollapsed. Collapse is irreversible and adds the building’s occupants to casualties exactly once. Extinguishing restores a building tonormalwith no casualties. - Fires grow by a per-tick rate. A stationary truck within the scenario’s suppression radius of a burning building fights the nearest one (ties broken by building ID) and reduces its intensity; multiple trucks stack. A fire that reaches full intensity collapses its building; one driven to zero is extinguished.
- Fire spread between adjacent buildings is an isolated extension flag, disabled by default for the first release.
- Trucks have a
vehicle_id, a public position, and a status ofidle,moving, orfirefighting. They have no home station and roam freely; repositioning is just a dispatch to a point.
What you can see
- A public map with a road graph and building display geometry. Building coordinates come only from converting IDs you learned from contacts; there is no building enumeration or search.
- Truck vision: each truck truthfully reports the buildings within its radius as a typed observation contact each tick, carrying a per-fire tick countdown for each burning one.
- Human contacts as evidence, which may be imperfect. Partial observability is the point: uncertainty comes from the contacts, not from sensor noise on truck vision.
Play is lockstep. You reason about a frozen observation for the current tick, queue commands, then call next_tick to advance exactly one tick. An episode ends when it is resolved (no active fires and none scheduled) or hits its tick_limit. Scenarios use published seeds, and you may restart at any time; the same scenario and seed reproduce the same episode.
Difficulty stages
Scenarios share one engine; difficulty stages do not fork simulation code. They change what evidence looks like:
- Stage 1 delivers perfect
structured_reportcontacts directly. - Stage 2+ delivers rendered
human_textplus the exact reported location, withholding the canonical claim so intake must extract it. - Typed sensor contacts (truck observations) always bypass language rendering.
Every stage runs on the same trap: there are always fewer trucks than fires, suppression is slow so a truck committed to a fire stays on it, and the most crowded building is under-claimed as only medium severity. The fire you lose is the one you never dispatched to, so difficulty is a prioritization-and-reconciliation problem, not a geometry one. Each stage keeps that trap and layers on one new demand:
| Stage | What makes it hard |
|---|---|
| 1 | Nothing is hidden and the reports are perfect. This is as easy as it gets. |
| 2 | Reports arrive as panicked human-language calls. |
| 3 | Separate calls describe the same fire; they are not always accurate. Merge before you dispatch. |
| 4 | Reports contradict each other, and you have to react to changing situations. Revise the plan and redirect a truck if you need to. |
| 5 | The ultimate challenge. High call volume, duplicates, false alarms, a wrong address, and callers who misremember when the fire started. Triage signal from noise and trust neither the loudest report nor the stated time. |
Display and scoring
An ordinary browser display polls a read-only snapshot. Each scenario sets an immutable display policy: full_state scenarios expose current truth and live metrics for teaching or spectating, while public_only scenarios (including assessment scenarios) show only public facts, so the room display reveals no truth either.
A deterministic greedy baseline, using only public evidence, sits on the scoreboard as the bar to beat. Scoring is honor-system: extraction is graded offline in your own tests against published labeled datasets, the engine has no grading operation and never sees your report schema, and self-reported tokens and model IDs remain visibly unverified and cannot affect the simulation.
The API guide documents the typed control surface, connection rules, every tool, and the error catalogue.
Maps
The @map half of a scenario ID picks one of three cities; the map guide describes each one in full.
| Londone | Feeladelphia | Stockhome |
|---|---|---|
![]() |
![]() |
![]() |
Expected scores
The baseline is the simplest dispatcher, perfect makes the best possible decision with perfect understanding. Students should exceed the baseline and get as close to perfect as possible. All figures are casualties/buildings lost; lower is better.
Last run 2026-08-10 at temperature=0: intake mimo-v2.5 (JSON mode), dispatch minimax-m3 (tool loop), deterministic reconciliation. Three runs per scenario; tokens are means.
londone
| Scenario | Baseline | Perfect | Our Solution | Intake tokens | Dispatch tokens | of which cached |
|---|---|---|---|---|---|---|
| stage1-basic | 40/1 | 5/1 | 5/1 | 0 | 51.9 K | 37.0 K (71%) |
| stage2-language | 40/1 | 5/1 | 5/1 | 4.9 K | 51.0 K | 36.8 K (72%) |
| stage3-partial | 26/1 | 0/0 | 0/0 | 4.9 K | 61.5 K | 44.8 K (73%) |
| stage4-replan | 40/1 | 5/1 | 5/1 | 4.0 K | 93.6 K | 72.9 K (78%) |
| stage5-triage | 40/1 | 0/0 | 0/0 | 23.3 K | 94.1 K | 59.3 K (63%) |
stockhome
| Scenario | Baseline | Perfect | Our Solution | Intake tokens | Dispatch tokens | of which cached |
|---|---|---|---|---|---|---|
| stage1-basic | 34/1 | 5/1 | 5/1 | 0 | 62.1 K | 49.0 K (79%) |
| stage2-language | 53/2 | 5/1 | 5/1 | 5.0 K | 60.7 K | 43.6 K (72%) |
| stage3-partial | 26/1 | 0/0 | 0/0 | 4.9 K | 54.2 K | 37.7 K (69%) |
| stage4-replan | 34/1 | 5/1 | 5/1 | 3.9 K | 102.4 K | 78.5 K (77%) |
| stage5-triage | 53/2 | 0/0 | 0/0 | 19.5 K | 73.8 K | 51.7 K (70%) |
feeladelphia
| Scenario | Baseline | Perfect | Our Solution | Intake tokens | Dispatch tokens | of which cached |
|---|---|---|---|---|---|---|
| stage1-basic | 34/1 | 5/1 | 5/1 | 0 | 65.6 K | 49.3 K (75%) |
| stage2-language | 34/1 | 5/1 | 5/1 | 5.0 K | 70.1 K | 53.2 K (76%) |
| stage3-partial | 27/1 | 0/0 | 0/0 | 4.9 K | 71.5 K | 52.8 K (74%) |
| stage4-replan | 34/1 | 5/1 | 5/1 | 4.0 K | 64.8 K | 48.6 K (75%) |
| stage5-triage | 34/1 | 0/0 | 0/0 | 23.3 K | 122.0 K | 77.7 K (64%) |
Every cell held on every run: 47 episodes, 15 pairs, no disagreement anywhere. So a single run is fair evidence about a team, and a result that differs from this table is a real difference in their policy rather than the dispatch model having an off day.
How to read the numbers
- Beat the baseline column. That is the acceptance bar, and it is deliberately loose: improve at least one safety column, be worse on neither.
- Perfect is reachable, but only just. The reference solution now matches the Perfect column on all 15 pairs. Perfect pays no intake error, so a team that lands on it has both a working extraction prompt and a dispatcher that revises; a team one notch above it has usually lost a single fire to a misread claim, which is a fair result and not a broken pipeline.
- Zero on stage 1, 2, or 4 is suspicious. Those bottom out at 5 casualties played perfectly; zero means a bug or a cheatcode, not a better policy. Stage 3 and stage 5 do clear outright.
- One run is enough. The reference solution repeated every cell in this table across three or four runs. A team whose own numbers swing between runs has a policy that is not deciding anything - usually a dispatcher that never revisits a truck already on the road, so which fire it saves depends on what got reported first.
- Watch the cached column. The reference solution sits at 63-79% of dispatch tokens. A team whose cache reads stay near zero has broken their stable prefix.
Extraction accuracy
Upstream of all of that: uv run python evals/extraction.py --n 42 over a stratified subset of datasets/contacts_labeled.jsonl (6 per source type), same intake model.
| Field | Accuracy |
|---|---|
| incident_type (target >=85%) | 100% (42/42) |
| reported_location (preserved) | 100% |
| severity (±1 band) | 95.2% |
| headcount (±2) | 100% |
| event_time (tick ±2) | 100% |
| all fields | 95.2% |
Roughly 980 tokens per contact (38.3 K read, 2.9 K written): MiMo V2.5 is not a reasoning model, so almost all of the spend is the prompt. If a team’s extraction sits below the 85% incident_type target, their dispatch numbers are not worth reading yet.
The instructive failure is event_time, which scored 57% before the prompt named the rule per source_type and 100% after. Left to its own judgement the model reads any present-tense report of an ongoing fire as a past event and stamps a tick on all of them. Worth raising with a team whose per-field breakdown has one column dragging the rest down: the fix is usually a stated convention, not a better model.


