The frequency panel was always empty when using OpenAIP because of three
compounding bugs in frequencies.get.ts:
1. Wrong query parameter — ?icao=EDDF performs an unfiltered full-text
search and returns all 46 000+ airports paginated; EDDF wasn't even
on the first page. The correct parameter is ?search=EDDF, which
returns exactly the one matching airport.
2. Wrong ICAO field name — the code checked airport.icao but the real
field in the v2 API response is icaoCode. Even on a correctly
filtered response the match would always fail.
3. Wrong frequency field names and numeric types — each frequency item
exposes the MHz value in a value field (not frequency / frequencyMHz),
and the service type is a numeric code (5=Delivery, 9=Ground,
14=Tower, 15=ATIS) rather than a string. Added OPENAIP_TYPE_MAP to
translate these numeric codes to the internal DEL/GND/TWR/ATIS codes
the rest of the pipeline expects.
With these fixes EDDF now returns all 8 frequencies (Delivery 122.035,
Ground 121.805, three Tower variants, two ATIS) which are then stored in
delivery_freq / ground_freq / tower_freq / atis_freq and used for both
the frequency overview panel and the per-state frequency validation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Expected Communication card now driven entirely by backend responses:
controller speech from controller_say_rendered, expected pilot phrase
from expected_pilot_template — both update after every state transition
instead of relying on the static COMMUNICATION_STEPS array
- Initial expected pilot phrase seeded on session creation via the new
expected_pilot_template field in CreateSessionResponse, so the card
shows the correct text before any transmission
- Radio pronunciation toggle (mdi-radio / mdi-text) on the card applies
normalizeRadioPhrase() (ICAO alphabet, wun/too/tree, callsign expansion)
to both ATC speech and expected pilot phrase
- Frequency validation at transmit time: if the pilot's active frequency
does not match the state's expected frequency (resolved from
frequency_name → flight-plan variable) a canned ATC reply is played
and the backend is not called
- Flight-plan variables (callsign, squawk, destination, ATIS, SID, stand,
initial altitude) now passed to createSession() as variable_overrides
so backend sessions use real flight-plan data from the first state
- Backend session variables synced into local vars after each response
so frontend and backend stay consistent
- Removed hardcoded frequency chip from Expected Communication card
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Pass as top-level field in PTT requests so Whisper STT
results are linked to the correct Python backend session
- Add namespaced helper in pm.vue (info/warn/error/debug/group)
controlled by localStorage PM_DEBUG flag; logs transmit/response
cycles, TTS calls, flag/variable syncs, and fallback warnings
- Log backend session creation context (flow, start state, vars, flags)
in startMonitoring
- Fix typo in text input hint: STT fails not PTT fails
and
fix: sync backend variables to frontend after each transmission
The ATC say template was rendered using the frontend engine's local
variable defaults (squawk '1234', hardcoded SID, etc.) instead of
the authoritative values from the Python backend session. This caused
the spoken clearance and the readback prompt to show different squawk
codes.
- After each backend transmission response, sync all response.variables
into vars.value (same pattern already used for flags)
- Prefer controller_say_rendered (pre-rendered by backend) over the raw
template for TTS scheduling, eliminating any remaining dependency on
local variable state for the ATC speech text
Replace the LLM-per-request flow in /pm with a stateful Python backend
(OpenSquawk-LiveATC-api). The backend owns session state, does regex-first
routing with readback evaluation, and returns the next state + ATC speech.
The frontend keeps its local cursor (communicationsEngine) for TTS and
monitoring UI, but no longer calls /api/llm/decide.
Changes:
app/composables/useRadioBackend.ts (new)
Typed Nuxt composable wrapping the Python REST API:
createSession, transmit, deleteSession, fetchFlows.
Base URL read from NUXT_PUBLIC_RADIO_BACKEND_URL (default 127.0.0.1:8000).
nuxt.config.ts
Expose radioBackendUrl as a public runtime config key so the composable
and communicationsEngine can both reach the Python backend.
shared/utils/communicationsEngine.ts
- fetchRuntimeTree now accepts an optional baseUrl so it fetches from the
Python backend instead of the Nuxt server when a URL is provided.
- renderTpl handles both {var} (old MongoDB schema) and {{var}} (new YAML
schema) — double-brace matched first to avoid partial matches.
- stateSayTpl / stateUtteranceTpl helpers unify say_tpl|say_template and
utterance_tpl|expected_pilot_template across both schema versions.
- auto_transitions from the new YAML schema are included when collecting
eligible transitions in collectAtcStatesUntilPilotTurn.
shared/types/decision.ts
RuntimeDecisionState extended with say_template and expected_pilot_template
fields (new YAML schema field names alongside the existing legacy names).
app/pages/pm.vue
- startMonitoring: loads tree from Python backend, then creates a backend
session (backendSessionId). Cursor synced to session.current_state.
- handlePilotTransmission: calls radioBackend.transmit instead of
/api/llm/decide. Applies auto_advanced_states via moveToSilent, then
the final state. Speaks controller_say_template via TTS.
- Both fetchRuntimeTree calls now pass radioBackendUrl so they hit the
Python backend, not the Nuxt flow-from-MongoDB path.
AGENTS.md (new)
Project guide updated to document the new two-backend architecture,
the Python backend session lifecycle, and the dual template schema.
docs/plans/2026-05-06-pm-python-runtime-contract.md (new)
Implementation plan and API contract written before the work started.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Darken all tape backgrounds to match real A320 PFD (#16181f/#1c1e26)
- Speed/altitude readouts green (#19e34a) instead of cyan
- Attitude indicator: sky/ground gradients, W-shaped aircraft symbol
- Speed tape: cyan target zone (not red), VFE/min-speed red bands
- Altitude tape: ticks on left side toward attitude indicator
- Flight physics: 3x slower pitch (smoothed, tau 2s), halved roll rate,
stronger speed-pitch coupling for realistic 150t inertia
- Pitch/bank exercises use normal flight ranges (±10° bank, ±3-4° pitch)
- Multi-phase speed exercise: explain → coarse hold 5s → fine hold 8s
- ATC messages emphasize small inputs, patience, and anticipation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Aircraft model rebuilt with proper Three.js coordinate system:
- Wings along X axis (horizontal), fuselage along Z (forward=-Z), Y=up
- Pitch rotates around X, bank around Z
- Camera positioned for front-left view
Heading indicator fixed to match Airbus PFD style:
- Yellow heading readout box at top with pointer triangle below
- Ticks grow upward from bottom edge
- Labels positioned above ticks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pitch rotates around Y axis (wing axis), bank around X axis
(longitudinal axis). Camera moved to front-right view to clearly
show both pitch and bank movements.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove Y-axis inversion so pulling stick down (toward user) gives
positive pitch (nose up), matching real Airbus sidestick behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Nuxt auto-import prefixes component names based on directory path.
Components in app/components/flightlab/pfd/ are registered as
FlightlabPfd*, not Pfd*. Fixed in learn-pfd.vue and PfdContainer.vue.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- learn-pfd.vue now creates a WebSocket session on mount and displays
the 4-char session code in the header bar for touchscreen connection
- New stick-input.vue page at /flightlab/medienstationen/stick-input
with touch-based sidestick (spring-loaded 2D pad) and throttle
(vertical slider). Joins session by code, sends input at 30Hz.
- Stick input page added to medienstationen index as second card.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root div min-h-screen → h-screen ensures flex-1 children get real
height. Grid children get min-h-[200px] so Three.js and PFD SVG
have measurable container dimensions to render into.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Integrates FBW physics, PFD SVG instruments, 3D aircraft model,
TTS narration, goal evaluation engine, and WebSocket stick input
into a single interactive learning experience. Features dynamic
CSS Grid layout that transitions between model-focus, split, and
pfd-focus modes as the user progresses through phases.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New index page at /flightlab/medienstationen with PFD learning card
and coming-soon placeholder. Adds medienstationen section link to
the main flightlab index page.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>