FlowX is an EuroScope plugin for VATSIM air traffic controllers. It is primarily aimed at delivery, ground, and tower controllers and provides departure and arrival flow management, and a range of convenience functions.
The plugin ships with a config.json file that defines all airport-specific data. It is currently configured for LOWW (Vienna International Airport). Adding other airports requires only changes to config.json — no recompilation is needed.
- Getting Started
- Tag Items
- Tag Functions
- Taxi Planning
- Aircraft Ground Tags
- Custom Windows
- Start Menu
- Assists
- Notifications
- Chat Commands
- config.json Reference
- Testing
- Contributing
- License
- EuroScope (developed against v3.2.3)
- A sector file / profile that includes the airports you want to configure in
config.json
- Download the latest
FlowX.zipfrom the Releases page. - Extract
FlowX.dll,config.json, all sound files and the icon file into your plugin directory. - In EuroScope open OTHER SET → Plug-ins, click Load and select
FlowX.dll. - Allow the FlowX to draw onto
Ground Radar display - Successful load is confirmed in the Messages chat:
[08:34:10] FlowX: Version 0.7.0 loaded. - Add the desired tag item columns to your departure list (see Tag Items below).
Sound files (all must be placed alongside
FlowX.dll):
nap.wav— plays when the NAP reminder window appears; stops on acknowledgement.airbourne.wav— plays once when an aircraft is detected airborne.readyTakeoff.wav— plays when a lined-up aircraft has been clear for takeoff for 5 seconds (departure separation resolved).gndtransfer.wav— plays when a landed inbound transitions to GND frequency.click.wav— plays on start-menu clicks.noRoute.wav— plays when the taxi router cannot find a valid route to the assigned holding point.taxiConflict.wav— plays when a taxi conflict is detected between two aircraft with active routes.
flowx.icoused for custom window icons and the start menu.
settings.jsonis created automatically by the plugin in the same directory asFlowX.dll. It stores all plugin preferences, the screen positions of all custom windows, and the last NAP reminder dismissal date. Delete it to reset everything to defaults.
Tag items are added to EuroScope departure lists or tag definitions via Tag Item Type = FlowX / <name>. Only the following five items are registered with EuroScope. All other columns (HP, spacing, TTT, etc.) are rendered inside the custom GDI windows described below.
| Tag Item | Typical display | Description |
|---|---|---|
| Push+Start Helper | ->121.600 / OK / !RWY / ... |
Validates the flight plan and shows the next frequency or an error code. Left/right click triggers the ONFREQ/ST-UP/PUSH function. See table below for all values. |
| Taxi Out? | T / P |
Green T for taxi-out stands, P for pushback stands. Determined by point-in-polygon against taxiOutStands polygons in config. |
| New QNH | X (orange) |
Appears when a METAR change contains a new QNH and the aircraft has a clearance. Click to acknowledge. |
| Same SID | LANUX2A |
SID name colour-coded by group. Used in standard EuroScope departure lists. |
| ADES Type-Y | LOWW / WW523 (turquoise) |
Destination ICAO for standard IFR plans. For type-Y (mixed IFR/VFR) plans, shows the last IFR waypoint in turquoise instead. |
| Display | Colour | Meaning |
|---|---|---|
!RWY |
Red | No departure runway assigned |
!ASSR |
Red | No squawk code assigned |
!CLR |
Red | No clearance flag set |
1234 |
Orange | Squawk not yet set by pilot |
OK |
Green | Cleared and ready; controller is GND or above |
->121.600 |
Green | Cleared and ready; shows next frequency (GND, TWR, APP, CTR, or UNICOM) |
Only two tag functions are registered with EuroScope and can be assigned to tag column buttons. All other controller actions (HP assignment, line-up, takeoff, transfer, etc.) are triggered by left/right click inside the custom GDI windows.
| Function | Description |
|---|---|
| Set ONFREQ/ST-UP/PUSH | Sets the appropriate ground state. DEL position: sets ONFREQ. GND/TWR: detects push vs. taxi-out stand and sets ST-UP or ONFREQ accordingly. |
| Clear New QNH | Removes the new-QNH flag from the flight strip so the orange X disappears. |
FlowX includes an interactive taxi router that computes A*-based routes over the OSM taxiway graph. Planning is triggered directly on the radar screen — no separate menu is needed.
Right-click any ground aircraft on the radar screen to enter taxi planning mode. The plugin automatically determines whether to start taxi planning or pushback planning based on the aircraft's current position:
- Inside a parking stand polygon (and not in a taxi-out apron) → pushback planning
- Everywhere else (taxi-out apron, rolling, inbounds) → taxi planning
When taxi planning starts, the router immediately computes a suggested route to the aircraft's destination — the assigned holding point for departures, or the assigned stand approach point for inbounds. This suggested route is shown as a yellow line.
Moving the mouse across the taxiway network shows a magenta line — a live preview of the route the router would compute to wherever the cursor is snapped. The cursor snaps to nearby graph nodes, intersection waypoints, holding points, and the yellow suggested route itself.
- Left-click near the aircraft (within ~80 px of the radar target) — accepts the yellow suggested route
- Left-click anywhere else — accepts the magenta manual route to the cursor position
- Side mouse button (X1/X2) — shortcut to accept the yellow suggested route from anywhere on screen
- Middle-click — add a custom waypoint the route has to pass
- Middle-click + drag — draw a custom waypoint path; the router threads the A* route through the drawn points in sequence
Pressing the middle mouse button places a waypoint at the cursor position. Multiple custom waypoints can be placed along the route.
The controller can also draw a path: hold the middle button and drag across the taxiway network — the router collects nearby graph nodes as preferred nodes along the drawn path — then release to commit the waypoint. The route is immediately recomputed through all waypoints in order. Multiple waypoints can be placed this way; each gesture adds one.
When waypoints are present, the router operates in custom route mode: all flow restrictions and preferred-route rules from config.json are disabled, and the router finds the geometrically shortest path through the waypoints. This lets the controller override both one-way taxiway flow and any configured preferred routing for that specific aircraft.
Once a route is accepted (by any of the methods above), the line turns green. The ground state is automatically set to TAXI (for departures) or TXIN (for inbounds). The route is tracked and monitored for deviations and conflicts from this point on.
Double right-clicking an aircraft clears its assigned route.
Every 250 ms, each tracked aircraft's distance to its assigned route polyline is measured. If it exceeds deviationThreshM (default 40 m) while the ground speed is above minSpeedKt (default 3 kt), the aircraft is flagged. A relaxed threshold (endpointDeviationThreshM, default 80 m) applies within endpointRadiusM (60 m) of the route's start or end node, to account for stands and holding points sitting slightly off the graph. When deviation is detected, a yellow connector line is drawn from the aircraft to the nearest point on its route, and a yellow !ROUTE label appears next to the aircraft symbol.
For each tracked moving aircraft, a timed predicted path is built by projecting forward along its remaining route at the current ground speed, up to maxPredictS seconds (default 60 s). The system then performs a pairwise check across all predicted paths: when two paths cross, the estimated arrival times at the intersection are compared. If they differ by less than conflictDeltaS (default 30 s), it is flagged as a conflict. Same-direction paths (bearing difference < sameDirDeg, default 45°) are excluded to avoid false alerts on aircraft following each other.
Conflicts are colour-coded by time-to-intersection:
- 15–30 s: yellow marker at the conflict point and a yellow
CONFLICTlabel next to each aircraft involved. - < 15 s: marker and label both turn red — the most urgent tier.
If the Taxi Conflict sound notification setting is enabled, an audio alert fires once per conflict pair as soon as the predicted separation drops below 15 s and has persisted for at least 2 seconds. The sound plays at most once per pair; it is re-armed only if the conflict clears and then reappears.
Right-clicking an aircraft at its parking stand enters pushback planning mode. The router computes viable taxiway pivot points in the aircraft's push direction (backwards from the aircraft heading), filtered by wingspan.
Moving the mouse adjusts the blue push-zone preview — a line segment on the taxiway that the aircraft will occupy during the push. Left-clicking confirms the zone: the line turns orange, the segment is reserved as a push block, and the aircraft's ground state is set to PUSH. Other taxi routes will treat the orange block as an obstacle.
FlowX draws additional overlays directly on the radar screen for ground-taxiing aircraft. These are rendered next to the GroundRadar plugin target.
Shown for every departing aircraft in TAXI or DEPA ground state. The tag is draggable and connected to the radar target by a leader line. It contains:
- Dep info text — mirrors the DEP? column from TWR Outbound (e.g. spacing to the previous departure in seconds or NM), coloured green / yellow / red by readiness.
- SID colour dot — a filled circle below the dep info text, coloured by the aircraft's SID group (same colour coding as the Same-SID tag item).
- HP label — the assigned holding point (e.g.
A2), shown to the right of the SID dot when one is assigned. ,Tsuffix — appended to the dep info text when the aircraft has not yet been transferred to TWR by the GND controller.- Queue number — when the aircraft has a departure queue position, its number is shown in a small box above the radar target dot.
Right-clicking the SID colour dot appends the aircraft to the end of the departure queue for its runway (next available number). Left-clicking the dot triggers a frequency transfer to TWR (GND controllers only).
To insert an aircraft at a specific queue position, use the # column in the TWR Outbound list: left-click opens a popup to select any position (shifting others down), right-click appends to end.
Queue positions auto-progress: once an aircraft transitions to LINE UP or DEPA state, it is automatically removed from the queue and all same-runway positions behind it are shifted down by one. This keeps the displayed numbers consistent without manual intervention.
A trail of small hollow squares drawn behind each correlated radar target on the ground, replacing (overlaying) the dim grey dots Ground Radar paints by default. Dots use the GroundRadar tag colour (Color_Arrival / Color_Departure from GRpluginSettings.txt, or built-in defaults if that file is missing) so the trail visually matches the rest of the ground radar picture.
- Sampled once per second. When an aircraft stops moving its trail drains at one dot per second, so a parked aircraft visibly fades to nothing.
- Arrival (flight plan destination is one of the configured airports) → pale green by default.
- Departure (flight plan origin is one of the configured airports) → orange by default.
- Older dots fade toward black; the newest dot keeps the full tag colour.
- Configurable via GND tail dots in the Settings window (Taxi group). Range 0–30; setting 0 disables the feature entirely.
Shown for landed inbound aircraft once their ground speed drops below 50 kt for the first time after touchdown. Indicates the aircraft is ready to be handed off to GND frequency. The square is clickable.
| Colour | Meaning |
|---|---|
| Green | Just vacated — transfer is due |
| Yellow | ~25 s elapsed — transfer is overdue |
| Red | ~35 s elapsed — transfer is significantly overdue |
FlowX draws seven custom GDI windows on the radar screen. All windows are draggable by their title bar and persist their position between sessions via settings.json in the plugin directory. Most windows can be popped out into their own native Win32 window via the ^ button in the title bar.
A vertical time bar showing all tracked inbound aircraft for up to two runway groups (left / right), ordered by estimated time to touchdown. Each runway's aircraft appear on the side configured via estimateBarSide in config.json. Aircraft labels are coloured by inbound-list colour when Appr Est Colors is enabled, or always green otherwise. Go-around aircraft are shown with a red background; TTT-frozen aircraft with yellow. The window is resizable by dragging its lower-right corner.
Shows the hourly departure count and 15-minute average spacing per runway. Rebuilds every second. No interaction.
A modal overlay that appears at the configured local time (see napReminder in config) to alert controllers of the start of noise abatement procedures. nap.wav plays on appearance and stops on acknowledgement. Draggable; dismissed via the ACK button.
Tracks all departing aircraft. Rows are sorted by a composite key (holding point position, then ground state, then callsign). Aircraft that have departed and are no longer being tracked are shown dimmed at reduced size.
| Column | Source | Description | Left click | Right click |
|---|---|---|---|---|
| C/S | EuroScope | Callsign | — | — |
| STS | FlowX | Ground state (ONFREQ / START-UP / TAXI / LINE UP / TAKE OFF / --DEP--) |
Line Up | Take Off |
| DEP? | FlowX | Departure readiness vs. previous departure: time (s) or distance (nm), colour-coded green/yellow/red | — | — |
| RWY | FlowX | Assigned departure runway | EuroScope runway selector | — |
| SID | FlowX | SID name colour-coded by group | EuroScope SID selector | — |
| WTC | EuroScope | Wake turbulence category | — | — |
| ATYP | TopSky | Aircraft type | — | — |
| Freq | FlowX | Next handoff frequency; turquoise above transfer altitude, blinking orange at warning threshold, !MODE-C if airborne without Mode-C |
Transfer Next | — |
| HP | FlowX | Holding point; orange with * if readback pending, grey after departure |
Assign HP | Request HP |
| # | FlowX | Departure queue position (1-based); empty if not yet queued | — | — |
| Spacing | FlowX | Takeoff spacing snapshot — time (lighter follows heavier) or distance (equal/heavier follows lighter), colour-coded green/yellow/red | — | — |
| T+ | FlowX | Elapsed time since takeoff roll start (M:SS); empty while on the ground |
— | — |
| dNM | FlowX | Live distance to the previous departure (XX.X nm), updated on every position report; colour-coded green/yellow/red using the same distance thresholds as Spacing; --- if no previous departure tracked |
— | — |
90s /120 ← Spacing: time (s) / required (s) — lighter follows heavier
4.2nm/3 ← Spacing: distance (nm) / required — equal or heavier follows lighter
--- ← no preceding departure recorded
12.3 nm ← dNM: live current distance to previous departure
Required distance is 3 nm by default, 5 nm when both aircraft share a SID group. Required time is derived from the holding point configuration (default 120 s).
Tracks aircraft on approach, ordered by time to touchdown per runway. Aircraft furthest from the threshold are dimmed. Rows are grouped by runway with a blank separator between groups.
| Column | Source | Description | Left click | Right click |
|---|---|---|---|---|
| TTT | FlowX | Time to touchdown prefixed with runway (e.g. 29_03:42); green >2 min, yellow >1 min, red <1 min. Go-arounds show the go-around frequency blinking red/yellow. |
Start tracking | — |
| C/S | EuroScope | Callsign | Cleared to land | Missed approach |
| NM | FlowX | Leading inbound: absolute distance to threshold. Following inbounds: gap to aircraft ahead (+2.3), green >3 nm, yellow >2.5 nm, red <2.5 nm |
— | — |
| SPD | EuroScope | Ground speed | — | — |
| WTC | EuroScope | Wake turbulence category | — | — |
| ATYP | TopSky | Aircraft type | — | — |
| Gate | Ground Radar | Assigned stand | Open stand menu | Auto stand assignment |
| Vacate | FlowX | Suggested vacate point based on assigned stand and gap to trailing inbound | — | — |
| RWY | FlowX | Assigned arrival runway; blinks red/yellow if tracked on a different runway than assigned | EuroScope runway selector | — |
Shows wind, QNH, ATIS letter, and RVR (when present) for every airport in config.json. Wind/QNH/RVR are parsed from METAR; the ATIS letter is polled from the VATSIM v3 data feed every 60 seconds. Clicking an airport row acknowledges any pending QNH change.
| Column | Description |
|---|---|
| Wind | Direction/speed (dddKKkt); colour reflects conditions |
| QNH | Current QNH in hPa; turns orange when changed and unacknowledged |
| ATIS | Current ATIS letter; greyed out when no ATIS is online |
| RVR | RVR reading(s) from METAR, shown as a second line when present |
Prototype — DIFLIS is under active development and not yet fully implemented. Layout and functionality may change.
A popout-capable electronic flight strip board, toggled from Start → Windows → DIFLIS. Layout is fully data-driven from the "diflis" block in config.json: column widths, group definitions (title, column index, height weight, sort mode, collapse behaviour), and strip colour palettes are all configurable per airport.
Strips are automatically placed into groups based on EuroScope ground state, clearance flag, and airborne status. Controllers can drag strips between groups to override placement (e.g. moving a strip to a "Standing By" or "Storage" group that has no EuroScope counterpart). The strip cache is rebuilt every tick so state changes are reflected immediately.
Each strip shows callsign, aircraft type, stand, runway, SID/STAR, squawk, and status. Strips can render in collapsed (single-row) or expanded (two-row) variants depending on group configuration and available space.
A dedicated settings window opened from Start → Settings. Organises all plugin toggles into four groups: Assists (Auto-Restore, Auto PARK, Auto-Clear Scratch, HP auto-scratch), Notifications (sound toggles for Airborne, GND Transfer, Ready T/O, No Route, Taxi Conflict), Options (Debug mode, Update check, Flash messages, Appr Est Colors, Fonts, BG opacity), and Taxi (Update/Clear TAXI info, Show TAXI network/labels/graph/routes, Log TAXI tests). Can be popped out into its own window.
Click the FlowX button in the bottom-right corner of the radar screen to open the start menu. It has two sections:
Commands — one-shot actions:
| Item | Description |
|---|---|
| Redo CLR flags | Toggles all existing clearance flags off then back on |
| Dismiss QNH | Bulk-clears all pending QNH change markers |
| Save positions | Saves current window positions to settings.json |
Windows — expandable submenu to toggle visibility of each custom window (Approach Estimate, DEP/H, TWR Outbound, TWR Inbound, WX/ATIS, DIFLIS).
Settings... — opens the Settings window where all plugin toggles, sound notifications, taxi overlays, and font/opacity controls are configured.
Assists are automation helpers that reduce repetitive actions. They are toggled in the Assists group of the Settings window.
| Setting | Default | Description |
|---|---|---|
| Auto-Restore FPLN | On | When a pilot disconnects, FlowX captures a snapshot of their clearance flag and ground state. If they reconnect within 90 seconds with a matching flight plan, those values are automatically restored — useful for brief network drops during pushback or taxi. |
| Auto PARK | On | Automatically sets an arriving aircraft's ground state to PARK once it has stopped at its assigned stand. Removes the need to manually update the strip for parked inbounds. |
| Auto-Clear Scratch | On | Clears the scratchpad automatically when the LINEUP or DEPA button is clicked, provided the current scratchpad content does not start with any of the scratchpadClearExclusions prefixes defined in config.json. Keeps strips clean after departure without manual intervention. |
| HP auto-scratch | On | TWR only. Watches for scratchpad entries starting with . and interprets them as holding-point shortcuts. .NAME assigns the HP and appends a trailing dot (.NAME.) to mark it as processed and prevent re-triggering; .NAME? registers an HP request and leaves the pad unchanged. When TWR later assigns the HP via the popup and it matches the requested name, the scratchpad is automatically set to .NAME ok so GND sees the approval. If a different HP is assigned instead, the stale request is cleared from the pad. |
Notification sounds alert the controller to events that may need immediate attention. Each is toggled individually in the Notifications group of the Settings window. All sounds require the controller to be logged in at TWR facility or above. Sound files must be placed alongside FlowX.dll.
| Setting | Default | Description |
|---|---|---|
| Airborne | On | Plays once when a departure is detected airborne. |
| GND Transfer | On | Plays when a tracked inbound aircraft's ground speed drops below 50 kt after landing, indicating it is about to vacate or has vacated the runway and is ready to be transferred to GND. |
| Ready T/O | On | Plays when a lined-up aircraft that was previously held (departure info was not OK while in LINEUP) becomes clear for takeoff and remains OK for 5 seconds. Suppressed if the aircraft was already clear when LINEUP was given. |
| No Route | On | Plays when the taxi router fails to find a valid route during taxi planning. |
| Taxi Conflict | On | Plays once per conflict pair when the predicted time to intersection drops below 15 s and has persisted for at least 2 seconds. See Conflict detection. |
All commands are entered in any EuroScope chat channel, prefixed with .flowx.
Running .flowx alone prints the loaded version and a list of available commands.
| Command | Description |
|---|---|
.flowx debugstats |
Print internal performance counters (position updates, tag item calls, timer ticks, stand launches/skips) to the Messages window |
Note: Most toggles that were previously chat commands (debug, update, flash, autorestore, etc.) have moved to the Settings window.
config.json lives alongside the DLL and is loaded at startup. It is a JSON object keyed by ICAO airport code. Multiple airports can be defined.
{
"LOWW": { ... },
"LOWI": { ... }
}| Field | Type | Description |
|---|---|---|
fieldElevation |
integer | Airport elevation in feet, used for airborne detection |
airborneTransfer |
integer | Altitude (ft) at which the TWR Next Freq tag turns turquoise |
airborneTransferWarning |
integer | Altitude (ft) at which it starts blinking orange |
gndFreq |
string | Default ground frequency |
defaultAppFreq |
string | Default approach frequency used when no SID-specific match exists |
ctrStations |
array of strings | Centre primary frequencies in priority order. The first frequency that has at least one online centre station wins. |
osmCenterLat |
number | Centre latitude (decimal degrees) for the Overpass API bounding circle used to fetch taxiway data |
osmCenterLon |
number | Centre longitude (decimal degrees) for the Overpass API bounding circle |
osmRadiusM |
integer | Radius in metres for the Overpass API bounding circle (default 6500) |
"fieldElevation": 600,
"airborneTransfer": 1500,
"airborneTransferWarning": 3000,
"gndFreq": "121.6",
"defaultAppFreq": "134.675",
"ctrStations": ["129.200", "134.440", "134.350", "132.600"],
"osmCenterLat": 48.1103,
"osmCenterLon": 16.5697,
"osmRadiusM": 6500Geographic ground frequency zones. When an aircraft's position falls inside a zone's polygon, that zone's frequency is used instead of gndFreq.
"geoGndFreq": {
"west": {
"freq": "121.775",
"lat": [48.123917, 48.113056, 48.117222, 48.129167],
"lon": [16.533667, 16.567444, 16.570472, 16.536750]
}
}Named polygons that identify taxi-out aprons. Aircraft inside these polygons receive ST-UP instead of ONFREQ when the ONFREQ/ST-UP function is triggered.
"taxiOutStands": {
"bstands": {
"lat": [48.120075, 48.119410, 48.121754, 48.122101],
"lon": [16.552188, 16.554506, 16.556262, 16.553642]
}
}Named polygons that always force taxi planning mode, regardless of stand assignment or clearance state. Use for remote aprons or cargo areas that never require a push-back tug.
Same polygon format as taxiOutStands.
"taxiOnlyZones": {
"GAC": {
"lat": [48.124791, 48.126092, 48.129959, 48.128719],
"lon": [16.537689, 16.533779, 16.536438, 16.540431]
}
}All aeroway=taxiway and aeroway=taxilane ways from OSM are included in the taxi graph automatically. The taxiIntersections array reclassifies specific ways as intersection type, which affects routing penalties and graph behaviour. Entries ending with * are treated as prefix patterns.
| Field | Type | Description |
|---|---|---|
taxiIntersections |
array of strings | Intersection/exit refs or prefix patterns to reclassify (e.g. "Exit *", "Exit 12") |
"taxiIntersections": ["Exit *"]Maps taxiway or taxilane refs to a maximum wingspan in metres. Aircraft wider than the limit are hard-blocked — the router never uses that element for them.
"taxiWingspanMax": {
"P": 36.0,
"TL 40 \"Blue Line\"": 36.0
}Maps taxiway or taxilane refs to a maximum wingspan in metres. Aircraft at or below the limit receive a soft cost penalty on that ref, steering the router toward a parallel narrower lane when one is available. Unlike taxiWingspanMax this is not a hard block — the router can still use the ref if no alternative exists (e.g. an initial shared segment).
The penalty multiplier is set via taxiNetworkConfig.edgeCosts.multWingspanAvoid (default 3.0).
"taxiWingspanAvoid": {
"TL 40": 36.0
}This example steers aircraft with wingspan ≤ 36 m away from the centre TL40 lane toward TL 40 "Blue Line" / TL 40 "Orange Line". Wide-body aircraft (> 36 m) are already hard-excluded from Blue/Orange Line by taxiWingspanMax and continue to use the centre lane normally.
Pairs of taxilane refs that are physically the same strip painted with two direction-of-travel markings. The taxi router treats them as freely interchangeable — an aircraft assigned to either lane may use the other without penalty.
"taxiLaneSwingoverPairs": [
["TL 40 \"Blue Line\"", "TL 40 \"Orange Line\""]
]Taxiway direction rules that are always active, regardless of which runways are in use. Each rule specifies a preferred direction of travel on a named taxiway. The router applies a cost penalty to edges that go against the grain.
Each rule may optionally include an againstFlowMult override that replaces the global taxiNetworkConfig.flowRules.againstFlowMult for that taxiway only.
"taxiFlowGeneric": [
{ "taxiway": "P", "direction": "N" },
{ "taxiway": "Q", "direction": "S", "againstFlowMult": 5.0 }
]Per-runway-configuration taxiway direction rules. The map key identifies the active runway configuration as "<dep>_<arr>", where multiple runways on one side are joined with /. Rules in the matching entry are applied on top of taxiFlowGeneric.
Key examples:
| Config | Key |
|---|---|
| DEP 29, ARR 29 | "29_29" |
| DEP 16, ARR 11 | "16_11" |
| DEP 29 + 16, ARR 16 (split dep) | "29/16_16" |
| DEP 16, ARR 11 + 16 (sim landings) | "16_11/16" |
"taxiFlowConfigs": {
"29_29": [
{ "taxiway": "M", "direction": "E" },
{ "taxiway": "L", "direction": "W" },
{ "taxiway": "W", "direction": "S" }
],
"16_11": [],
"29/16_16": []
}Entries with an empty array are valid and simply apply no additional rules beyond taxiFlowGeneric. Configurations not present in the map inherit only taxiFlowGeneric.
Configures a once-per-session modal alert at a specific local time (e.g. to remind controllers of the start of noise abatement procedures).
"napReminder": {
"enabled": true,
"hour": 20,
"minute": 30,
"tzone": "Europe/Vienna"
}Maps approach frequencies to the list of SIDs that should be handed off to them. When a departing aircraft's SID matches an entry, that frequency is used for the handoff instead of defaultAppFreq.
"sidAppFreqs": {
"125.175": ["BUWUT2A", "LANUX4A", "LEDVA4A"],
"129.050": ["ARSIN2A", "LUGEM2A", "MEDIX2A"]
}Maps each target approach frequency to a priority-ordered list of approach frequencies to try. When transferring a departure or displaying the next frequency, the plugin uses this list to find the best available station: for each frequency in the list it finds all online approach stations on that frequency, sorts their callsigns alphabetically, and picks the first. If no approach station is found across all fallbacks, centre stations are tried via ctrStations.
The target frequency itself should always be listed first.
"appFreqFallbacks": {
"134.675": ["134.675", "129.050", "118.775", "125.175"],
"125.175": ["125.175", "129.050", "118.775", "134.675"]
}Night-time SIDs are filed with a truncated name (last character dropped), so IRGOT2A appears in the flight plan as IRGO2A. This map restores the full name for display: the key is the truncated SID prefix as filed, and the value is the full SID name prefix. The tag appends * to mark the SID as a night procedure.
"nightTimeSids": {
"IRGO": "IRGOT",
"IMVO": "IMVOB"
}For example, a filed SID of IRGO2A is displayed as IRGOT2A*.
A list of scratchpad prefixes that are exempt from the Auto-Clear Scratch feature. Comparison is case-insensitive. If the aircraft's scratchpad starts with any listed prefix the auto-clear is skipped.
"scratchpadClearExclusions": [".cs", ".new"]Maps stand names to a routing override target. When an inbound aircraft is assigned one of these stands, the taxi router terminates at the override target instead of routing to the stand's own approach point. Two target types are supported:
| Type | Description |
|---|---|
"hp" |
Target is a holding-point label (resolved via HoldingPointByLabel). Use for uncontrolled aprons where the tower hands off to a marshaller at a defined point. The label must match the ref tag of the OSM aeroway=holding_position node exactly. |
"stand" |
Target is another stand name (resolved via StandApproachPoint). Use when two stands share a physical position and routing should use the co-located stand's approach point (e.g. G16 → F16). |
"standRoutingTargets": {
"GAC": { "type": "hp", "target": "P1" },
"G16": { "type": "stand", "target": "F16" }
}If the target cannot be resolved (HP label not found in the OSM graph, or stand not in Ground Radar data), routing falls back to the original stand's approach point.
Targeted routing overrides for destinations whose real-world standard routing can't be reproduced by the global edge weights, flow rules, and turn penalties alone. Rules are grouped per runway configuration (same normalized "<dep>_<arr>" keying as taxiFlowConfigs) and evaluated at route planning time: the destination name — a stand designator for inbounds, a holding-point label for outbounds — is matched against each rule's destination regex, and the first rule whose destination matches and whose optional origin filters are satisfied wins. The router then produces a route whose ordered wayref list contains the rule's mustInclude sequence as an in-order subsequence (other wayrefs may appear between the required entries). If no such route is feasible from the aircraft's current position, routing falls back to the unconstrained result and a [PREF] line is logged to the debug log — rules degrade gracefully rather than failing hard.
| Field | Type | Description |
|---|---|---|
destination |
string (ECMAScript regex) | Matched as a full match against the bare destination name (no ICAO: prefix, no HP: prefix). Stands and holding points share the same namespace; disambiguate with the pattern itself. |
origin |
string (ECMAScript regex, optional) | Allow-list on the aircraft's starting wayref. When set, the rule only fires if the origin wayref is known (non-empty) and matches this pattern. Origin wayref resolves by stand-polygon containment first (the aircraft's parked stand name) and falls back to the nearest graph node's wayref (the taxiway it's currently rolling on). |
originExclude |
string (ECMAScript regex, optional) | Deny-list on the starting wayref. When set, the rule is skipped if the origin wayref is known and matches this pattern. An unknown origin bypasses the deny-list — rules scoped purely by exclusion still fire when the aircraft's starting wayref can't be resolved. |
mustInclude |
array of strings | Ordered wayref sequence (same strings that appear in TaxiRoute::wayRefs debug output, e.g. "W", "Exit 22", "TL 40 \"Blue Line\""). The sequence must appear in order in the chosen route; unrelated wayrefs may appear between consecutive required entries. |
Regex ranges with parity are expressed with character classes — no custom syntax is needed. For example, to match every even stand in F04–F36:
F(0[2468]|[123][02468])
Stand-range origin filters work the same way — "E4[1-7]" matches an aircraft parked on any of E41 through E47, and "TL 4[0-9]" excludes any aircraft currently rolling on TL 40–49.
Example:
"preferredRoutes": {
"16_11": [
{
"destination": "B1",
"originExclude": "TL 4[0-9]",
"mustInclude": ["W", "Exit 21", "Exit 31"]
},
{
"destination": "F(0[2468]|[123][02468])",
"origin": "Exit 2[0-9]|W",
"mustInclude": ["W", "Exit 22", "Exit 32"]
}
],
"29_34": [
{ "destination": "A1", "mustInclude": ["M", "Exit 2", "L", "W"] }
]
}Because rules are hard constraints with graceful fallback, keep the list small and reserve it for cases where global tuning can't converge. The first-match-wins order means more specific rules should come before more general ones, and origin filters should be used to scope rules away from aircraft that don't need them.
Optional fine-tuning of the taxi graph builder, A* router, interactive snapping, and safety monitor. Every sub-section and every field is optional — omitting any of them leaves the corresponding parameter at its default. All defaults match the values previously hardcoded in the plugin, so existing airports need no changes to config.json.
| Field | Type | Default | Description |
|---|---|---|---|
subdivisionIntervalM |
number | 15.0 |
Long OSM way segments are subdivided into waypoint nodes at this interval (metres). |
osmHoldingPositionSnapM |
number | 25.0 |
Maximum radius (m) for projecting an OSM stop-bar node onto the nearest taxiway edge via edge splitting. The projected point becomes a HoldingPoint node at sub-metre accuracy on the taxiway centreline. |
Applied at graph-build time to all edges of the corresponding aeroway type. Higher values make the router prefer other paths.
| Field | Type | Default | Description |
|---|---|---|---|
multIntersection |
number | 1.1 |
Slight penalty for taxiway-intersection edges. |
multTaxilane |
number | 3.0 |
Stand-access taxilane edges are strongly discouraged vs main taxiways. |
multRunway |
number | 20.0 |
Runway edges are only traversed to vacate the runway; never preferred for taxi. |
multRunwayApproach |
number | 18.0 |
Additional multiplier for edges arriving at a holding-point node (approaching the runway threshold). Slightly below multRunway so vacating via the HP is still preferred over remaining on the runway. |
multWingspanAvoid |
number | 3.0 |
Cost multiplier applied to taxiWingspanAvoid refs when the aircraft wingspan fits the avoid threshold. Higher values produce a stronger preference for the parallel narrower lane. |
Controls how heavily active taxiway flow rules penalise against-flow routing.
| Field | Type | Default | Description |
|---|---|---|---|
withFlowMaxDeg |
number | 45.0 |
Bearing difference (°) at or below which an edge is considered to follow the active flow rule. |
withFlowMult |
number | 0.9 |
Cost multiplier applied to edges that follow the active flow direction (< 1.0 gives a slight preference over uncontrolled taxiways). |
againstFlowMinDeg |
number | 135.0 |
Bearing difference (°) at or above which an edge is considered against the flow rule. |
againstFlowMult |
number | 3.0 |
Additional cost multiplier applied to edges that go against an active flow rule. |
| Field | Type | Default | Description |
|---|---|---|---|
hardTurnDeg |
number | 50.0 |
Bearing change (°) above which an edge is hard-blocked during A* within the same taxiway or between two non-intersection taxiways (prevents kinks and forces use of smooth intersection curves). |
wayrefChangePenalty |
number | 200.0 |
Cost added when the route transitions from one named taxiway to another. |
forwardSnapM |
number | 120.0 |
Radius (m) used to collect up to 3 forward start-node candidates for A*. |
backwardSnapM |
number | 300.0 |
Radius (m) used to collect up to 2 backward start-node candidates for A*. |
heuristicWeight |
number | 1.0 |
Weight applied to the A* heuristic. Values above 1.0 are more goal-directed but may expand nodes sub-optimally; 1.0 is correct for small graphs. |
maxNodeExpansions |
integer | 5000 |
Maximum number of nodes A* expands before giving up. Higher values find better routes at greater CPU cost. |
softTurnCostPerDeg |
number | 0.0 |
Cost added per degree of bearing change at each edge transition; 0 disables. Penalises winding routes and favours straighter paths. |
Snap radii when the controller clicks to set a waypoint. Higher-priority types are checked first; the first match within the radius wins.
| Field | Type | Default | Priority | Description |
|---|---|---|---|---|
holdingPointM |
number | 45.0 |
1 (highest) | Snap to holding-point nodes. |
intersectionM |
number | 15.0 |
2 | Snap to intersection waypoint nodes (labelled "Exit …"). |
suggestedRouteM |
number | 20.0 |
3 | Snap to the suggested route polyline. |
waypointM |
number | 40.0 |
4 | Snap to any graph waypoint node. |
goalSnapM |
number | 170.0 |
— | Snap radius (m) for searching goal-node candidates near the destination stand; must cover the longest taxilane between a stand centroid and the nearest graph node. |
Six-tier search used when routing to a stand approach point. Tiers are tried in order (narrow, near) → (narrow, far) → (medium, near) → (medium, far) → (wide, near) → (wide, far) and the first non-empty tier wins. Cones widen so closely aligned nodes are preferred; within each cone the near radius is tried before the far radius so the closest taxilane behind the stand beats a far-but-aligned one.
| Field | Type | Default | Description |
|---|---|---|---|
narrowConeDeg |
number | 10.0 |
Tight cone (°) around the stand approach bearing; catches nodes closely aligned with the stand heading. |
mediumConeDeg |
number | 20.0 |
Medium cone (°) used when no node is found inside narrowConeDeg. |
wideConeDeg |
number | 90.0 |
Wide cone (°) used as a last resort; prevents picking nodes behind the stand (> 90° off the approach axis). |
nearRadiusM |
number | 80.0 |
Near radius (m) tried inside each cone before farRadiusM; covers taxilanes running immediately behind straight stands. |
farRadiusM |
number | 170.0 |
Far radius (m) tried inside each cone after nearRadiusM; covers diagonal stands and edge cases where the nearest taxilane is further out. |
| Field | Type | Default | Description |
|---|---|---|---|
deviationThreshM |
number | 40.0 |
Distance (m) an aircraft may deviate from its assigned route before a deviation warning is raised. |
endpointDeviationThreshM |
number | 80.0 |
Relaxed deviation threshold (m) used while the aircraft is within endpointRadiusM of the route's first or last node — accounts for stands and holding points sitting slightly off the graph. |
endpointRadiusM |
number | 60.0 |
Radius (m) around the first/last route node within which endpointDeviationThreshM replaces deviationThreshM. |
minSpeedKt |
number | 3.0 |
Minimum ground speed (kt) required before safety checks are evaluated. |
maxPredictS |
number | 60.0 |
Maximum prediction horizon (s) used when building conflict-detection paths. |
conflictDeltaS |
number | 30.0 |
Two aircraft at the same intersection are flagged as conflicting if their estimated arrival times differ by less than this value (s). |
sameDirDeg |
number | 45.0 |
Bearing difference (°) below which two converging paths are considered same-direction and excluded from conflict alerts. |
"taxiNetworkConfig": {
"graph": { "subdivisionIntervalM": 15.0, "osmHoldingPositionSnapM": 25.0 },
"edgeCosts": { "multIntersection": 1.1, "multTaxilane": 3.0, "multRunway": 20.0, "multRunwayApproach": 18.0, "multWingspanAvoid": 3.0 },
"flowRules": { "withFlowMaxDeg": 45.0, "withFlowMult": 0.9, "againstFlowMinDeg": 135.0, "againstFlowMult": 3.0 },
"routing": { "hardTurnDeg": 50.0, "wayrefChangePenalty": 200.0, "forwardSnapM": 120.0, "backwardSnapM": 300.0, "heuristicWeight": 1.0, "maxNodeExpansions": 5000, "softTurnCostPerDeg": 0.0 },
"snapping": { "holdingPointM": 45.0, "intersectionM": 15.0, "suggestedRouteM": 20.0, "waypointM": 40.0, "goalSnapM": 170.0 },
"targetSelection": { "narrowConeDeg": 10.0, "mediumConeDeg": 20.0, "wideConeDeg": 90.0, "nearRadiusM": 80.0, "farRadiusM": 170.0 },
"safety": { "deviationThreshM": 40.0, "endpointDeviationThreshM": 80.0, "endpointRadiusM": 60.0, "minSpeedKt": 3.0, "maxPredictS": 60.0, "conflictDeltaS": 30.0, "sameDirDeg": 45.0 }
}Two overlays can be enabled from the Taxi group of the Settings window to help diagnose routing issues without leaving EuroScope.
Draws the raw OSM geometry loaded from the taxiway cache, colour-coded by way type:
- Yellow — taxiway
- Cyan — taxilane
- Red — holding-point way
- Green — intersection way
- Orange — runway (OSM ways and derived centrelines, drawn bold)
Holding-position circles are drawn at each OSM holding position. Enable Show TAXI labels alongside this overlay to render taxiway name labels at each way's midpoint — useful for identifying which wayRef strings to use in flow rules and mustInclude sequences.
Use this overlay to verify that the OSM data was fetched and parsed correctly, and to check that holding positions and taxiway types are being interpreted as expected.
Draws the internal A* routing graph built from the OSM data, showing every node and directed edge. Edges are colour-coded by their cost multiplier (cost ÷ distance):
- White — neutral taxiway (no active flow rule)
- Green — taxiway with an active flow rule for the current runway configuration
- Yellow — taxilane (≥ 2× cost)
- Orange (bold) — holding-point connector (≥ 5× cost)
- Red — runway edge (≥ 30× cost)
Nodes are drawn as small coloured squares:
- Cyan — regular waypoint node
- Red — holding-point node
Use this overlay to diagnose unexpected routes: a taxiway that should be one-directional appears white instead of green if no flow rule is matching, and a missing edge indicates the OSM data has a gap or the subdivision interval needs adjusting.
Per-runway configuration keyed by runway designator.
| Field | Type | Description |
|---|---|---|
opposite |
string | Reciprocal runway designator (used for go-around detection) |
twrFreq |
string | Tower frequency for this runway |
goAroundFreq |
string | Go-around (approach) frequency for this runway |
width |
int | Runway width in metres (e.g. 45) |
threshold |
object | { "lat": ..., "lon": ... } — runway threshold coordinates |
thresholdElevationFt |
int | Threshold elevation in feet; overrides fieldElevation for TTT altitude gates. Omit or set to 0 to use fieldElevation. |
estimateBarSide |
string | Which side of the Approach Estimate bar this runway's aircraft appear on: "left" or "right". Omit to exclude from the bar. |
sidGroups |
object | Group number → array of SID prefixes. Aircraft in the same group get 5 nm spacing instead of 3 nm. |
sidColors |
object | Colour name → array of SID prefixes. Valid colours: green, orange, turq, purple, red, white, yellow. SIDs omitted here default to white. |
holdingPoints |
object | Named holding point definitions (see below) |
vacatePoints |
object | Named vacate point definitions (see below) |
gpsApproachPaths |
array | Non-straight-in RNP approach paths for early TTT detection. Each entry defines a sequence of fixes with distance/altitude gates. Omit for straight-in approaches. |
"runways": {
"11": {
"opposite": "29",
"twrFreq": "119.4",
"goAroundFreq": "125.175",
"threshold": { "lat": 48.122766, "lon": 16.533610 },
"sidGroups": {
"1": ["LANUX", "BUWUT", "LEDVA"],
"2": ["OSPEN", "RUPET"]
},
"sidColors": {
"green": ["LANUX", "BUWUT", "LEDVA"],
"orange": ["OSPEN", "RUPET"]
},
...
}
}Each holding point entry defines the polygon that detects when an aircraft is at that point.
| Field | Type | Description |
|---|---|---|
assignable |
boolean | If true, the point appears in the HP popup list |
sameAs |
string | Name of another point considered physically equivalent for spacing calculations |
polygon |
object | { "lat": [...], "lon": [...] } — detection polygon vertices |
"holdingPoints": {
"A12": {
"assignable": true,
"sameAs": "A11",
"polygon": {
"lat": [48.1231, 48.1228, 48.1239, 48.1241],
"lon": [16.5335, 16.5344, 16.5347, 16.5342]
}
},
"A9": {
"assignable": true,
"polygon": { ... }
}
}Recommended runway exit points for arriving aircraft based on their assigned stand and the gap to the following (trailing) inbound.
| Field | Type | Description |
|---|---|---|
minGap |
number | Minimum gap in NM to the following (trailing) inbound required before this vacate is suggested |
stands |
array of strings | Stand names (or glob-style patterns with *) associated with this vacate |
"suggestedVacatePoints": {
"A3": {
"minGap": 5,
"stands": ["F04", "F08", "H*", "K*"]
},
"A4": {
"minGap": 3,
"stands": ["E*", "F*"]
}
}Allowed runway vacation exits with optional WTC restrictions. Unlisted HP refs are excluded from inbound taxi routing. Used by the taxi router to constrain which exits an arriving aircraft may vacate via.
| Field | Type | Description |
|---|---|---|
excludeWtc |
array of strings | WTC categories that cannot use this exit (e.g. ["H", "J"]) |
excludeRef |
object | Per-WTC map of refs excluded when vacating via this exit (e.g. {"H": ["D"]}) |
"vacatePoints": {
"A3": {},
"A4": { "excludeWtc": ["H", "J"] },
"A5": { "excludeRef": { "H": ["D"] } }
}FlowX includes a companion FlowXTests project — a standalone Win32 console executable that runs unit tests against the core plugin logic without requiring a live EuroScope installation.
doctest v2.5.1 (single-header, bundled at include/doctest/doctest.h).
Open FlowX.sln in Visual Studio 2022 and build the FlowXTests project (Debug|Win32 or Release|Win32). The output is Debug\FlowXTests.exe or Release\FlowXTests.exe in the solution root.
A post-build event copies the required fixture files into the output directory automatically:
| Fixture | Destination | Purpose |
|---|---|---|
config.json (solution root) |
$(OutDir) |
Real LOWW airport configuration |
FlowXTests/fixtures/settings.json |
$(OutDir) |
Test settings with updateCheck: false |
FlowXTests/fixtures/osm_taxiways_LOWW.json |
$(OutDir) |
Snapshot of the OSM taxiway cache |
FlowXTests/fixtures/Groundradar/ICAO_Aircraft.json |
$(SolutionDir)Groundradar\ |
Aircraft wingspan data |
FlowXTests/fixtures/Groundradar/GRpluginStands.txt |
$(SolutionDir)Groundradar\ |
Stand polygon data |
FlowXTests/fixtures/taxi-test.json |
$(OutDir) |
Fixture-driven taxi route test cases |
Release\FlowXTests.exe
A non-zero exit code means one or more tests failed. Pass --help for doctest options (e.g. --tc=*OSM* to filter by name).
| File | Real source compiled | Coverage |
|---|---|---|
test_geometry.cpp |
taxi_graph.h |
Haversine distance, bearing, bearing-diff, point-to-segment distance, segment intersection |
test_graph.cpp |
taxi_graph.cpp |
Graph build from synthetic OSM data, A* routing, flow-rule enforcement, wingspan hard-exclusion and soft-avoidance, edge splitting, preferred-route matching, goal selection, departure HP resolution |
test_taxi_routes.cpp |
taxi_graph.cpp |
Fixture-driven regression tests against the real LOWW TaxiGraph using taxi-test.json — verifies route validity, mustInclude/mustNotInclude wayref constraints, and distance ranges |
test_osm.cpp |
osm_taxiways.cpp |
OSM JSON parsing (taxiways, taxilanes, runways, holding positions, stands) |
test_lookups.cpp |
CFlowX_LookupsTools.cpp |
Point-in-polygon, colour parsing, holding-point annotation encoding, weight category ranking |
test_helpers.cpp |
(helpers are inline/header-only) | String split/join, trim, upper-case, frequency annotation formatting |
test_tags.cpp |
CFlowX_Tags.cpp |
Tag text and colour generation (Push+Start helper, Same-SID, ADES, QNH marker) using EuroScope stubs |
test_settings.cpp |
CFlowX_Settings.cpp |
LoadSettings, LoadConfig, LoadAircraftData, LoadGroundRadarStands against real fixture files; OSM cache load and TaxiGraph build from the LOWW snapshot |
FlowXTests/stubs/EuroScope/EuroScopePlugIn.h shadows the real SDK header via include-path ordering. It provides the same types and method signatures as the production header, implemented as plain data-holder structs with no DLL linkage. Tests set member variables directly to simulate flight-plan and radar-target state.
FlowXTests/fixtures/osm_taxiways_LOWW.json is a point-in-time snapshot of the LOWW taxiway data (232 ways, 47 holding positions). It is used to verify that the cache loader and TaxiGraph builder work end-to-end without a network connection. If the OSM data changes significantly and you regenerate the cache from EuroScope, copy the new osm_taxiways_LOWW.json from the plugin directory into FlowXTests/fixtures/ and update the way/holding-position count assertions in test_settings.cpp.
If you have a suggestion or encountered a bug, please open an issue on GitHub. Include a description of the problem, relevant logs, and steps to reproduce.
Pull requests are welcome. Please keep features reasonably generic — the plugin is intended to be configurable via config.json rather than hard-coded for a specific airport or vACC.
Primary development is done in VS Code with the Microsoft C/C++ extension and CMake Tools; Visual Studio 2022 is also fully supported and remains the reference build environment.
- Set the environment variable
EUROSCOPE_ROOTto the EuroScope install directory (not the executable itself) to enable the debugger launch configuration in VS2022 - Avoid breakpoints during live controlling — use
.flowx debuginstead - Target: 32-bit or 64-bit DLL (
Release|Win32orRelease|x64), C++23, Windows SDK 11.0
Dependencies are bundled in include/ and lib/:
| Library | Version | License | Purpose |
|---|---|---|---|
| EuroScope SDK | — | — | Plugin base classes |
| nlohmann/json | v3.9.1 | MIT | JSON config parsing |
| semver | v0.2.2 | MIT | Version comparison for update check |
| date/tz | — | MIT | IANA timezone support for NAP reminder |





















