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>
Composable manages phase navigation, interaction goal monitoring with
hold-time validation, progress tracking, and hint system for the
learn-pfd medienstation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>