11 Commits

Author SHA1 Message Date
itsrubberduck
e004843aa9 feat(live-atc): forward aircraft position, un-throttle bridge polling
- NormalizedTelemetry carries lat/lon (0/0 no-GPS guard, position in the
  change-detection signature) so the backend can derive distance triggers
- bridge polling moves from setInterval to a Worker tick: with the sim in the
  foreground the /live-atc tab is backgrounded and Chrome throttles plain
  intervals to ~1/min — telemetry-driven ATC lagged behind the silence fallback

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 20:48:09 +02:00
itsrubberduck
3fe431ea47 fix(live-atc): stop ATC from repeating/looping and hanging on silent frequencies
- Neutralize the local engine's autonomous auto-advance (evaluateAutoTransitions/
  evaluateSimpleAutoFlow) — the Python backend now drives state exclusively via
  moveToSilent; the old walker could self-answer pilot states and race the
  backend, producing loops.
- Dedupe applyBackendDecision's ATC log entry + TTS: moveToSilent no longer logs
  say_tpl for backend-driven auto-advanced states (suppressSay), and a
  lastAppliedSay guard drops a repeated decision from overlapping sources
  (transmit reply, telemetry tick, silence timeout).
- Cap consecutive silence-timeout re-fires on the same state at 2 instead of
  re-arming forever.
- Pause telemetry forwarding while a pilot transmission is in flight, and guard
  against overlapping telemetry POSTs.
- Add request timeouts to TTS (20s) and backend transmit/telemetry/timeout
  (30s)/createSession (60s) calls so a hung request can no longer freeze the
  whole session.

Verified live against the Python backend: a full clearance→taxi chain now logs
each ATC line exactly once instead of 2-3x.
2026-07-16 23:47:52 +02:00
leubeem
e32cee5f87 feat(pm): silence auto-advance timer for backend timeout states
Arm a timer whenever the session lands on a pilot state with
auto_advance_on_silence (from the runtime tree); on expiry call the backend
/timeout endpoint and apply the response through applyBackendDecision. Cleared
on pilot transmission, stale-guarded against session/state changes, and
deferred while PTT is recording so ATC never talks over the pilot.

This makes the tower-v1 airborne handoff work without a sim bridge: telemetry
fires it when connected, the silence timeout covers everyone else.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 17:18:43 +02:00
leubeem
04022ef654 Merge branch 'feat/telemetry-atc-backbone' 2026-07-06 17:07:32 +02:00
leubeem
6b8d2b9eb8 feat(pm): runway from live ATIS + bridge position for real taxi routes
- The VATSIM flight plan carries no runway, so the runway in use is now parsed
  from live ATIS text (DEP/ARR/EXPECT...APPROACH phrasing preferred, generic
  RWY mention as fallback, "7L" normalised to "07L"). Falls back to the
  engine-generated runway when no ATIS text mentions one. Local engine vars
  are patched so the HUD matches what ATC says.
- The bridge poller now tracks PLANE_LATITUDE/LONGITUDE; when the bridge is
  connected, the aircraft position is sent at session creation so the backend
  starts the departure taxi route at the real parking position instead of a
  random stand.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 17:00:21 +02:00
leubeem
25929b9c06 feat(pm): forward normalized sim telemetry to the backend decision engine
Send live bridge telemetry to the backend so it can drive proactive,
aircraft-state-aware ATC. Purely additive — bridge-less sessions are unchanged.

- NormalizedTelemetry contract + sendTelemetry() + telemetry_fired
  (useRadioBackend.ts), sim-agnostic and matched to the backend contract
- extract applyBackendDecision() so telemetry- and pilot-driven ATC responses
  land through one shared path
- normalizeBridgeTelemetry() + change-detection + forwardTelemetryToBackend()
  wired into the existing bridge poll

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 09:23:53 +02:00
leubeem
3b79adf407 feat(pm): send ICAO for taxi-route compute + show start spinner
- useRadioBackend.createSession: forward airport_icao / destination_icao so the
  backend can compute the real OSM taxi route for taxi flows.
- pm.vue: pass the resolved airport ICAO from startMonitoring, and show a
  full-screen overlay ("Calculating taxi route…") while the session is being
  created — the backend computes taxi routes synchronously for taxi-only
  training, so the create call can take a few seconds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 16:18:13 +02:00
leubeem
6fae54c89f feat(stt): seed Whisper prompt with expected readback + per-field debug UI
Whisper prompt seeding (per request):
- ptt.post.ts builds the prompt as generic ICAO bias + this state's expected
  readback appended LAST (survives the 224-token truncation), in both raw token
  form and spoken ICAO form via new radioSpeech.speakToken().
- pm.vue passes the expected phrase + active variable values; classroom.vue
  passes the lesson's expected field values.

Per-field readback debug:
- sttMatch.matchTranscriptionToFields returns fields[] (matched/missing + which
  view matched) plus normalized/denormalized transcription views.
- useRadioBackend types readback_report on the transmit response.
- pm.vue renders a "Readback check" panel in the right log rail; classroom.vue
  renders per-field rows under the STT panel.

Radio-pronunciation fixes (radioSpeech.ts):
- callsign expander handles multi-letter suffixes (DLH6RK -> Lufthansa six Romeo
  Kilo).
- toRadioSpeech now expands airports (EDDC -> Echo Delta Delta Charlie).
- bare altitudes >=1000 in a clearance context are spoken ("climb initially
  5000" -> "climb initially five thousand feet"); speeds/headings untouched.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 14:12:54 +02:00
leubeem
59e35aa087 feat: scenario picker, flow chaining UX, reliable frequency checks
Scenario picker & completion:
- Login → scenario selection screen with complete chains + individual phases
- Completion screen with "fly again / try opposite / back to scenarios"
- Scenario.airport ('dep'|'arr') drives which airport frequencies to fetch;
  arrival scenarios (vfr-arrival, circuit-landing, taxi-in) use arr ICAO

Backend session integration:
- createSession forwards no_chain; response carries active_flow + session_complete
- Pass all six airport frequency variables to session so every chained flow
  has the real airport values from creation
- fetchAirportFrequencies now runs before session creation so resolved
  frequencies are included in backendVariables

Wrong-frequency check:
- airportFreqMap computed (from airportFrequencies, always up-to-date)
  used as primary source in expectedFrequencyForState — immune to flow
  snapshot switches
- setActiveFlow called when response.active_flow changes so local engine
  cursor moves to the correct flow's states after a chain
- Wrong-freq ATC reply appended to communication log (offSchema entry)

Engine fixes (communicationsEngine.ts):
- patchVariables / patchFlags: write directly to the internal reactive
  store, bypassing readonly(ref) which silently blocked all (vars as any)
  .value[k] = v mutations
- appendLogEntry: push ATC speech (and wrong-freq replies) into comm log
- ATC controller_say_rendered appended to comm log after every transmission
2026-06-08 13:03:55 +02:00
leubeem
62721ced4c feat: live expected communication from backend, radio pronunciation toggle, and frequency validation
- 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>
2026-05-20 17:51:44 +02:00
leubeem
e807e483ce Wire /pm to Python backend for stateful ATC training sessions
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>
2026-05-09 17:49:28 +02:00