Commit Graph

228 Commits

Author SHA1 Message Date
itsrubberduck
034ddbab2e feat(bug-reports): trace reports by code and tell Live ATC from Classroom
Every report now gets a UUID that the notification mail quotes as
"Nenne den Fehlercode <uuid> beim Commit", so a fix can be tied back to
the report it closes. The mail also names the area it came from, and the
comment field is relabelled "Fehlerbeschreibung/Featurewunsch" since it
collects feature wishes just as often as bugs.

Classroom loses its plain feedback link and gets the same reporter as
Live ATC — screenshot, arrow annotation and all. `useBugReport` takes an
options object so the communications-engine snapshot stays optional; the
dialog moves out of `live-atc/cockpit` now that both pages use it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 21:11:43 +02:00
itsrubberduck
4dbd0bda9f fix(live-atc): keep the callsign visible on narrow screens
The cockpit HUD is already a sticky top bar carrying the callsign, but a media
query hid the whole flight-context group below 720px — so on a phone the pilot
had no way to see their own callsign without opening the flight sheet.

Shrink the group there instead of hiding it. Verified at 320/375/1280px: the
callsign stays in the sticky bar while the body scrolls, and the HUD row still
fits without horizontal overflow (318px of 318 at the 320px width).

Also fills two gaps the mock dev harness had after the ATIS work: it now stubs
/api/airports/:icao/atis, and its STT fallback no longer transcribes to
"say again", which the backend now intercepts as a request to repeat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 20:12:34 +02:00
itsrubberduck
9ead29a52a fix(live-atc): match the D-registration to the aircraft class
VFR scenarios drew from a fixed pool of D-E** registrations regardless of type,
so an A320 was assigned "D-ETMO". The letter after "D-" is assigned by weight
and type class (LuftVZO Anlage 1): D-E is single-engine pistons up to 2 t, so
an airliner has to be D-A.

Pick the class from the ICAO type designator — D-A for airliners, D-I for light
twins, D-H for helicopters, D-E for light singles and anything unrecognised,
since VFR scenarios are overwhelmingly light aircraft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 20:05:04 +02:00
itsrubberduck
8b62b8e3aa feat(live-atc): drive ATIS letter, runway, QNH and wind from the resolved report
The broadcast and the flow variables used to read different sources: the audio
came from VATSIM while `information` was genATIS() — a random A-Z letter — and
the runway came from genRunway(), a hard-coded list unrelated to the airport.
So the pilot could hear "information Q" and be expected to call "information T".

Both now read the same AtisReport. The frequency list backfills the letter and
broadcast text from it, so an ATIS station always announces an information
letter even when no VATSIM controller is online. QNH and surface wind come from
the same observation, so the controller stops contradicting the ATIS.

A synthesised ATIS is labelled "Simulated ATIS" in the frequency picker rather
than being credited to VATSIM.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 19:40:40 +02:00
itsrubberduck
c828ffe221 Route Live ATC entry through Bridge 2026-07-23 08:31:41 +02:00
itsrubberduck
db74c48b95 Make classroom intro skip action more visible 2026-07-22 21:41:21 +02:00
itsrubberduck
1dd228b5d3 feat(live-atc): route TTS through local bridge with cloud fallback 2026-07-22 20:32:04 +02:00
itsrubberduck
db984c22bb feat(live-atc): route STT through local bridge with cloud fallback 2026-07-22 20:30:24 +02:00
itsrubberduck
0a51fe68ab feat(live-atc): local speech bridge discovery composable 2026-07-22 20:27:43 +02:00
itsrubberduck
990e6059eb bridge connct auferäumt 2026-07-22 17:02:57 +02:00
itsrubberduck
4d8b99ec80 feat(ui): declutter start, classroom header, live-atc & bridge
- start: simplify mode cards — whole card is one click target,
  badges become inline labels, equipment chips become a single
  muted footer line
- classroom: replace back/switch-mode + brand buttons with the
  Classroom↔Live ATC experience dropdown
- live-atc (CockpitShell): drop logo + "OpenSquawk" wordmark next to
  the dropdown; remove the flashy Report-issue coach animation & arrow
- bridge: cut the "How do you want to train?" cards, lead straight
  into a clean header + OS picker; simplify the linking/requirements
  section and drop redundant per-card Alpha badges

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 16:58:14 +02:00
itsrubberduck
9657d38aca feat(classroom): first-run nudge to continue after a readback
After the very first readback is checked, highlight the footer's primary
button (pulse + "Readback checked — continue here" bubble) so learners
realise they advance from there. Shown once, then dismissed permanently
via localStorage on the first advance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 09:16:29 +02:00
itsrubberduck
38226d3afb feat(ui): clearer onboarding across start, classroom, live-atc & bridge
- index: replace header GitHub link with a "Sign up / Log in" primary button
- start: number the modes (1. Classroom / 2. Live ATC), add headset/mic
  equipment chips, and unlock Live ATC immediately for new accounts
- classroom: replace brand logo + experience dropdown with a clear
  "Back / Switch mode" link back to /start; remove the mic/STT readback
  input so the readback is text-only again
- live-atc: first-run coach mark pointing at the "Report issue" button
- bridge: add a "How do you want to train?" split — flight sim via the
  Bridge vs. a browser-only web version (dry run, no live sim link)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 08:47:21 +02:00
itsrubberduck
6dbd9c3ef4 fix(classroom): force English (US) voice for browser TTS default
When no fixed instructor voice is chosen ("Standard · Ryan US"), the
Web Speech path set no voice and fell back to the OS default (German on
a German system). Now prefers an en-US (then en) voice, matching the
online instructor default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 19:42:35 +02:00
itsrubberduck
3cd07e4331 feat(live-atc): manual flight entry on setup screen
Adds a "Manual flight" card alongside VATSIM and demo so a flight can
be entered by hand (callsign, dep, arr, optional aircraft/altitude)
without a VATSIM account. Feeds into the same scenario picker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 19:37:53 +02:00
itsrubberduck
4f367a67aa fix(tts): back to fast Piper controllers, classroom voice setting
Kokoro-82M generates several times slower than Piper — since the controller
pool moved onto it, every ATC reply arrived seconds late. Controllers return
to distinct Piper speakers with the standard US voice (ryan) as the product
default, so replies are fast again and the default sounds like before.

The classroom gets an instructor-voice setting: standard US voice by default,
'random per module' (stable instructor per module), or one of ten named
US/GB voices.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 12:36:19 +02:00
itsrubberduck
f03355daff feat(tts): curated Kokoro+Piper radio voices, classroom instructor from pool
Controllers now speak with the four Kokoro speakers (bm_george, bf_emma,
am_michael, af_heart — heard most, best quality), pilots with six distinct
Piper speakers (alan, alba, joe, amy, bryce, kristin); ATIS moves to the
dedicated ljspeech broadcast voice. The classroom instructor draws a stable
controller-pool voice per module when the user hasn't configured one, and the
TTS cache key follows the effective voice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 11:50:30 +02:00
itsrubberduck
04eb2f18ff feat(live-atc): squelch open/close transients around transmissions
Carrier-open click at speech start, ~230ms noise tail cut off by a closing
click at speech end. Playback holds the effect chain alive past the voice so
the tail rings out instead of being killed with the noise generators.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 10:49:56 +02:00
itsrubberduck
3d5a0a4e59 feat(live-atc): per-position controller personas with voice and pace
Each ATC position (session + airport + type + frequency) now gets a stable
persona: a controller-pool voice and a base pace of 1.1-1.3x, jittered +/-0.05
per transmission. Anything spoken without an explicit voice — the controller
reply path and AI-traffic ATC lines — uses the persona; pilot voices are
untouched. A new session reshuffles the shift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 10:48:19 +02:00
itsrubberduck
b87884a608 feat: add complete SEO metadata and indexing controls 2026-07-18 21:29:31 +02:00
itsrubberduck
db1155c8c9 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
c73fefe409 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
itsrubberduck
3172516918 feat(websim): browser A320 cockpit to test /live-atc without MSFS (WIP)
Flight model (ground/air physics, SELECTED/NAV/APPR/AUTOLAND autopilot with
STAR sequencing and ILS capture), bridge client that feeds the existing
/api/bridge/* endpoints so /live-atc can't tell it apart from a real bridge,
and the cockpit UI (PFD reuse, FCU, radio panel, Leaflet ND, three.js
exterior, spawn presets at EDDF/EDDS). Design doc:
docs/plans/2026-07-16-websim-design.md.

Also adds a local-dev-only auto-login (/dev-login, server/api/dev/login.post.ts)
that bypasses the invite-only login and MongoDB entirely via a fixed in-memory
user, so require-auth pages are reachable for local testing even when the dev
DB is unreachable. Hard-disabled outside development.

Status: unit tests green (yarn test) and typecheck clean (yarn typecheck).
Browser walkthrough of the actual cockpit (flying a preset, confirming
telemetry reaches /live-atc) is not yet done — picking up from a fresh dev
server + /dev-login?redirect=/flightlab/websim confirmed the spawn screen
renders past auth, but full instrument/map/exterior verification is still
outstanding.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 18:49:31 +02:00
itsrubberduck
672ac18ac7 fix(sim-control): narrow SimControlParseResult via type predicates
3fe0ea5 left `nuxt typecheck` failing on main:

  useLiveAtcSession.ts: Property 'reason' does not exist on type
  'SimControlParseResult'.

Cause: the union is discriminated by a boolean, and the project builds
with `strict: false` (nuxt.config.ts). With strictNullChecks off,
TypeScript does not treat `true`/`false` literal types as discriminants,
so `if (r.matched) … else r.reason` never narrows. Nothing about the
sim-control types themselves is wrong — the same three lines with any
boolean-discriminated union fail identically.

Adds isSimControlMatch/isSimControlRejection type predicates, which narrow
regardless of strictNullChecks, and routes the one caller through them.
Turning on strictNullChecks would fix it at the root but is a repo-wide
change, not a bug fix.

The pre-push hook was correctly refusing to push this; origin/main is
clean, so the breakage never escaped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 17:51:09 +02:00
itsrubberduck
7d49f18cc6 feat(live-atc): add simulated AI background traffic on the tuned frequency
Implements the ai-traffic roadmap item per
docs/plans/2026-07-14-ai-traffic-architecture-design.md.

Simulated other aircraft on the user's frequency — callsigns, ATC
instructions, readbacks in their own stable voice, handovers — as pure
scenery. It never touches radioBackend: the Python backend keeps owning
the dialogue *with* the user, useAiTraffic owns the radio *around* the
user. The two share only the speech queue (arbitration) and the log.

Rules live as pure, seeded, framework-free modules under
shared/utils/aiTraffic/ so they run in tsx --test without a browser:
callsign collision rules, wake/in-trail separation, runway slots, the
speed ladder, direct validation, the §3 decision table, and the gating
chain. app/composables/useAiTraffic.ts wires them to Vue (1 Hz tick,
spawner, scheduler).

Gating is evaluated twice — before enqueue and again at playback, since
seconds pass in between. Traffic never keys up while the user holds PTT,
while their transmission is out at the backend, or inside the fresh
readback window. Off by default; the toggle surfaces the feature's v1
limitations rather than burying them in a doc.

Zero LLM calls: variance comes from seeded RNG over template variants.

Deviations from the design, both documented in the design doc:
- Adds SimAircraft.quietUntilSec. The design's rule table says "first
  matching row per tick" but never says an instruction must be allowed to
  take effect before the next one. Without it the planner re-derives the
  same unresolved condition every second and nags one aircraft with the
  same vector: 624 calls/30min measured, vs 90 with the cooldown.
- Airline pool limited to the 14 designators DEFAULT_AIRLINE_TELEPHONY
  already knows; UAE/AUA/WZZ from the design would be spelled out letter
  by letter instead of spoken as airline names.

Verified: 406 tests pass (176 new), no new typecheck errors, /live-atc
compiles and serves. The manual in-session walkthrough (audible traffic,
toggle mid-session) is NOT verified — it needs a login and the Python
backend. The 30-minute deterministic integration run stands in for it and
caught two of the three bugs found during development.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 17:46:52 +02:00
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
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