All demosTower: radar control in milliseconds

Tower: radar control in milliseconds

A live radar scope where decision-machine-1 picks one air traffic control instruction per aircraft in conflict, against a rule-based autopilot and a 2.5 s chat-LLM loop.

/classify/rate/yes-no

How it works

The page runs a 50 NM terminal sector: range rings, runway 27, six entry fixes, two initial approach fixes and 20 to 40 aircraft generated from a seeded PRNG. Code steps the simulation every 0.5 s of sim time, predicts every pairwise conflict 120 s ahead by linear extrapolation, and writes each conflict out in words. Nothing is sent until you press Run, and every run stops itself after 60 seconds.

What the original asked Jev

The jev-tower experiment sent one JSON state and three free-text questions per aircraft in a single request, keyed a0_instruction, a0_urgency, a0_handoff: which of ten ATC instructions to issue, how urgent the situation is on a four-level scale, and whether the aircraft is clear and ready for handoff.

What we send here

decision-machine-1 has no free-text instructions field, so each aircraft becomes one short paragraph and the three questions become three batched calls over the same texts array — every aircraft in the cycle in one request per route, up to 32 texts:

  • POST /classify with { texts, labels } — five described labels, the kind of resolution: none ("The traffic has priority and is already turning, climbing or descending away."), vector ("The traffic crosses from the left or from the right, or it is head-on."), level ("The traffic is co-altitude and level with this aircraft, or just above or below it."), spacing ("The traffic is same direction, intruder ahead, in trail on the same track."), resume ("This aircraft is off route on an old vector and needs its own navigation.").
  • POST /rate with { texts, scale } — four levels, low to high: separation holds for 120 s / is lost in 75 to 120 s / is lost in 30 to 75 s / is lost in under 30 s. The level colours the halo and sizes the turn.
  • POST /yes-no with { texts, statement, when_true, when_false } — one statement shared by the whole batch: "This aircraft is clear of all traffic and ready for the next controller." An H goes on the data block above 0.9.

Two parts of the original ten-way question are arithmetic rather than judgement: which side to turn, and whether to climb or descend. Folding them into the label set measurably wrecked the classification — ten action labels collapsed onto one answer on a 32-state tuning set, where five well-separated labels land the right kind in the top three every time. So the model picks the kind, /rate picks the size, and the same geometry the rule policy uses picks the side. Two more wording rules came out of that tuning: label descriptions must echo the words the text actually uses, and they must be about the same length, because a longer description wins on length alone.

Batching and rate

In API mode one cycle covers every aircraft that needs a decision and has not been asked in the last second, so a cycle is three HTTP calls whatever the traffic level. Slow mode sends one aircraft per cycle and adds 2.5 s, to imitate a chat-LLM agent. Rules mode sends nothing. All calls go through the shared client in src/lib/dm1.ts, which paces the whole page at 2 calls per second and retries 429s; on a hard rate limit the controller backs off for 5 s and the rule policy keeps the sector attended.

The model proposes, the validator disposes

Every pick is checked against the cooldown, the altitude floor and ceiling, the speed envelope, and whether it would turn or climb into other traffic or bring a predicted loss closer. A rejected pick falls to the next-highest-probability instruction, and then to the deterministic rule policy. The instruction ticker names the reason each time that happens, so you can watch the model and the guardrails disagree.

Ported from the jev-tower Jev experiment. Every request here is live; the shared demo key allows about 2 requests per second across all visitors.