Commit Graph

381 Commits

Author SHA1 Message Date
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
leubeem
859da2fc0c feat(pm): ignore sub-threshold voice transmissions (STT noise gate)
Whisper hallucinates short real words ("Test", "Thank you", "Okay") on
near-silent or noisy audio. Unfiltered these reached the backend as a wrong
readback attempt — counting toward the 3x-skip — and triggered paid
LLM-router calls. Drop voice (PTT) transcripts below NUXT_PUBLIC_PTT_MIN_WORDS
(default 2); text input is exempt so deliberate short commands still work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 11:35:47 +02:00
leubeem
1d09a9fc2f feat(pm): LLM routing endpoint, usage capture, and admin review view
Backend counterpart to the Python engine's semantic router.

- POST /api/decision/route: service-secret-guarded endpoint the Python
  backend calls on regex-miss. Calls gpt-5-mini (ROUTER_LLM_MODEL),
  validates the chosen id against the candidate set, and writes both a
  UsageEvent (central cost ledger) and a routing-review record — including
  timeouts, with timeoutMs + actual latencyMs — so the budget can be tuned
- LlmRoutingDecision model + GET /api/admin/llm-routing (paginated,
  status-filtered, per-status counts)
- admin "LLM Routing" tab: transcript vs expected phrase, candidate chips
  with the chosen one highlighted, latency/budget chip, model reason
- serviceAuth util (mirrors CRON_SECRET pattern)
- .env.example: ROUTER_LLM_MODEL, SERVICE_SECRET

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 10:27:57 +02:00
leubeem
f2f516acaa feat(pm): bilingual how-it-works help overlay
First-run overlay explaining how /pm works (tune frequency, push-to-talk,
read back, emergency, bug button), reopenable via a new "?" button in the
HUD. Shows one language with a DE/EN toggle; the choice and "seen" state
persist in localStorage so it auto-opens only on first use.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 10:21:33 +02:00
leubeem
a7b9c25831 feat(pm): VFR registration callsigns and pronunciation (#28)
- VFR scenarios get a German D-registration (e.g. D-EKLM) and its
  abbreviated form (callsign_short, D-EKLM -> D-LM) instead of the airline
  callsign; the pilot's first call uses the full registration, ATC uses the
  short form thereafter.
- radioSpeech: spell aircraft registrations phonetically for TTS
  ("D-EKLM" -> "Delta Echo Kilo Lima Mike", "D-LM" -> "Delta Lima Mike").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 10:17:52 +02:00
leubeem
bc71f141e1 feat(pm): add Rejected Take-Off drill to the scenario chooser (#14)
New standalone "Drills" section in the scenario picker, with the
Rejected Take-Off drill (rto-v1). Drills are surfaced separately from
the journey chains via DRILL_IDS / drillScenarios.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 06:34:59 +02:00
leubeem
2773f70ed8 feat(pm): accept any published frequency for a position (#43)
The wrong-frequency gate now accepts any of the frequencies a logical
position publishes (e.g. EDDM has two Tower frequencies, 118.700 and
120.500); expectedFrequencyForState() still returns the primary for the
"contact X on <freq>" prompt. Falls back to the single expected value
when no frequency list resolves.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 06:21:48 +02:00
leubeem
31e58d1a2e fix(pm): tester-round readback, frequency tuning, and PTT fixes
From /pm tester bug reports:
- pre-tune COM1 to the opening pilot state's frequency on scenario start,
  so the first call isn't met with a "wrong frequency" rejection (#5/#6/#21)
- taxi-route phonetics no longer stop at the first comma: "via A, V" now
  speaks "via Alfa, Victor" (#31)
- barge-in: keying the mic stops any ATC speech still playing
- ignore empty / punctuation-only transmissions (silence, stray PTT taps)
- PTT pad turns green while transmitting (was red)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 06:02:03 +02:00
leubeem
2ea37b1da3 Merge branch 'main' of github.com:OpenSquawk/OpenSquawk 2026-06-21 21:00:20 +02:00
itsrubberduck
94b8d304de Add bridge connect PTT controls 2026-06-21 13:11:30 +02:00
itsrubberduck
26f6120be8 hotkey anzeige online 2026-06-20 03:25:06 +02:00
itsrubberduck
7ee9392109 hotkey support 2026-06-20 03:17:53 +02:00
itsrubberduck
33503e6a61 fix(pm): standby strictly mirrors sim while bridge connected
Standby used change-detection like active, so a locally tuned standby
lingered as "the last" channel. While the bridge is connected, standby now
always reflects COM_STANDBY_FREQUENCY from telemetry on every poll. Active
keeps its change-detection anchor so flow/manual tuning isn't overridden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 13:40:20 +02:00
itsrubberduck
0e10e61e40 feat(bridge): map GPS position + true heading into telemetry
Bridge now sends latitude_deg/longitude_deg/heading_deg; map them to
PLANE_LATITUDE/PLANE_LONGITUDE/PLANE_HEADING_DEGREES_TRUE so position and
course land in the telemetry store and surface in /api/bridge/live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 13:36:30 +02:00
itsrubberduck
6f4db2fb01 fix(pm): preserve redirect when sending logged-out users to login
Unauthenticated /pm visits pushed to /login without a redirect query, so
after sign-in they landed on the classroom fallback instead of returning
to /pm. Pass route.fullPath as ?redirect= so the bridge link (incl.
?token=…) survives the login round-trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:55:12 +02:00