BRKMYR/Prototypes I built/INTENT Operator Console

INTENT Operator Console

A prototype command console for manned-unmanned teaming (MUM-T), where one human stays in charge of several uncrewed aircraft. The scenarios are air defence of sovereign airspace over Berlin. The machines can never act on their own, a person approves or refuses every action that matters, and the console writes each decision to an AI decision log that a reviewer can read afterwards. When a wingman loses contact its weapon goes safe, and one action stands the whole element down.

Prototype · 4 scenarios · 5-ship element · 306 tests

At a glance

What

A command console that keeps one human in charge of several uncrewed aircraft. The operator assigns tasks to an element of Collaborative Combat Aircraft (CCA, uncrewed wingmen) screening a crewed fighter over Berlin, approves or refuses each request with a key press, and every decision lands in an AI decision log on disk. A wingman that loses its link goes weapons safe and flies itself home, and one action stands the whole element down.

Why it matters

European air forces are ordering uncrewed aircraft faster than anyone has designed the seat that controls them. The open question is whether a person can stay genuinely accountable for several machines under time pressure. INTENT is one answer: safe by default, a bounded approval window, and a replayable record that shows who decided what.

How it was built

A Python simulator with toy flight physics at 60 Hz, a Berlin map baked from Overture Maps, and a PettingZoo environment for multi-agent reinforcement learning (MARL, several agents learning together). The console, safety gate and decision log are my own code, implemented with Claude Code agents from my spec. A trained policy was set aside because a scripted pilot flew better.

The sixty second escort_transit sortie, recorded headless by the shipping app (python main.py demo escort_transit --autopilot --record --headless). Five uncrewed CCAs screen the crewed lead across Berlin against four waves of incoming fighters. The fourth wave appears inside minimum standoff range and has to be met close in. Every element on screen (map, aircraft, mission goals, engagement authorization, AI decision log) is drawn by the same code that renders the interactive window. The capture ends on the MISSION ACCOMPLISHED card, which appears only when every declared goal is met, and then on the after-action review with its decision timeline. Debrief: nothing ever fired at the human pilot, five of five uncrewed aircraft came home, all four waves were turned back.


Problem

One human, several uncrewed wingmen, time pressure.

As MUM-T concepts move from slideware into procurement, the flight profile is the easier half of the problem. The hard half is one operator tasking several aircraft at once, authorizing a weapon release within seconds, and leaving a message trail that survives an after-action review.

Existing MARL testbeds exist to train agents. The operator experience is an afterthought where it exists at all. A product team building the operator side needs the console prototyped on the same simulator that would train the autonomy, so one scenario replay exercises both. That is what INTENT is.

What it does

Four tasks, three postures, one human approval per action.

All four scenarios are defensive. The element escorts a transit and answers intruders that come to it, and nothing is engaged without a human approval inside the authorization window. INTENT turns operator intent into aircraft behaviour and keeps a record of every step. The operator selects a wingman or the whole element, assigns one of four tasks, sets the rules of engagement (ROE, the posture that says when a weapon may be used) and answers engagement requests with a key press. Scripted autonomy flies the aircraft, a deterministic safety gate bounds its actions, and mission goals declared in the scenario file are scored every frame and summarised in the after-action review.

Solution

Console first, testbed underneath.

INTENT is an operator console (tasking, PHC engagement authorization, ROE cycling, AI decision log) on top of a PettingZoo ParallelEnv, the standard multi-agent simulation interface. The console attaches only in main.py. The training scripts import no console code, which I keep as a reviewed convention rather than a tested boundary.

The autonomy that flies the demo is scripted. A route pilot follows the scenario waypoints, a heuristic pilot changes behaviour with task and ROE, and the safety gate clips every action before physics. A 5M step PPO policy exists in the repo, regressed below the scripted pilot in training, and is never loaded by the demo. Four scenarios ship in the box, each with a ghost operator timeline that drives the console for replay. The --record flag streams frames straight into an H.264 encode, or an animated GIF where ffmpeg is absent, so the video above is produced by the app itself.

Scenario

escort_transit

West to east Berlin transit, four waves. Three are broken beyond the intruders’ own detection range. The fourth is a terrain-masked pop-up met at close range, with no standoff option left. 26 timeline entries.

Scenario

pincer

Two-axis pressure. Wingmen have to be re-tasked mid-mission and ROE cycling matters. 16 timeline entries.

Scenario

saturation

Enough tracks to exceed a single operator’s span of control. Stress test for the console. 19 timeline entries.

Scenario

contested_link

A wingman loses its link with a request pending and flies the lost-link procedure home. A late raid is turned back; the second raider runs for the border and the operator aborts rather than pursue. 18 timeline entries.


Result

The protected aircraft was never fired on, and a test asserts it.

In the shipped escort_transit run, five wingmen turn back four waves of incoming aircraft and the crewed lead is never fired on. tests/test_superiority_outcome.py runs the same headless demo path and asserts that outcome. A determinism test takes a fingerprint of 500 steps of trajectory with sha256, a standard way to tell whether two files are identical, and asserts the two runs match. The AI decision log of the shipped run holds 79 lines: 14 taskings and ROE changes, 15 engagement requests, 19 authorizations, 16 weapon releases and 15 state transitions. All five wingmen appear as the source of a request, an authorization or a release, because the whole element is under the console.

Human approved

16 / 16

Wingmen home

5 / 5

Shots at the protected lead

0

Decision log lines

79

PHC QUEUE · ONE STATE PER PLATFORM SAFE ARMED REQUESTED AUTHORIZED COOLDOWN SOLUTION REQUEST Y KEY RELEASE N KEY OR 5 S EXPIRY 2 S COOLDOWN WEAPONS FREE SKIPS THE REQUEST · WEAPONS HOLD STOPS AT ARMED
Why a wingman cannot fire on its own. A weapon leaves the rail only from the AUTHORIZED state. Under WEAPONS TIGHT the operator has five seconds to answer each request, and silence returns the platform to SAFE.
SENSOR ASYMMETRY · RINGS DRAWN TO SCALE CCA DETECTS INTRUDER · 2186 PX RELEASE RANGE · 1900 PX CLOSE RANGE · 420 PX CCA INTRUDER DETECTS CCA · 662 PX INTRUDER 1 PX = 4 M · TOY SCALE · RANGE SCALES WITH THE FOURTH ROOT OF CROSS-SECTION
Why the outcome is emergent rather than scripted. The wingman sees the intruder at three times the range the intruder sees the wingman, so it can act first from standoff distance. The sensor model in sensing.py uses the radar range equation, so three of four waves break before they detect anything.

Contested link

Out of contact means weapons safe, and control comes back through a person.

In the contested_link run, CCA-2 loses its link to the console while its engagement request is waiting for an answer. The request is cancelled in the same step and its weapon stays safe for the whole outage. CCA-3 is tasked onto the track. After ten seconds out of contact CCA-2 turns for home on its own, and the log names CCA-2, not the operator, as the source of that decision. The link returns after 25 seconds. CCA-2 keeps flying home with its weapon safe until the operator gives it a new order. Near the end a two-ship raid comes in. CCA-3 asks to engage the lead raider, the operator clears it and the raider is turned back. The second raider breaks off and runs for the border while a wingman is still asking to engage it. Chasing it would take the element out of the corridor, so the operator aborts: the open request is cancelled in the same frame, every weapon goes safe, and a banner on the map says what happened and why. tests/test_contested_link_outcome.py runs the shipped headless loop and checks each of these claims against the decision log itself, and a second run must produce an identical log.

Releases out of contact

0

Time out of contact

25 s

Abort to all weapons safe

same frame

Human approved

10 / 10

The contested_link sortie, recorded the same way as the video above. Watch the CCA-2 card: LINK LOST with the seconds since contact, then the lost-link procedure, then LINK BACK with a prompt to re-task. Around 40 seconds the element fires on the lead raider of the late raid. At 45 seconds the abort is deliberate: the remaining raider is leaving, and following it out of the corridor is not the mission. Only the link to the console is modelled. The aircraft still share their sensor picture with each other.

The INTENT console in its dark theme, with three engagement requests drawn on the map and the reasons for the first one under the authorization queue.

The same console in its dark theme (--dark), six seconds in. Three wingmen ask to engage the same track. Each request is a line on the map with the seconds left to decide, and under the queue the console says why: 10.2 km, closing at 839 m/s, the intruder could see the wingman in 5 seconds, four wingmen hold the track.


Solution architecture

What it stands on, and what is new.

Inputs and datamap and scenarios
Overture Maps Berlinabout 119,000 building footprints baked into a 4096 px tactical map, fetched with DuckDB
scenario packsroutes, raid waves, link outages, ghost operator timeline and mission goals for four sorties, JSON
baked map and one scenario per run
Models and algorithmswhat flies and what sees
flight physics 60 Hztoy fixed-wing kinematics with turn-rate and bank limits
RCS sensor modelradar range equation on RCS, the radar cross-section that says how big a target looks to radar. Detection range scales with the fourth root of it
scripted pilottask-modulated heuristic pilot, waypoint pilot and ghost operator
SB3 PPO, shelvedStable-Baselines3, supersuit, PyTorch. PPO, a standard reinforcement learning method, trained for 5M steps, scored below the scripted pilot, never loaded by the demo
proposed actions per wingman per step
Engineown code, Python
PettingZoo envMissileDefenseEnv on the ParallelEnv API, the multi-agent simulation loop
safety gatedeterministic clip of every action before physics, logs interventions
PHC auth queueSAFE, ARMED, REQUESTED, AUTHORIZED, COOLDOWN per platform, with a forced-safe path for abort and lost link
C2 link modelscripted outages, the lost-link procedure and link status messages
C2 message busten command and control message classes modelled on STANAG-4586, a NATO messaging standard, ring buffer and JSONL sink
PettingZoo, GymnasiumMIT, the environment API
messages, frames and mission state
Outputs and interfaceswhere the operator lands
operator consoleplatform cards, track table, tasking, authorization queue, mission panel, event strip
AI decision logone JSON line per event with step, class, source, destination, payload
after-action reviewdecision timeline from the log, goal checklist, protection, survival, interventions, JSON export
pygame-ce, ffmpegLGPL window and drawing, H.264 recorder with GIF fallback, headless SDL driver
Built in this projectThird party

Sources: Overture Maps data is CDLA-Permissive 2.0 with the buildings theme partly ODbL via OpenStreetMap, fetched with DuckDB (MIT). PettingZoo, Gymnasium, Stable-Baselines3 and supersuit are MIT, PyTorch is BSD, pygame-ce is LGPL, Pillow is HPND, ffmpeg is an optional LGPL or GPL binary. The C2 (command and control) message classes are modelled on the NATO STANAG-4586 groups and are not compliant with the standard. Everything else is this project.

Why it matters

The human stays accountable, and the record proves it.

Uncrewed aircraft are arriving in European air defence whether or not the control seat is ready, and the question that decides whether they are acceptable is who remains answerable for what they do. INTENT puts that question in code rather than in slides. The machine holds no authority of its own, the path to any consequential action runs through a person, and the log lets a reviewer reconstruct each decision and name who made it. It also covers the moment contact is lost: the weapon goes safe, and control comes back only through a human order. The saturation scenario also shows where one operator runs out of attention. That limit matters for product design as much as for engineering.

In five years

Consoles of this kind will sit between crewed aircraft and their uncrewed escorts in European air forces, and the message model will have to speak real command and control standards. Meaningful human control is already moving from principle into regulation, and the decision log is what turns the principle into something an auditor can check. INTENT is an early, toy scale version of that seat, with the human approval loop already in the right place.

How it was built

A sandbox, a failed policy, then the console.

Python

20,958lines

Tests

306

Commits

60

Build span

241days

Active days

13

Runtime deps

9+3 unlisted

Spec, PRD, product doctrine, thresholds and review by hand. Implementation with Claude Code agents. The console work is squashed into one commit. Measured from the repository.

The sandbox came first. Physics, the baked Berlin map and the PettingZoo environment landed in the first two days. Reinforcement learning followed with the RLlib wiring, SB3 PPO training and the safety gate around it. The trained policy regressed below the scripted pilot, so I shelved it and the project stood still for six months. When work resumed, it pivoted to the operator console, three scripted demo scenarios and the sensor model that makes the outcome emergent. A later pass put the whole five-ship element under the console and added the abort, the lost-link procedure and a fourth scenario to exercise them. A design pass after that made every request explain itself, reserved red for consequences, added a dark theme and a decision timeline, and fixed an abort that looked like a missed engagement on video because nothing on screen said why the wingmen held fire. The hardest part was making the demo falsifiable, so the superiority test drives the shipped demo path. Thrown away: the centralized critic claim and manual pilot mode as the main entry point.

Algorithms and mathFixed-wing kinematics with bank and turn-rate limits at 60 Hz, a radar range equation sensor model and a PHC state machine. No learning that worked.
Systems integrationOverture Maps through DuckDB, GeoJSON baked to a 4096 px PNG, PettingZoo and Gymnasium, SB3 and an RLlib wrapper, pygame-ce, ffmpeg piping and headless SDL in one loop.
Data and domainMUM-T doctrine, ROE postures, STANAG-4586 message groups and the radar equation drive the design of the PRD, the message bus and the sensor model.
Evaluation rigor306 tests, a sha256 determinism test, a superiority test on the shipped demo path and a contested-link test that checks its claims against the decision log, against a physics scale mismatch of 4 versus 5.8 m per px and an RL result evaluated only as regressed.
Interface and productA full operator console with tasking, an authorization queue that explains each request, element abort, link status, light and dark themes, event strip, mission panel, after-action review with JSON export, auto-director camera and mp4 recorder.
Difficulty 19 of 25 · AdvancedRelative to one competent engineer building it alone

Success criteria

When this counts as working.

Metrics, KPIs, evals

Numbers from the shipped runs.

KPIValueWhat it means
Time to authorize 5 s window From request to Y or N key press. Single key press authorization is a policy choice. It is fast and it makes the decision log entry unambiguous. An unanswered request expires to SAFE.
C2 message classes 10 Platform status, task assign and accept, engagement request, authorization and denial, weapon release, state transition, safety intervention and link status. Each one writes a JSON line to the AI decision log.
Scenario reproducibility sha256 over 500 steps Same seed, same scenario, same operator timeline gives the same trajectory hash. The decision log is identical except for the wall-clock timestamp field on each line, and the contested_link test asserts exactly that across two runs.
Headless recall SDL dummy Same code path renders the live window and the recording. The video above is one such capture, unedited.
Span of control 5 of 5 The console tasks and authorizes all five wingmen. Until this pass it reached only three, and orders to the other two were dropped without a trace. The saturation scenario stress-tests the limit.
Releases out of contact 0 Counted from the decision log in the contested_link run, not asserted. Two independent interlocks: the authorization queue holds a link-lost wingman safe, and the fire command checks link state again.
Abort to all weapons safe same frame Weapons first, then WEAPONS HOLD, then everyone home, so nothing can be released while the abort is being applied. A single X press only arms it: quit sits one key away.
Training decoupling convention The console lives in main.py and the training scripts import no console code. Reviewed on every change, with no test asserting the boundary.
What this does not do yet.
  • Fly a learned policy. A 5M step PPO run regressed below the scripted and random baselines and converged to leash boundary behaviour that the safety gate contained. The demo never loads it, and a fresh clone runs the heuristic pilot.
  • Networked C2 transport. The decision log is written locally, and a real deployment needs a wire protocol and a synced clock.
  • Radio and jamming model. Link loss is scripted per scenario: the outage, its timing and its length are data, with no radio propagation, latency or jamming behind them.
  • Operator ergonomics testing. The KPIs above are engineering KPIs and no user has sat in the seat, so there are no workload (NASA-TLX) or trust calibration trials yet.
  • Standard symbology. Map chips carry NATO APP-6 style affiliation frames, but the full symbol set is not implemented, and the track table still ranks by range rather than by explained threat.
  • Candidates for the next epic are theater expansion, RL rehabilitation and an intelligence layer. None is started.