Code¶
Overview¶
The codebase is organized around the core execution flow:
flowchart TD
A["Load scenario"] --> B["Create simulation state"]
B --> C["Detect current and predicted conflicts"]
C --> D["Reason about safety and priority"]
D --> E["Generate a resolution plan"]
E --> F["Apply maneuvers physically"]
F --> G["Emit JSONL events"]
G --> H["Replay in GUI"]
The most important intelligent behavior is implemented by the interaction between:
- Jason AgentSpeak sources;
- the Prolog safety reasoner;
- the STRIPS-style planner;
- the managed simulation engine;
- the explanation and event layers.
Intelligent Decision Flow¶
flowchart TD
A["Aircraft states at tick T"] --> B["ConflictDetector"]
B --> C{"Conflict detected or predicted?"}
C -- "No" --> N["Continue route"]
C -- "Yes" --> D["SafetyReasoner"]
D --> E["Priority reasoning"]
D --> F["Maneuver feasibility"]
E --> G["ResolutionPlanner"]
F --> G
G --> H["Candidate maneuvers"]
H --> I["SecondaryConflictAwareResolutionPlanner"]
I --> J["Forward simulation"]
J --> K{"Creates secondary conflict?"}
K -- "Yes" --> L["Reject maneuver"]
L --> H
K -- "No" --> M["Accept ResolutionPlan"]
M --> O["ManeuverApplier"]
O --> P["Updated SimulationState"]
P --> Q["SimulationEventRecorder"]
Q --> R["JSONL events"]
Domain Model¶
The domain model is located in src/main/kotlin/domain.
Important classes include:
Aircraft;Position;FlightLevel;Velocity;Waypoint;Route;Scenario;SimulationState;Conflict;Maneuver;ResolutionPlan;WeatherZone.
The model is intentionally immutable. For example, when a maneuver changes an aircraft altitude, the system creates a new Aircraft object inside a new SimulationState.
This supports safer testing and easier reasoning about simulation history.
Scenario Loading¶
Scenarios are loaded from JSON by JsonScenarioLoader.
The loader:
- reads a JSON file;
- decodes it into DTO classes;
- converts DTOs to validated domain objects;
- throws
ScenarioLoadingExceptionon invalid files.
Example scenario structure:
{
"name": "simple_conflict",
"maxTicks": 12,
"separation": {
"horizontal": 5.0,
"vertical": 1000
},
"aircraft": [
{
"id": "AZA123",
"x": 0.0,
"y": 0.0,
"altitude": 30000,
"speed": 1.0,
"priority": "normal",
"emergency": false,
"route": [
{ "name": "W1", "x": 5.0, "y": 5.0 }
]
}
]
}
Unknown JSON keys are rejected. This makes scenario files strict and reproducible.
Simulation Engine¶
Aircraft Movement¶
AircraftMover moves each aircraft one tick toward its active waypoint. Movement is based on the aircraft's current position, velocity, and route.
The movement model is simple:
next_position = current_position.stepTowards(active_waypoint, speed)
Conflict Detection¶
ConflictDetector detects unsafe aircraft pairs. It computes:
- horizontal distance;
- vertical distance;
- current conflicts;
- predicted conflicts over a horizon.
A predicted conflict is generated by simulating future aircraft movement and checking separation at each future tick.
Managed Simulation¶
ManagedSimulationEngine is the main execution orchestrator.
Its responsibilities include:
- build initial state from scenario;
- detect current and predicted conflicts;
- select a conflict for planning;
- generate a resolution plan;
- schedule the selected maneuver;
- advance the simulation tick by tick;
- apply scheduled maneuvers physically;
- handle weather replanning events;
- collect states and conflicts;
- return a managed run result.
This is the component that connects the intelligent decision layer with the physical simulation.
Maneuver Application¶
ManeuverApplier applies selected maneuvers to the simulation state.
Examples:
CLIMBchanges the aircraft flight level.DESCENDchanges the aircraft flight level.SLOW_DOWNreduces speed.RESUME_SPEEDrestores nominal speed.REROUTE_TO_WAYPOINTreplaces the current route with a new waypoint.AVOID_WEATHER_ZONEis treated as symbolic unless combined with reroute.
This separation is important: planners decide; the maneuver applier executes.
Agents¶
The project includes real Jason / AgentSpeak(L) source files.
The main conceptual agents are:
aircraft_agent;sector_controller;conflict_detector;resolution_planner;explanation_agent.
BDI Concepts¶
The Jason agents expose:
- beliefs;
- goals;
- intentions;
- message passing;
- delegation through
.send(...).
The Kotlin class JasonAgentSmokeAnalyzer statically analyzes the .asl files and extracts observable BDI concepts. This avoids fragile runtime coupling while still ensuring that the project contains real BDI-oriented agent sources.
Agent Responsibilities¶
Aircraft Agent¶
Represents aircraft-level reporting. It communicates aircraft state and may report emergency-related facts.
Sector Controller Agent¶
Coordinates the sector. It receives conflict information, delegates scanning or resolution, and communicates selected decisions.
Conflict Detector Agent¶
Represents the role responsible for detecting unsafe aircraft pairs.
Resolution Planner Agent¶
Represents the planning role. It is conceptually responsible for producing resolution actions.
Explanation Agent¶
Represents the explainability role. It produces explanations or receives facts needed to justify system behavior.
BDI Agent Collaboration¶
sequenceDiagram
participant Aircraft as aircraft_agent
participant Controller as sector_controller
participant Detector as conflict_detector
participant Planner as resolution_planner
participant Explainer as explanation_agent
Aircraft->>Controller: tell aircraft_state(...)
Controller->>Detector: achieve scan_sector
Detector-->>Controller: tell conflict_detected(A1,A2)
Controller->>Planner: achieve resolve_conflict(A1,A2)
Planner-->>Controller: tell resolution_plan(...)
Controller->>Aircraft: tell selected_maneuver(...)
Planner->>Explainer: tell planning_trace(...)
Controller->>Explainer: achieve explain_resolution(...)
Explainer-->>Controller: tell explanation(...)
Prolog Logic¶
The symbolic reasoning layer is accessed through the SafetyReasoner interface.
The tuProlog implementation is TuPrologSafetyReasoner.
The reasoner answers questions such as:
fun isConflictUnsafe(conflict: Conflict, state: SimulationState): Boolean
fun isManeuverAllowed(aircraftId: String, maneuver: Maneuver, state: SimulationState): Boolean
fun priorityOf(aircraftId: String, state: SimulationState): Int
fun explainDecision(decisionId: String): List<String>
Facts¶
The Kotlin layer translates simulation state into Prolog facts. These facts represent aircraft, priorities, emergencies, altitudes, maneuver targets, and separation values.
Rules¶
The Prolog theory encodes symbolic rules such as:
- minimum horizontal separation;
- minimum vertical separation;
- emergency priority;
- low-fuel priority;
- valid altitude change;
- maneuver allowed;
- unsafe pair;
- explanation facts.
Exact predicate details are documented in the Prolog source file and comments.
Symbolic Reasoning Interaction¶
sequenceDiagram
participant Planner as Kotlin Planner
participant Adapter as TuPrologSafetyReasoner
participant Theory as airspace_rules.pl
participant Solver as tuProlog Solver
Planner->>Adapter: isManeuverAllowed(aircraft, maneuver, state)
Adapter->>Adapter: translate Kotlin state to Prolog facts
Adapter->>Theory: combine rules and generated facts
Adapter->>Solver: solve maneuver_allowed(...)
Solver-->>Adapter: Solution.Yes or Solution.No
Adapter-->>Planner: Boolean decision
Planner->>Adapter: priorityOf(aircraft, state)
Adapter->>Solver: solve priority(...)
Solver-->>Adapter: numeric priority
Adapter-->>Planner: Int priority
Planning¶
The project contains a small STRIPS-style planning implementation.
STRIPS Concepts¶
The planning model includes:
- propositions;
- actions;
- preconditions;
- add effects;
- delete effects;
- initial state;
- goal state.
A planning problem is solved by a breadth-first search with bounded depth.
Resolution Planning¶
StripsResolutionPlanner translates conflicts into candidate maneuver actions. It generates actions such as climb, descend, or slow down, then asks the STRIPS planner to find a sequence that reaches a resolved-conflict goal.
Secondary Conflict Prevention¶
SecondaryConflictAwareResolutionPlanner improves the basic planning layer.
The problem it addresses is important: a maneuver can solve the primary conflict but create a new conflict with a third aircraft.
Example:
Primary conflict: IBE222 / SAS111
Naive maneuver: climb(SAS111, 32000)
Secondary conflict: SAS111 / EZY333
Safer maneuver: descend(SAS111, 28000)
The planner prevents this by:
- generating candidate maneuvers;
- checking each maneuver with the symbolic reasoner;
- applying the maneuver in a simulated future state;
- advancing the simulation for a prediction horizon;
- rejecting any maneuver that creates new conflicts;
- selecting the first safe alternative.
This combines symbolic filtering with forward simulation.
Weather Replanning¶
WeatherReplanningService handles active weather zones.
When a weather zone becomes active, the service checks whether the remaining route of an aircraft intersects the zone. It does not only check whether a waypoint is inside the zone; it checks route segments against the circular weather area.
If the route is unsafe, it creates a plan with:
avoid_weather_zone;reroute_to_waypoint.
The safe waypoint is generated laterally relative to the aircraft and weather-zone center, so the aircraft does not continue along a path that crosses the storm.
Weather Replanning Flow¶
flowchart TD
A["Dynamic event: activate weather zone"] --> B["WeatherReplanningService"]
B --> C["Check remaining route segments"]
C --> D{"Route intersects active zone?"}
D -- "No" --> E["Keep current route"]
D -- "Yes" --> F["Build lateral safe waypoint"]
F --> G["Generate avoid_weather_zone maneuver"]
F --> H["Generate reroute_to_waypoint maneuver"]
G --> I["SafetyReasoner feasibility check"]
H --> I
I --> J{"Maneuvers allowed?"}
J -- "No" --> K["No replanning decision"]
J -- "Yes" --> L["WeatherReplanningDecision"]
L --> M["ManeuverApplier changes route"]
M --> N["RouteSnapshotEvent"]
N --> O["GUI shows new route and waypoint"]
Explanation Layer¶
ExplanationService creates readable explanations for:
- no-conflict baseline runs;
- generated resolution plans;
- unresolved conflicts;
- weather replanning decisions.
Explanations are emitted as JSONL events and shown in the GUI.
Example explanation:
STRIPS generated plan 'secondary-safe-predicted-0-4-AZA123-DLH456'
with actions [climb(DLH456,32000)] for conflict 'predicted-0-4-AZA123-DLH456'.
The explanation layer is rule-based and deterministic. It does not use an LLM.
Event Logging¶
SimulationEventRecorder converts simulation results into structured events.
Main event types include:
aircraft_state;route_snapshot;conflict_detected;plan_generated;maneuver_selected;belief_update;explanation;weather_zone_activated;replanning_triggered.
Each event is serialized as one JSONL line. This makes the simulation replayable and inspectable.
GUI Code¶
The GUI is located in gui/app.py.
It performs the following tasks:
- loads a sample or uploaded JSONL file;
- validates events;
- builds a pandas DataFrame;
- creates a tick slider;
- renders map, timeline, agents, explanations, and raw event tabs.
The GUI visualizes:
- aircraft as airplane symbols;
- actual trails;
- planned routes;
- waypoints;
- weather zones;
- conflict lines;
- altitude profiles;
- vertical separation profiles;
- BDI traces;
- explanation messages.
The helper _arrow_safe_dataframe converts complex JSON values to strings before passing them to Streamlit. This prevents PyArrow serialization errors when columns mix scalar values and lists.
GUI Replay Flow¶
flowchart TD
A["JSONL event file"] --> B["validate_events.py"]
B --> C{"Valid file?"}
C -- "No" --> D["Show validation errors"]
C -- "Yes" --> E["pandas DataFrame"]
E --> F["Tick slider"]
F --> G["2D map"]
F --> H["Timeline"]
F --> I["Agents and BDI tab"]
F --> J["Explanations tab"]
F --> K["Raw events table"]
G --> L["Aircraft symbols"]
G --> M["Trails and waypoints"]
G --> N["Weather zones"]
G --> O["Vertical separation charts"]
CLI Execution Flow¶
The CLI entry point is AeroGuardCli.kt.
It:
- parses command-line options;
- runs Jason source smoke analysis;
- loads a JSON scenario;
- initializes the tuProlog reasoner;
- runs the managed simulation;
- prints summaries;
- generates explanations;
- writes JSONL events.
Example:
./gradlew run --args="--scenario scenarios/weather_replanning.json --events build/aeroguard/events/weather_replanning_events.jsonl --explain"