Debug console¶
The in-game developer console, the performance overlay, and the visual-verification harness.
Player-facing controls live in the User Guide; this page is the developer surface.
Game console¶
Toggle: ` (backtick / grave). Close: Escape.
The console is a half-screen drop-down overlay. It is fully independent of the cockpit HUD and available in any game state. All game inputs (flight controls and camera) are suppressed while it is open; throttle is held at its last value so opening the console does not cut the engines.
Editing¶
| Key | Action |
|---|---|
| Backspace | Delete last character |
| Up arrow | Recall previous command |
| Down arrow | Step forward in history |
| Enter | Submit command |
Commands¶
| Command | Description |
|---|---|
help [command] |
List all commands, or show usage for one |
types |
List all registered entity types with their indices |
entities |
List all live entities (idx/gen, type, world position) |
spawn <type> <x> <y> <z> [--faction <n>] [--ai <behavior> [args...]] |
Spawn entity with an optional faction/team (n = 0–65535; 0 = neutral) and AI controller (see AI behaviors below) |
kill <idx> |
Remove entity from simulation (queued to sim thread) |
tp <x> <y> <z> |
Teleport player entity to world position |
toggle_pos |
Toggle entity world-position readout below the camera position display |
show_ping |
Toggle "Ping: N ms" RTT overlay (visible even when F3 performance overlay is off) |
set_weather <preset> |
Set weather instantly: clear, partly_cloudy, overcast, rain, storm, snow, blizzard. Queued to sim thread; takes effect on next tick. |
atc_status [airport] |
Show ATC facility queues + runway occupancy (#705). Synchronous read of the ATC service; [airport] filters to one field. |
atc_scramble <airport> <type> [count] |
Launch count AI departures from a named airport (#705, default 1). They spawn hold-short, get sequenced onto the runway, and take off in order. Queued to the sim thread. |
atc_hold <airport> <on\|off> |
Freeze or release that airport's departure queue (#705). Queued to the sim thread. |
detonate <x> <y> <z> <radius_m> <damage> [--nuclear] |
AoE warhead at a world position (#356); --nuclear adds the EMP ring (avionics kill) at 4× the blast radius. Forwarded to the server. |
set_difficulty <level> |
(stub — Phase 2b) |
reload_content |
(stub — see issue #152) |
spawn, kill, and set_weather are queued to the sim thread and take effect on the next tick.
Entity indices shown by entities come from the most-recent render snapshot.
AI behaviors (optional --ai flag on spawn):
Every AI behaviour is sensing-gated (#670). A spawned AI engages only what its sensors have actually detected. A bandit behind a forward-looking aircraft is invisible to it; a fresh contact takes a reaction delay to act on (scaled by the server's
[ai] difficultyand the entity's own[ai].reaction); and a target that breaks the lock keeps being flown at its last-known position until the track'slock_hold_scoast runs out.So if a spawned AI seems to be ignoring you, first check whether it can see you. That is now a real question with a real answer, rather than a bug.
| Behavior | Args | Description |
|---|---|---|
loiter |
[cx cy cz] [radius_m] [alt_m] [throttle] [cw\|ccw] |
Orbit a fixed center point; cw = clockwise (default), ccw = counterclockwise |
dynamic_loiter |
<entityIdx> [radius_m] [throttle] [cw\|ccw] |
Orbit a moving entity (#464): re-centers the loiter circle on the target's live position each tick and matches its altitude; returns neutral when the target is dead or invalid |
waypoint |
x1 y1 z1 [x2 y2 z2 ...] [--loop] |
Fly a sequence of 3D waypoints; --loop restarts from the first when complete |
pursuit |
<entityIdx> |
Pursue an entity by pool index; returns neutral when target is dead or invalid |
evade |
<entityIdx> |
Flee a threat entity by inverting the pursuit heading error |
break |
<entityIdx> [rollDuration] |
Defensive ACM: roll toward threat then pull maximum-G (rollDuration in seconds, default 0.5) |
lead |
<entityIdx> [navGain] |
Proportional navigation pursuit; aims at predicted intercept point ahead of target (navGain: 0.0=pure pursuit, 1.0=first-order lead [default]) |
lag |
<entityIdx> [lagFraction] |
Lag pursuit; aims behind the target at target.pos − target.vel × TTC × lagFraction; keeps the attacker inside the target's turn circle without overshooting (lagFraction: 0.0=pure pursuit, 1.0=one TTC-step behind [default]) |
immelmann |
[pullDur] [rollDur] |
Half-loop + roll to reverse heading; pull up to inverted then roll upright (defaults: 4.0 s, 1.5 s) |
split_s |
[rollDur] [pullDur] |
Roll inverted + pull through to reverse heading; opposite energy trade to Immelmann (defaults: 1.5 s, 4.0 s) |
high_yo_yo |
<entityIdx> [climbDur] [reacquireDur] |
Overshoot correction: bank away from target, pull up to bleed speed, then reacquire (defaults: 2.5 s, 3.0 s) |
low_yo_yo |
<entityIdx> [diveDur] [pullDur] |
Dive-and-cut-corner to close on a turning target (defaults: 1.5 s, 2.5 s) |
guns |
<entityIdx> [muzzleVel] [lethalRadius] |
Guns employment (#462): steers onto the ballistic lead point (muzzle velocity, shooter-velocity carry, gravity drop) and fires only when the predicted miss is inside the lethal radius (defaults: 1030 m/s, 8 m) |
ballistic |
<tx> <ty> <tz> [mirvCount [spreadM]] |
Ballistic missile guidance (#355) for type = "ballistic" entities: boost-phase TVC steering to the impact point with a lofted pitch program, inertial after burnout; mirvCount > 0 deploys child RVs past apogee (kills credit whoever launched the bus) |
lua |
<script_name> |
Load a Lua AI script from the content pack's ai/ directory (e.g. patrol, interceptor). See docs/modding/ai.md. |
patrol_attack |
<entityIdx> [engageRangeM] [retreatHp] |
Three-state machine: loiter patrol → lead-pursuit engage when the target is detected within range → evade retreat when HP below threshold (defaults: engageRangeM=8000 m, retreatHp=0.25). Sensing-gated (#690): it engages what it has actually seen and reacted to, not whatever is within the radius |
escort |
<entityIdx> [standoffM] |
Two-state orbit protection: clockwise loiter at standoffM radius around the escorted entity, tracking it as it moves (#464) → Immelmann reversal when a hostile entity enters the inner defense zone (standoffM×0.5). Hostiles are classified by faction, so the escort and escortee should be spawned with the same non-neutral --faction; friendlies and neutrals are ignored. (default: standoffM=2000 m) |
swarm |
<cx> <cy> <cz> [neighborRadiusM] [separationRadiusM] [cruiseThrottle] |
Boids swarm member (#353): separation / alignment / cohesion with same-type, same-faction flockmates found through the spatial index, migrating toward the point. Spawn N identical entities with this behavior and they flock — there is no swarm object, so losing members degrades the flock, never breaks it. (defaults: 600 m, 120 m, 0.75) |
swarm_follow |
<entityIdx> [neighborRadiusM] [separationRadiusM] [cruiseThrottle] |
The same boids member, migrating after a moving anchor entity (e.g. a strike lead the swarm escorts) |
Weather presets:
| Preset | Cloud cover | Fog | Turbulence | Time of day | Precipitation |
|---|---|---|---|---|---|
clear |
0% | None | None | Driven by time clock | None |
partly_cloudy (default) |
35% | None | Light | Driven by time clock | None |
overcast |
75% | Light | Moderate | Driven by time clock | Rain |
rain |
85% | Heavy | Moderate | Driven by time clock | Rain |
storm |
95% | Maximum | Strong | Driven by time clock | Heavy rain |
snow |
85% | Moderate | Moderate | Driven by time clock | Snow (any altitude) |
blizzard |
95% | Heavy | Strong | Driven by time clock | Heavy snow (any altitude) |
When cloudCoverage ≥ 0.75 (overcast, rain, storm, snow, blizzard), precipitation particles emit from a 3×3 grid 60 m above the camera. The precipitation type is server-authoritative: snow/blizzard presets always emit snow particles regardless of altitude; overcast/rain/storm presets always emit rain particles. With no wind, particles fall straight down. Rain uses a 20° cone and 10%/25% wind influence; snow uses an 80° cone and 35%/55% wind influence.
In Cockpit mode (F1), a screen-space windshield overlay is rendered simultaneously: 48 semi-transparent streaks animate on the glass — blue-white diagonal lines for rain/storm, short white smears for snow/blizzard. Streak opacity and length scale with cloudCoverage. Lateral lean is proportional to crosswind speed (windX).
The in-game clock advances at 10× real time by default (1 real minute = 10 game minutes; full day/night cycle ≈ 2.4 real hours). The Cockpit HUD (F1 mode) shows IAS / ALT / AGL on the left column, THR / FUEL on the right column, HDG at the bottom, and HH:MM clock top-right. AGL is computed from the terrain heightmap at the aircraft's XZ position and falls back to the same value as ALT (MSL) when the LOD-0 chunk is not yet loaded. The time scale is configurable via [world] time_scale in server.toml.
Position widget¶
The camera world position (CAM x y z) is always displayed in the top-right corner in all
camera modes. toggle_pos adds a second line showing the player entity position (ENT x y z)
below it; toggle it off with a second toggle_pos.
Performance overlay (F3)¶
Cycles Off → Compact → Full. Full mode includes a 128-position rolling frame-time bar graph using Unicode shade characters (░ ▒ ▓ █ — U+2591/92/93/88). The renderer uses GNU Unifont 8×16 (full Unicode BMP), so these render correctly on all platforms without the CP437 fallback workaround previously used.
Visual verification (tools/visual_check.sh)¶
One command opens a window rendering the builtin:shape-gallery mission — a museum row with one
entity of every category (ground vehicle, naval vessel, structure), floating ordnance exhibits
(missile / bomb / rocket projectile types spawned as plain objects), an armed joinable player
slot, and a live fight 9 km out (fighters + SAM + AAA) so missiles fly. No menu interaction:
tools/visual_check.sh # observer mode: standalone server + ghost camera; wreck-
# staging detonations fire after ~25 s so you watch the
# intact -> wreck swap live (FL_VISUAL_STAGE_DELAY=0 disables)
tools/visual_check.sh --fly # pilot mode: single-player into the armed player slot —
# fire bombs/rockets (stations 4/5), strafe the museum row
tools/visual_check.sh --build # build the debug preset first
In observer mode use Num1/Num2 to cycle entities (labels show type + faction), F2/F1 to
frame the pick in Chase/Cockpit, F4 to return to free-fly. Windows: tools\visual_check.ps1
with -Fly / -Build. Any mission id works via --mission <id>, including a .yaml file path
(fl-server resolves builtin id → file path → pack asset).
The loading screen reports specific connection failures immediately rather than waiting for the 10-second timeout:
| Message | Cause | Returns to |
|---|---|---|
Server binary not found. |
fl-server executable not found at startup |
Main menu after 3 s |
Port already in use. |
fl-server could not bind to the chosen port |
Main menu after 3 s |
Server startup timed out. |
fl-server started but never became ready |
Main menu after 3 s |
Server version mismatch. |
Server sent MsgHello with a different protocolVersion |
Main menu after 3 s |
Connection refused by server. |
Server dropped the ENet connection before accepting the client (ban, allowlist, rate limit) | Main menu after 3 s |
The server denied the requested role. |
Requested --observer but the server has allow_observers = false (#857) |
Main menu after 3 s |
Connection timed out. |
No response from server within 10 s | Main menu after 3 s |
Local server failed to start. |
fl-server process hung and never became ready within 10 s (fallback) |
Main menu after 3 s |