Commit Graph

345 Commits

Author SHA1 Message Date
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
itsrubberduck
c6c8ac7123 chore(bridge): add bridge logo asset
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:42:26 +02:00
itsrubberduck
d5e93283db feat(pm): mirror full COM1 panel (active + standby) from SimBridge
Sync both COM_ACTIVE_FREQUENCY and COM_STANDBY_FREQUENCY from bridge
telemetry into the radio, each tracked independently so they only retune
on an actual sim change. Active retune still cuts in-progress ATC speech;
standby has no audio side effects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:40:26 +02:00
itsrubberduck
66208d8c5d feat(pm): auto-tune radio from SimBridge telemetry via ?token
When /pm is opened with ?token=<bridge-token>, poll /api/bridge/live for
fresh telemetry. While the bridge keeps posting, mirror the sim's COM1
active frequency into the radio (only on actual sim change, so manual/flow
tuning isn't clobbered) and show a "Bridge connected" badge in the HUD.

Telemetry now carries COM_ACTIVE_FREQUENCY/COM_STANDBY_FREQUENCY from the
bridge's com_active_frequency/com_standby_frequency fields.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:30:23 +02:00
itsrubberduck
d6b8e608b6 refactor(bridge): simplify connect page to a focused linking flow
Strip the overloaded two-step grid, status cards and elaborate success
overlay down to a single centered card with clear states: ask for code,
prompt login, link (auto), and a state-aware success view. Keep the
background photo (more visible) and the server log as a collapsible panel.
Attempt to auto-close the tab once linked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:18:26 +02:00
itsrubberduck
0aa8ccc496 fix(pm): clearer arrow-annotation instructions in bug report dialog
Explain plainly that testers drag an arrow to the broken spot (mouse or finger),
add an on-image hint until the first arrow is drawn, and switch the annotation
canvas to pointer events so touch dragging works as the instructions promise.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 10:58:26 +02:00
itsrubberduck
bfa83305e8 ci: add pre-push hook (auto-installed) and API/model smoke tests
- .githooks/pre-push: runs vue-tsc before every push; blocks TypeScript
  regressions locally without any manual developer setup
- postinstall: git config core.hooksPath .githooks activates the hook
  automatically on yarn install (yarn 4, enableScripts: true)
- tests/smoke/apiHandlers.smoke.test.ts: import-level smoke tests for all
  bug-report handlers + 3 core admin handlers — catches broken exports and
  top-level runtime errors without a DB or running server
- tests/server/bugReport.test.ts: 16 unit tests covering comment validation,
  contact-string building, model schema fields, status enum, and patch logic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 10:46:56 +02:00
itsrubberduck
d8c27260da fix(pm): make bug-report screenshots and state restore actually work
Screenshots: html2canvas 1.4.1 only parses rgb/hsl and throws on color-mix(),
which the app uses app-wide; the throw was swallowed so no screenshot was ever
captured. Swap to modern-screenshot (native SVG foreignObject — supports
color-mix/oklch), capture before opening the dialog, surface failures instead of
hiding them, and show a capture spinner on the Bug button.

State restore: the old handler only patched local vars/flags and loaded the tree
— it never entered the monitor screen or created a backend session, so nothing
happened. New restoreBugReportState() reuses startMonitoring() to spin up a real
session for the same flight/scenario from the snapshot, overlays saved
variables/flags, and replays the captured comm log. The Python backend has no
mid-session resume, so the session restarts at the flow start and the captured
state id is surfaced in a banner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 10:43:55 +02:00
itsrubberduck
013ac12241 feat(pm): add bug-report button with screenshot annotation and admin panel
- New BugReport MongoDB model (comment, contact, userId, screenshot, pmState, status)
- POST /api/bug-reports — authenticated submit; emails emanuel@faktorxmensch.com on receipt
- GET/PATCH /api/admin/bug-reports + /[id] — admin list, detail with screenshot, status toggle
- /pm: "Bug" button in HUD captures viewport screenshot (html2canvas), shows annotation
  canvas where testers can draw arrows; submits comment + contact + state snapshot
- /admin: new "Bug Reports" tab with open-count badge, screenshot expand, "Erledigt" toggle,
  and "In /pm öffnen" link that restores captured engine state via ?restoreBugReport=<id>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 10:05:25 +02:00
leubeem
6c8bd0b466 fix(pm): forward arrival/shared variables to the backend session
backendVariables was a departure-shaped subset, so arrival flows (IFR and VFR)
ignored the selected flight and ran on YAML defaults for runway, QNH, surface
wind, aircraft type, cruise level and assigned squawk — even though the engine
generates them. Forward those (with name mapping qnh_hpa→qnh, acf_type→
aircraft_type, cruise_flight_level→cruise_level). Also fixes taxi/tower on
departure, which use runway/qnh/surface_wind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 21:56:07 +02:00
leubeem
54aa999875 fix(pm): require manual tuning at session start; name the correct frequency
Reverses the auto-tune: tuning to the first controller is part of the exercise.
Each scenario now starts from a fixed baseline frequency (121.900), and the
first call is rejected until the pilot dials the correct one — we do NOT silently
put them on the expected frequency.

The wrong-frequency reply now names the position and frequency to switch to,
e.g. "DLH6RK, you are on the wrong frequency. Contact Approach on 119.900."

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:28:10 +02:00
leubeem
fdc8bbd02d fix(pm): tune radio to the starting position's frequency on session start
The initial active frequency was only set via a hardcoded EDDF special-case
(121.900); every other airport — and any arrival that begins on Center/Approach
rather than Delivery — kept a stale/default active frequency. That triggered an
immediate "check frequency" on the pilot's very first call (e.g. IFR enroute
start: on 121.900 but Center expects 121.800).

Now the radio is tuned to expectedFrequencyForState() for the start position
when the session begins, so the first call is always on the right frequency and
subsequent handoffs change to the correct next-position frequency.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:24:26 +02:00
leubeem
64ecb564a9 feat(pm): group scenario chooser by journey with a phase flow
Reworked the scenario picker so it's clear which single-phase practice belongs
to which chain. Scenarios are grouped by Departure / Arrival; each complete
chain renders as a left-to-right flow of its phases with arrows
(e.g. Clearance → Startup & Taxi → Tower → Departure). Tap any step to practise
just that phase, or "Fly full" to run the whole chain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:09:07 +02:00
leubeem
5d7d74f9e0 feat(pm): add IFR arrival scenarios to the chooser
Adds the IFR arrival to the PM scenario picker:
- Complete chain "IFR Arrival" (Enroute → Approach → Landing → Taxi-in),
  starting at ifr-enroute-arrival-v1.
- Individual phases: Enroute Descent, IFR Approach, IFR Landing
  (taxi-in already present). All use the arrival airport for frequencies.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:04:10 +02:00
leubeem
b80feb80d6 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
453b04881f test: cover the core engine and auth rotation/JWT hardening (TEST-03, TEST-04)
TEST-03 — communicationsEngine had zero tests. Add tests/shared/
communicationsEngine.test.ts exercising the deterministic core: system load &
ready state, VariableDefinition unwrapping, dual {{}}/{} template rendering,
patchVariables, moveToSilent (cursor advance + state actions + controller log),
unknown-state handling, getStateDetails, and normalizeATCText expansion.

TEST-04 — auth utils were tested but rotation and JWT verification were not.
Extend tests/server/auth.test.ts with refresh-token rotation (valid rotate,
missing cookie, access-token-as-refresh, version mismatch) and JWT hardening
(alg-confusion rejection, tampered signature, expired, malformed).

97 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 12:14:59 +02:00
leubeem
6b5e8b9df8 fix(security): mandatory cron secret + reject placeholder JWT secrets (SEC-07, OPS-02, SEC-09)
SEC-07 — committed secrets:
- Replace real-looking defaults in .env.example (JWT_SECRET/JWT_REFRESH_SECRET
  "changeme", MANUAL_INVITE_PASSWORD "pm.local@zghl.de") with CHANGE_ME
  placeholders, and drop the personal DOME_LIGHT_WEBHOOK_URL default.
- Add a Nitro startup plugin (server/plugins/validate-secrets.ts) that refuses
  to boot in production when JWT_SECRET is unset, looks like a placeholder, or
  is shorter than 32 chars (warns only in development).

OPS-02 / SEC-09 — cron endpoints:
- requireCronSecret now fails closed: when no CRON_SECRET/KPI_CRON_SECRET is
  configured the endpoint returns 503 instead of being publicly callable
  (previously it allowed the request with a warning). Both cron routes already
  call the guard. Prefer the x-cron-secret header over the loggable ?secret=
  query param; document CRON_SECRET in .env.example.

Operational note: production deployments must now set JWT_SECRET (>=32 chars)
and CRON_SECRET, or the server won't start / crons return 503.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:07:27 +02:00
leubeem
3ea297f050 ci: make typecheck a real blocking gate; bump actions to Node 24 majors
The previous `vue-tsc --noEmit` step was a no-op: the root tsconfig uses
`files: []` with project references, so without `--build` it checks zero files
and always passes. Switch to `vue-tsc --build` (new `yarn typecheck` script)
and make the job blocking.

Fix the one error this surfaced: UsageEventDocument extended mongoose.Document,
whose `model` method collides with the `model: string` field. Use the
recommended pattern — a plain attrs interface passed to the Schema/Model
generics (hydrated docs still expose Document methods). Typecheck is now clean.

Bump actions/checkout@v5 and actions/setup-node@v5 to silence the Node.js 20
runtime deprecation (forced to Node 24 from 2026-06-16).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:28:27 +02:00
leubeem
f097141e8a feat(atc): bias Whisper STT toward aviation phraseology
Replace the generic one-line Whisper prompt with a vocabulary-rich bias prompt
(phonetic alphabet, aviation number words, common phraseology, and the airline
telephony names pulled live from DEFAULT_AIRLINE_TELEPHONY) and set
temperature: 0. Aviation R/T is a constrained domain, so seeding the expected
vocabulary improves transcription of callsigns, runways, and readbacks — the
upstream input to the decision engine. Stays within the ~224-token prompt cap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:47:22 +02:00
leubeem
13dcee52ab ci: fix red radioSpeech assertion and add a test/typecheck pipeline
The suite was red on main: the SID test still expected the old
letter-by-letter spelling ("Mike Alfa Romeo...") after the pronunciation
change to speak named waypoints as words ("Marun seven Foxtrot"). Update the
stale assertion.

Add a GitHub Actions workflow: yarn install + yarn test as the required gate,
plus a non-blocking vue-tsc job (TS strict mode is still off — promote to
required after that cleanup). Suite now 80/80.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:47:09 +02:00
leubeem
10fabe67ba docs: align CLAUDE.md/AGENTS.md/README with the two-repo architecture; drop dead LLM routing
The LLM decision routing moved to the external Python backend
(OpenSquawk-LiveATC-api), but the core docs still described the old in-Nuxt
path (routeDecision(), /api/llm/decide, a Nuxt /api/decision-flows/runtime
route) — none of which exist. Every agent session started with a wrong mental
model.

- Rewrite CLAUDE.md to describe the real /pm flow (useRadioBackend -> Python
  backend owns authoritative state; Nuxt owns STT/TTS/audio/auth/editor) and
  fix stale commands (bun -> yarn).
- Fix the two stale AGENTS.md lines (openai.ts is getOpenAIClient() only;
  decisionFlowService is editor-only, no Nuxt runtime route).
- README: note the Python backend is required for /pm; correct server/ desc.
- Remove dead shared/utils/openaiDecision.ts (called the non-existent
  /api/llm/decide) and the now-orphaned LLM decision contract types in
  shared/types/llm.ts. Trace types used by pm.vue are kept.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:46:57 +02:00
leubeem
7cc1ca1a10 Merge branch 'main' of github.com:OpenSquawk/OpenSquawk 2026-06-15 13:00:57 +02:00
itsrubberduck
43a106f3c8 manual frequencies 2026-06-12 10:14:38 +02:00
leubeem
97d6475208 feat(pm): recover gracefully when the backend session expires
The radio backend now expires sessions idle for 5+ hours and may restart
without in-memory state. A 404 on transmit previously left the monitor
screen stuck; now it clears the session, returns to the scenario picker,
and shows a dismissible "session expired" alert there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:17:14 +02:00
leubeem
7367f7cdb7 feat(server): per-user AI usage tracking, cost alerting, and endpoint hardening
Usage tracking:
- new UsageEvent collection records every STT/TTS/LLM call per user with
  provider, model, volume (audio seconds, characters, tokens) and an
  estimated USD cost; self-hosted providers (Speaches/Piper) and cache
  hits record at $0
- pricing table for whisper-1, tts-1, gpt-5-nano & co. in server/utils/usage.ts
- weekly KPI mail gains an "AI-Nutzung & Kosten" section: weekly and
  rolling 30-day cost, per-kind breakdown, top 5 users by cost
- quota alert mail when rolling 30-day cost exceeds USAGE_ALERT_USD
  (default $5), at most once per calendar month (UsageAlertDelivery)

Hardening:
- /api/atc/say now requires an authenticated session (middleware
  exemption removed); useFlightLabAudio sends the bearer token
- /api/service/tools/latency requires auth (was a public LLM endpoint)
- per-user rate limits: PTT 20/min, say 60/min, latency 5/min
- cron endpoints (waitlist-drip, weekly-kpi-report) require a shared
  secret via ?secret= or x-cron-secret (CRON_SECRET, falls back to
  KPI_CRON_SECRET); allowed with a warning while unset so existing
  deployments keep working
- PTT records the actual transcribed audio duration for billing accuracy

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:17:03 +02:00
leubeem
cab7b36d85 feat(atc): accept preNormalized flag on /api/atc/say
Clients that already ran the full radiotelephony normalizer (see
normalizeATCText) pass preNormalized: true so the server skips
normalizeATC — double-normalizing corrupts the text, e.g. expandAirports
spells the city name "MAIN" letter-by-letter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:12:53 +02:00
leubeem
3c6816c44c feat(pm): live ATIS broadcast loop with METAR-slot refresh and multi-station support
- per-airport ATIS loop keyed by station with a virtual start epoch, so
  re-tuning resumes where the broadcast would be instead of restarting
- refetch airport data at :23/:53 to follow VATSIM ATIS regeneration from
  real-world METAR publication, with faster retries while no ATIS is on
  the feed; prefetch audio when the info letter changes
- support separate arrival/departure ATIS stations on different frequencies
- cancel the deferred audio teardown on retune so a fresh broadcast is not
  killed by the previous stop()'s fade-out timer (atisAudioLoop)
- comm log shows newest entries first

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:10:35 +02:00
leubeem
2a504885b5 fix(radio): speak SID names and waypoints as words, expand more ATIS elements
- SID basenames and 5-letter waypoints (ANEKI 7S, SULUS) are pronounceable
  by design and are now spoken as words instead of letter-by-letter phonetics
- skip acronyms (ATIS, RNAV, MAIN, ...) when spelling 4-letter ICAO codes
- expand stand/gate designators, ATIS information letter, and surface wind
  groups for TTS
- normalizeATCText now runs full client-side radiotelephony expansion
  (callsigns, airports) since preNormalized texts skip the server normalizer

Note: tests/radioSpeech "normalizes SID suffix and METAR data" still expects
the old spelled-out SID behavior and fails until updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:10:22 +02:00
leubeem
3b16b6f1d6 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
1ae985fdc2 fix: interrupt ATC speech on frequency change, fix PTT stack overflow
- Stop in-progress ATC speech (HTTP request + audio playback) when the
  active frequency changes via preset select or swap button. Prevents the
  pilot "hearing" a controller on a frequency they have already left.
- Fix RangeError crash for long PTT recordings: btoa(String.fromCharCode
  (...largeArray)) overflows the call stack above ~2 s of audio and
  silently drops the PTT request. Replaced with chunked 8 KB conversion.
- Add 30 s PTT auto-stop timer so runaway holds are submitted rather than
  silently lost.
- Add AbortSignal support to useApi so fetch requests can be cancelled.
2026-06-08 10:14:07 +02:00
itsrubberduck
853418ecdb neue tabbed view 2026-05-31 11:18:45 +02:00
itsrubberduck
f22a79ba00 add Prerec mode for pm 2026-05-29 18:50:24 +02:00
itsrubberduck
3bcbb42479 fix(pm): remove max-width from hud-inner
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 21:22:49 +02:00
itsrubberduck
2ad47259e1 fix failed deployment 2026-05-28 21:04:09 +02:00
itsrubberduck
fe3d0b3c7b neues navbar design 2026-05-28 14:16:42 +02:00
itsrubberduck
d6aa858ff8 feat(atis): inline METAR expansion, airport name lookup, acronym fix
ATIS text often arrives in raw METAR form (e.g.
"METAR EDDF 281050Z AUTO 02008KT 320V070 CAVOK 24/02 Q1025 NOSIG"),
which TTS reads as letter-by-letter spelling. The normalizer now expands
the full WMO Code Form FM 15-XV vocabulary inline: DDHHMMZ date stamps,
compressed wind (with gusts, VRB, calm), wind variability ranges, RVR
(R25L/1500N), wind shear, slash-form temp/dewpoint, Q/A pressure,
NSC/SKC/CLR/NCD/VV cloud codes, weather phenomena (with intensity and
descriptors), recent-weather RE prefix, BECMG/TEMPO/FM/TL/AT trend
codes, and strips RMK remarks. Plus ATIS/METAR/SPECI get lowercased
so TTS pronounces them as words (pilots SPELL ILS/QNH/VOR so those
stay uppercase).

Airport ICAO codes are substituted with their OpenAIP name when the
frequencies endpoint returns one. New `airportName` field added to
the FrequencyResponse for that. Adds 7 test cases covering the user-
reported EDDF sample plus calm/VRB/gust winds, RVR, weather codes,
cloud specials, trend codes, and RMK stripping.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 13:45:15 +02:00
itsrubberduck
47efee2148 feat(atis): carrier-noise bed and Web-Audio loop seek
Tuning to the ATIS frequency now plays carrier noise immediately so the
pilot gets feedback before the TTS comes back (synthesis takes a moment
on cold cache). When the ATIS audio is ready, the carrier ducks down to
a subtle bed level and stays underneath the announcement — mimicking
how a real radio channel always carries some noise floor.

Switches the loop from HTMLAudioElement (whose seek on data: URLs gets
quantized by some browsers) to a Web-Audio AudioBufferSourceNode.
`source.start(0, offset)` is sample-accurate per spec, so the
virtual-clock entry point lands exactly where computed. `window.__atisDebug`
exposes ctx/source/state for manual inspection, and pm.vue logs the
requestedOffset/duration/epochAge on each loop start.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 09:24:31 +02:00