From 7d49f18cc64a975fa260a0b32650ec640bd51281 Mon Sep 17 00:00:00 2001 From: itsrubberduck Date: Thu, 16 Jul 2026 17:46:52 +0200 Subject: [PATCH] feat(live-atc): add simulated AI background traffic on the tuned frequency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../live-atc/cockpit/SettingsSheet.vue | 69 +++ app/composables/useAiTraffic.ts | 456 +++++++++++++++ app/composables/useLiveAtcSession.ts | 14 +- app/composables/useRadioSpeech.ts | 31 +- app/composables/useSessionState.ts | 4 + app/pages/live-atc.vue | 54 ++ shared/data/simAircraftTypes.ts | 57 ++ shared/data/trafficTiers.ts | 102 ++++ shared/utils/aiTraffic/callsign.ts | 194 +++++++ shared/utils/aiTraffic/gating.ts | 99 ++++ shared/utils/aiTraffic/instructions.ts | 549 ++++++++++++++++++ shared/utils/aiTraffic/rng.ts | 72 +++ shared/utils/aiTraffic/separation.ts | 137 +++++ shared/utils/aiTraffic/sim.ts | 213 +++++++ shared/utils/aiTraffic/types.ts | 50 ++ shared/utils/communicationsEngine.ts | 9 +- shared/utils/voicePool.ts | 68 +++ tests/shared/aiTrafficCallsign.test.ts | 250 ++++++++ tests/shared/aiTrafficGating.test.ts | 205 +++++++ tests/shared/aiTrafficInstructions.test.ts | 424 ++++++++++++++ tests/shared/aiTrafficIntegration.test.ts | 271 +++++++++ tests/shared/aiTrafficSeparation.test.ts | 193 ++++++ tests/shared/aiTrafficSim.test.ts | 257 ++++++++ tests/shared/trafficTiers.test.ts | 104 ++++ tests/shared/voicePool.test.ts | 89 +++ 25 files changed, 3968 insertions(+), 3 deletions(-) create mode 100644 app/composables/useAiTraffic.ts create mode 100644 shared/data/simAircraftTypes.ts create mode 100644 shared/data/trafficTiers.ts create mode 100644 shared/utils/aiTraffic/callsign.ts create mode 100644 shared/utils/aiTraffic/gating.ts create mode 100644 shared/utils/aiTraffic/instructions.ts create mode 100644 shared/utils/aiTraffic/rng.ts create mode 100644 shared/utils/aiTraffic/separation.ts create mode 100644 shared/utils/aiTraffic/sim.ts create mode 100644 shared/utils/aiTraffic/types.ts create mode 100644 shared/utils/voicePool.ts create mode 100644 tests/shared/aiTrafficCallsign.test.ts create mode 100644 tests/shared/aiTrafficGating.test.ts create mode 100644 tests/shared/aiTrafficInstructions.test.ts create mode 100644 tests/shared/aiTrafficIntegration.test.ts create mode 100644 tests/shared/aiTrafficSeparation.test.ts create mode 100644 tests/shared/aiTrafficSim.test.ts create mode 100644 tests/shared/trafficTiers.test.ts create mode 100644 tests/shared/voicePool.test.ts diff --git a/app/components/live-atc/cockpit/SettingsSheet.vue b/app/components/live-atc/cockpit/SettingsSheet.vue index 8260d42..973051a 100644 --- a/app/components/live-atc/cockpit/SettingsSheet.vue +++ b/app/components/live-atc/cockpit/SettingsSheet.vue @@ -19,6 +19,20 @@ const learningMode = defineModel('learningMode', { required: true }) const debugMode = defineModel('debugMode', { required: true }) const prerecEnabled = defineModel('prerecEnabled', { required: true }) const prerecSeconds = defineModel('prerecSeconds', { required: true }) +const aiTrafficEnabled = defineModel('aiTrafficEnabled', { required: true }) + +/** + * Shown while AI traffic is on. These are deliberate v1 boundaries from the + * architecture design, not bugs — stating them up front is cheaper than having + * someone discover them mid-session and file them as defects. + */ +const AI_TRAFFIC_LIMITS = [ + 'Background scenery only — it never affects how your own radio work is scored.', + 'Audible on the tuned frequency only. Other frequencies stay busy, just silent.', + 'Never transmits while you hold PTT, while ATC is answering you, or in the ~12s after an instruction.', + 'Fixed phraseology, no AI text generation. Invented waypoint names, not real SIDs/STARs.', + 'Traffic never conflicts with you: it always yields, and you get no extra instructions from it.', +]