Commit Graph

395 Commits

Author SHA1 Message Date
itsrubberduck
3fe0ea5f8a feat(sim-control): wire frequency-sim-control command channel end-to-end
Implements the open items from docs/plans/2026-07-14-frequency-sim-control-design.md
§4/§"Offen für die Implementierungsphase": a per-bridge-token in-memory command
queue piggybacked on the existing telemetry channel, plus the client-side gate
and TTS confirmations.

- server/utils/simControlQueue.ts: TTL-based queue keyed by bridge token
  (enqueue → drainPending → resolve → drainResultsForClient).
- server/api/bridge/data.post.ts: response gains a `commands` field the
  bridge drains on its next telemetry POST.
- server/api/bridge/command.post.ts (new): client enqueues a parsed command,
  re-validated server-side via isValidSimControlCommand.
- server/api/bridge/command-result.post.ts (new): bridge reports ok/failed.
- server/api/bridge/live.get.ts: response gains `commandResults` so the
  client can announce outcomes.
- shared/utils/simControl.ts: wire types, isValidSimControlCommand, and
  simControlRejectionSpeech/simControlResultSpeech TTS phrasing.
- useLiveAtcSession.ts: parseSimControl() gated on bridgeConnected, wired in
  right after the local special cases and before the frequency check —
  matched commands never reach radioBackend.transmit().
- useSimBridgeSync.ts / live-atc.vue: bridgeToken threaded through, command
  results forwarded from the telemetry poll to TTS.

43 new tests (shared parser/validation/speech + server queue lifecycle/TTL).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 09:31:17 +02:00
itsrubberduck
1048b1dbbc feat(sim-control): add fail-closed intent parser for frequency-driven sim setup
Roadmap item "frequency-sim-control": pilot speaks a self-service
sim setup command on frequency ("set me up for an approach from 5000
ft to EDDF 07R", "change my altitude to 8000"). Rule-based, not an
LLM call per transmission — a misparsed command changes the user's
real sim state, and it must never be confused with real ATC
phraseology routed through sttMatch.ts.

parseSimControl() gates on explicit self-service anchors first
("set me up", "put me", "change/set my <param>"); anything without
one of those anchors returns no_intent, so regular readbacks and
clearances can never be misrouted. Matched intents still refuse on
any ambiguous slot (missing unit, out-of-range, invalid runway) with
a typed reason instead of guessing.

Parameter vocabulary follows NormalizedTelemetry (altitude_ft,
ias_kts, heading_deg) since the command travels toward the bridge/sim,
not the decision engine's DecisionNodeAutoTrigger vocabulary.

Design doc covers the still-missing server→bridge write channel
(piggyback on the existing telemetry POST response) — not
implemented yet, this commit is the parser only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 17:47:21 +02:00
itsrubberduck
e4859c418e fix(stt): anchor callsign digits verbatim, fuzzy only the airline name
The whole-string fuzzy fallback in callsignMatches() ran before the
digit-anchored check and let single-digit typos slip through (e.g.
"Lufthansa 350" matched expected "DLH359" at Levenshtein distance 1).
Spoken-digit-word candidates ("three five niner") bypassed the digit
anchor entirely since they contain no digit characters pre-fold.

Fold candidates through denormalizeSpokenAtc before checking for
digits, then require the flight number verbatim (word-boundary regex,
zero tolerance) while keeping the existing ~25% fuzzy tolerance on the
airline-name portion. Single-digit flight numbers (DLH4) now get the
same anchoring as longer ones.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 17:47:11 +02:00
itsrubberduck
46224dd70d chore(live-atc): add dev-only, opt-in API mock for driving /live-atc offline
Verification aid, not a product feature. Guarded by import.meta.dev (stripped
from prod builds) and opt-in via `?mock=1` (`?mock=0` disables). It wraps
globalThis.$fetch and stubs only the Nuxt-side endpoints that would otherwise
need a login, VATSIM, or OpenAI — auth (/api/auth/me, refresh), airport
frequencies, METAR, TTS (silent), STT — and passes everything else through,
including the authoritative Python radio backend on :8000, which is expected to
run for real.

Used to run the full real /live-atc page end-to-end against the real decision
engine: setup → scenario picker → cockpit → text transmit → real controller
reply in the log → readback → session_complete → completion screen. Confirms
the redesign extraction holds on the real page, not just in component harnesses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 09:55:19 +02:00
itsrubberduck
2d2b4925e3 milstone 5 2026-07-10 17:35:44 +02:00
itsrubberduck
918b2e1c12 feat(live-atc): add CockpitShell — HUD header + body layout
Moves the monitor screen's HUD header, body layout (main column + desktop log
rail), bottom nav and their ~450 lines of scoped CSS out of the page and into
CockpitShell.vue, which composes the cockpit components added in Tasks 17-20.
live-atc.vue drops from 1714 to 744 lines.

The shell owns only presentational state (active tab, HUD menu open/close).
inputMode stays on the page as a v-model because a page watcher drives the
pre-recording buffer off it. debugMode and useBugReport also stay on the page,
so the sheets, dialogs and the debugMode-gated DebugPanel are passed back in
through an #overlays slot — that keeps them inside .learn-theme, whose CSS
variables their styles depend on, without handing the shell ownership of them.

The experience-menu rules stay :global(): Vuetify teleports that menu body to
<body>, out of reach of scoped styles.

Verified by rendering the shell on a throwaway route with fake data — the open
question from the last session was that the reworked HUD had never actually
been seen, since it sits behind the auth guard. It renders: the amber
"Report issue" pill is the visually heaviest button in the cluster
(rgba(245,158,11,.18) fill, #fcd34d text) and collapses to an amber icon at
mobile width. The moved media queries survived the move — the log rail shows
and the bottom nav hides at desktop width, and they swap at 375px, where the
Log tab switches the main column over. All shell emits fire and the inputMode,
pilotInput and manual-frequency v-models round-trip. No Vue warnings.

Typecheck clean, 167/167 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:56:19 +02:00
itsrubberduck
9f45e49f5a feat(live-atc): add TextInputPanel, LastTransmissionCard, CommLogRail
Moves the last three inline cockpit blocks out of the page into components,
re-skinned onto the navy/instrument-panel language the rest of the cockpit
already uses (the v-card bg-white/5 glass treatment is gone).

LastTransmissionCard collapses the old four-button row (Mark as faulty / Edit
issue / Reset / Delete) to two icon buttons, per the design doc. Nothing is
lost: "Reset" was a duplicate of the issue dialog's own "Remove flag" action,
so the amber flag icon opens that dialog and a plain × dismisses the card.
resetLastTransmissionFault therefore has no caller left on the page.

CommLogRail wraps the readback-check block plus CommLog for the desktop rail;
the .pm-readback-* styles move with it. The mobile Log tab keeps using
CommLog directly, as the design doc specifies.

Verified by rendering all three on a throwaway route with fake data: the
suggestion chip fills the text field through v-model, the flag button flips
the card into its faulty skin, and the readback rows render matched/missing.
Typecheck clean, 167/167 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:48:45 +02:00
itsrubberduck
39a3650b4e refactor(live-atc): extract FlightInfoSheet, SettingsSheet, BugReportDialog, DebugPanel
DebugPanel now owns the FAB, the drawer, and useDebugSimulation, and the page
mounts it behind `v-if="debugMode"` — so on the normal cockpit path the whole
simulation machinery is never constructed. That required moving `debugMode`
out of useDebugSimulation and into the page: a flag cannot both live inside the
thing it gates and be the gate. The settings sheet is the one place it is
toggled. runFullSimulation's `debugMode = true` line went with it; nothing
called that function, so the write was already unreachable.

BugReportDialog takes the useBugReport handle as a single prop rather than a
dozen individual ones. The composable stays owned by the page because the HUD's
"Report issue" button starts the screenshot capture before the dialog exists.

SettingsSheet carries its own copy of the .pm-seg segmented-control styles and
routes the theme choice back through the page, so usePmTheme.setPreference
still persists it to the cookie.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:35:34 +02:00
itsrubberduck
e836edd108 feat(live-atc): dock the expected-comm hint onto a round, glowing PTT pad
The PTT pad is the control the pilot uses most, but it read as one card among
several. It is now a circular transmit key that lights up cyan at rest and
green while transmitting, with the active frequency called out underneath.

Expected Communication shrinks from a full card to a two-line strip (ATC line,
pilot line) sharing a frame with the pad, so it reads as guidance for the next
transmission rather than as general reading material. The strip still renders
in text mode when the learning aid has something to say, and disappears with
the learningMode toggle as before.

The pilot line's icon was `mdi-account-pilot`, which is not an MDI icon and
rendered as an empty glyph; it is now `mdi-headset`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:26:30 +02:00
itsrubberduck
4a143de4a8 feat(live-atc): put ACTIVE left of STANDBY, lighten the HUD action cluster
Real avionics (Garmin GTN and friends) place the active frequency window to
the left of standby; the panel now matches, so muscle memory transfers.

In the HUD, "Report issue" is the tool pilots reach for most often during a
session, so it gets a filled amber pill with icon + label while Settings drops
to an icon-only gear. The divider moves down to sit directly above Logout,
grouping the three session tools (report / settings / help) together.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:20:57 +02:00
itsrubberduck
ab9b52ad4c feat(live-atc): add RadioPanel as the cockpit's primary frequency control
Moves frequency handling out of the HUD header — where it lived as two
compact chips behind a dropdown — into a first-class module at the top of
the main column, styled like a physical radio stack: STANDBY and ACTIVE
windows flanking a circular swap button, the published-channel row below,
and the readability control in the panel's own corner rather than as a
separate global chip.

Behaviour is unchanged: HoldSelect stays the interaction primitive for
preset selection and manual free-tune entry, and swap/preset/readability
all route to the same useFrequencyPresets and useRadioSpeech handlers.
Tapping a channel chip stages it in standby (setStandbyFrequencyFromList),
matching the old preset behaviour.

The header's hud-center wrapper and the freq-chip/freq-stack/signal-chip
styles it carried are removed; the freq-option/freq-manual menu styles
move into the panel. The active frequency scales down via clamp() so
seven monospace digits cannot overflow the window at 375px.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:13:27 +02:00
itsrubberduck
8159bff0a5 refactor(live-atc): extract setup steps to components, apply navy/radar skin
Moves the four non-monitor screens out of live-atc.vue into
FlightSourceStep, FlightSelectStep, ScenarioPickerStep and
SessionCompleteStep, and replaces the bg-white/5 glassmorphism cards with
the navy/glow treatment from the design doc.

The card skin lives in one shared SetupCard so the four steps cannot
drift apart and the light-theme overrides stay in one place; it renders
as a real <button> when interactive, which the flight-plan list needs.
RadarBackdrop is mounted once around the setup container rather than per
screen. The scenario picker keeps its phase-chain structure verbatim —
only the card treatment changed — and its .pm-flow styles move with it.

Instrument readouts (callsign, route, flight level, squawk) now render in
font-mono per the design doc's convention.

Verified all four screens render in light and dark on a throwaway route,
since the real screens sit behind an auth guard and MongoDB is not
running locally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:58:01 +02:00
itsrubberduck
1d6490d4d9 refactor(live-atc): extract session controller to useLiveAtcSession
Moves applyBackendDecision, handlePilotTransmission, the silence
auto-advance timer, startMonitoring, loadFlightPlans, the screen
transitions (startDemoFlight/launchScenario/flyAgain/backToSetup),
sendPilotText and restoreBugReportState out of live-atc.vue.

The dependency is genuinely circular: the controller drives speech, PTT
and the bridge, while all three need to call back into it. It is
constructed last, and the page hands the other composables two thin
forwarding closures that resolve `session` at call time. The engine,
frequency and speech destructures in the page shrink accordingly —
most of what they exposed is now only read inside the controller.

Also drops normalizeExpectedText, which had no callers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:51:11 +02:00
itsrubberduck
71d148e156 refactor(live-atc): move scenario catalogue to shared/constants/scenarios.ts
SCENARIOS, CHAIN_DEFS and the Scenario type are pure data with no
reactive inputs, but the session controller needs them — leaving them in
the page would mean threading the catalogue through as a dependency.

drillScenarios/chainGroups were computed() over module constants only, so
they can never invalidate; they become plain DRILL_SCENARIOS/CHAIN_GROUPS
consts, evaluated once at module load. Verified the derived shape is
unchanged: 15 scenarios, 2 drills, 3 chains with 4/4/3 segments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:45:11 +02:00
itsrubberduck
1398433cce refactor(live-atc): extract session state to useSessionState
First half of the session extraction: the screen/loading/error refs, the
backend session identity (backendSessionId, lastControllerSay,
backendExpectedPhrase, readback report) and the last-transmission strip
with its fault-reporting dialog.

These are pulled out ahead of the controller because the dependency runs
both ways: useAtisPlayback needs currentScreen, usePttRecording needs
backendSessionId and useRadioSpeech needs setLastTransmission, while the
controller needs all three composables to already exist. State first,
controller last.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:43:42 +02:00
itsrubberduck
33d7aec1dd refactor(live-atc): extract debug simulation and trace inspectors to useDebugSimulation
Moves debugMode, the scripted end-to-end simulation (simulationPilotSteps,
simulationDecisions, runFullSimulation and its helpers), the decision-trace
computeds the debug drawer renders, and debugState/debugNextStates out of
live-atc.vue.

The page keeps only the destructured bindings its debug template still
reads; once DebugPanel.vue exists it constructs this composable itself and
the page stops touching it entirely.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:37:20 +02:00
itsrubberduck
7a1f75152a refactor(live-atc): drop unreachable decision-trace normalization
normalizeDecisionTraceResult had no callers — it was the only root of
ensureTraceCalls, normalizeTraceFallback/AutoSelection, the whole
normalizeTimeline* chain, VALID_TRACE_STAGES and isPlainObject, so that
entire block was unreachable. Same for activeFlowInfo.

Leftovers from the LLM decision routing that moved to the Python backend
(see CLAUDE.md): the trace the debug panel renders now comes straight
from the engine's lastDecisionTrace, already shaped. cloneForTrace stays
— runFullSimulation still uses it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:34:50 +02:00
itsrubberduck
4d94bd3101 refactor(live-atc): extract SimBridge telemetry sync and remote PTT to useSimBridgeSync
Moves the bridge-token poller, COM1 active/standby mirroring, telemetry
forwarding to the Python backend, and the remote-PTT WebSocket out of
live-atc.vue.

The bridge's own onMounted/watch(bridgeToken)/onUnmounted wiring moves
with it, so stopBridgeSync/disconnectPttSocket drop out of the page's
onUnmounted; the remaining teardowns there (ATIS loop, pre-rec capture,
airport refresh, silence timer) still belong to other composables.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:33:05 +02:00
itsrubberduck
618a2a2d53 refactor(live-atc): extract bug-report capture/annotation to useBugReport
Moves the screenshot capture, arrow-annotation canvas handling and the
submit call out of live-atc.vue. The state snapshot it posts is read off
the engine directly; the scenario is passed in.

restoreBugReportState stays in the page: it is session orchestration
(it calls startMonitoring and patches the engine), not part of the
report dialog, and it will move with the rest of the session logic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:31:10 +02:00
itsrubberduck
2176b42bde refactor(live-atc): extract TTS/radio-effects speech to useRadioSpeech
Moves the audio-context/Pizzicato setup, TTS fetch+playback pipeline,
controller/readback speech scheduling, and the radio-check handling
(RADIO_CHECK_RE/SAY_AGAIN_RE/FAREWELL_RE, answerRadioCheck,
performRadioCheck, readability quick-select) out of live-atc.vue.

The interrupt primitives (stopCurrentSpeech/enqueueSpeech and the
audio/abort bookkeeping they tear down) live in a separate
useSpeechInterrupt because useFrequencyPresets needs stopCurrentSpeech
while useRadioSpeech needs frequency state — one composable owning both
would be circular. useSpeechInterrupt is constructed first and threaded
into both.

signalStrength/speechSpeed/radioCheckLoading/radioEffectsEnabled/
readbackEnabled stay page-level refs and are passed in, because
useAtisPlayback already consumes signalStrength before useRadioSpeech
can be constructed (it needs handlePilotTransmission).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:29:04 +02:00
itsrubberduck
efd1a7f1d8 refactor(live-atc): extract PTT/mic recording and pre-rec buffer to usePttRecording
Moves mic access, MediaRecorder handling, the pre-recording rolling
PCM buffer, and Whisper transmission processing out of live-atc.vue.
Keeps the internal AudioContext (prerecCtx) properly encapsulated —
the two external call sites that used to reach into it directly
(the lead-in-duration watcher and the SimBridge remote-PTT handler)
now go through small exposed functions instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:40:46 +02:00
itsrubberduck
5c695f96fb refactor(live-atc): extract ATIS playback/loop scheduling to useAtisPlayback
Moves ATIS carrier/broadcast loop, TTS audio caching, METAR fallback,
and the background airport-data refresh scheduler out of live-atc.vue.
Also extracts the pmLog debug logger to shared/utils/pmLog.ts since it
has no Vue/component dependency and multiple composables need it —
threading it through every composable's parameter list would've been
worse than giving it one shared home. Verified via typecheck plus a
browser mount/redirect smoke test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:33:03 +02:00
itsrubberduck
486dbb27b6 refactor(live-atc): extract frequency/preset management to useFrequencyPresets
Moves radio frequency state, airport-frequency fetching, manual tuning,
swap, and hold-select presets out of live-atc.vue into a composable.
atisVariantLabel and FREQ_ROLE_LABEL also moved here since both the
frequency-preset labeling and the (still-inline) ATIS/radio-speech code
need them — avoids a circular dependency once those move out in later
tasks. Verified via typecheck plus a browser mount/redirect smoke test
(no component test harness exists in this repo).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:25:30 +02:00
itsrubberduck
73d96a11a7 feat(live-atc): add shared RadarBackdrop component for navy/radar visual language
Extracted from start.vue's inline .start-bg__* treatment so /live-atc's
setup screens (Milestone 3) can reuse the same navy/radar/glow look
instead of duplicating the gradients/keyframes a third time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:13:10 +02:00
itsrubberduck
25911e03d7 refactor(live-atc): extract encodeWav to shared/utils, add tests
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:10:29 +02:00
itsrubberduck
10e084c35b refactor(live-atc): extract bridge-telemetry normalization to shared/utils, add tests
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:09:17 +02:00
itsrubberduck
5e266c0733 refactor(live-atc): extract normalizeManualFreq to shared/utils, add tests
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:07:33 +02:00
itsrubberduck
fb25af913a atc header 2026-07-10 08:33:36 +02:00
itsrubberduck
142d18f850 feat(start): add tile illustrations, radar background, equal-height cards 2026-07-10 08:05:14 +02:00
itsrubberduck
080691643d light mdoe fix 2026-07-10 07:46:52 +02:00
itsrubberduck
736e467bf3 feat(pm): trim nav bar — remove Feedback, move Theme into Settings
- Remove the Feedback link from /pm's nav entirely
- Theme (dark/light/system) now lives as a segmented control at the
  top of the Settings dialog instead of its own nav menu
- Help and Logout become icon-only and move to the far right,
  separated by a divider from Bug/Settings
2026-07-10 07:03:40 +02:00
itsrubberduck
83d9b8ae09 feat(onboarding): add /start mode chooser, fix pilot-profile-setup UX
- Fix missing icon for the free-tier/self-host pricing option
- Make selected cards visually obvious (checkmark badge, stronger border)
- Add Back navigation between onboarding steps
- Network-experience screen: button reads "No, not yet" instead of an
  ambiguous "Continue" when nothing is selected
- classroom-introduction reverts to Classroom-only content; the
  Classroom/Live ATC chooser moves to a new /start page instead
- Remove the "Skip to Classroom" button in favor of a subtle text link
- New /start page: choose Classroom or Live ATC after login (when
  there's no specific redirect target) or right after finishing
  onboarding. Shows a "start with Classroom" hint only for first-time
  arrivals from pilot-profile-setup. Live ATC is locked for 24h after
  registration.
2026-07-09 23:53:12 +02:00
itsrubberduck
c2717d6a89 fix(pm): re-theme custom CSS classes missed by the light-mode pass
The first light-mode pass only covered Tailwind utility classes in the
template. ~40 hand-written classes (.pm-flow-node, .pm-readback-*,
.freq-*, .pm-seg-*, .pm-bottomnav, signal bars) still hardcoded
rgba(255,255,255,N)/#fff, so scenario chips, the readback panel, and
the frequency display were unreadable in light mode. Switched them to
var(--text/--t2/--t3/--border) and color-mix() so they follow the
theme. Debug panel (dev-only) left as-is.
2026-07-09 22:59:32 +02:00
itsrubberduck
c4496c545f feat(classroom-intro): add Classroom/Live ATC alpha chooser
LiveATC is now in alpha, not closed testing, so the intro offers both
paths up front: Classroom (default, dry radio practice) or Live ATC
alpha test connected to the simulator, with an honest disclaimer that
it's not fully reliable yet and we want bug reports.
2026-07-09 17:26:29 +02:00
itsrubberduck
e521dcba0f feat(pm): add light/dark/system theme toggle
Defaults to dark (matching the rest of the app) with a menu to switch
to Light or System. Custom CSS-variable overrides handle the
hand-rolled Tailwind classes; a second Vuetify theme is swapped in
sync for Vuetify components. Scoped entirely to /pm.
2026-07-09 17:26:21 +02:00
itsrubberduck
5531da0e9a fix(onboarding): also ask OS for X-Plane, not just 'other'
X-Plane runs on Windows/Mac/Linux same as any non-MSFS sim, so it
should trigger the OS follow-up too.
2026-07-09 17:26:11 +02:00
itsrubberduck
67385aedaa feat(onboarding): add pilot-profile-setup page with all 7 screens
Single-question-per-screen wizard (cockpit, radio confidence, stress
point, network experience, hangar tools + paid-tool duration, feature
wish capped at 2, funding preference) plus the reward callsign card
and skip handling. Verified end-to-end against a mocked API layer in
the browser (MongoDB Atlas isn't reachable from this sandbox).
2026-07-09 16:48:12 +02:00
itsrubberduck
7c4369ce09 feat(onboarding): add PUT /api/onboarding/profile route with radioLevel side effect 2026-07-09 16:27:18 +02:00
itsrubberduck
294e48c2cd feat(onboarding): add GET /api/onboarding/profile route 2026-07-09 16:26:18 +02:00
itsrubberduck
43f8f7d092 feat(onboarding): add sanitization and callsign computation helpers 2026-07-09 16:25:29 +02:00
itsrubberduck
bd21e91182 feat(onboarding): add PilotProfile model 2026-07-09 16:24:37 +02:00
itsrubberduck
f039eb0c0a feat(onboarding): add shared pilot-profile option config 2026-07-09 16:24:00 +02:00
leubeem
4adefe9ce3 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
7d9f8378f7 Merge branch 'feat/telemetry-atc-backbone' 2026-07-06 17:07:32 +02:00
leubeem
d4c2d86246 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
5b8e5c3205 fix(pm): stop sending placeholder taxi_route so backend computes it
The backend computes the real OSM taxi route from airport_icao + stand/runway
and falls back to the flow's YAML default on its own. Sending a placeholder
taxi_route counted as a caller override and suppressed that computation, so no
session ever got a computed route. Drop it from the create payload.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 09:08:54 +02:00
leubeem
b7b86b2a17 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
b1e2165b40 feat(pm): add Uncontrolled Field (Info) arrival to the Drills chooser (#38)
Surfaces the info-arrival-v1 flow as a standalone drill and gives it a
German D-registration callsign (same as VFR scenarios).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 16:28:06 +02:00
leubeem
17d382e2b4 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
d8b714a35b feat(pm): say-again, sign-off, readback-correct, drop stale transmissions
- "say again" / "repeat" → ATC re-speaks its last transmission locally (#8/#40)
- bare sign-off ("thank you", "tschüss", "good day" with no readback/digits)
  gets a polite "Good day" instead of being evaluated (#32)
- positive "Readback correct" confirmation prefixed when every required
  readback field matched, not only on the IFR clearance (#10)
- a newer pilot transmission supersedes an older one still awaiting its
  backend reply; the stale reply is dropped (#16)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 16:02:07 +02:00