From 4b07609c525e9644b08881886c3be56504abadde Mon Sep 17 00:00:00 2001 From: leubeem Date: Mon, 29 Jun 2026 10:17:52 +0200 Subject: [PATCH] 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 --- app/pages/pm.vue | 15 +++++++++++++++ shared/utils/radioSpeech.ts | 7 +++++++ 2 files changed, 22 insertions(+) diff --git a/app/pages/pm.vue b/app/pages/pm.vue index f1674d7..b35a16e 100644 --- a/app/pages/pm.vue +++ b/app/pages/pm.vue @@ -3538,6 +3538,21 @@ const startMonitoring = async (flightPlan: any, scenario: Scenario) => { approach_freq: v.approach_freq || '119.000', handoff_freq: v.handoff_freq || '131.150', } + + // VFR flights identify by aircraft registration, not an airline callsign. + // Assign a German D-registration and its abbreviated form (D-EMIL -> D-IL, + // first letter + last two), which ATC uses after the first call. Mirror it + // into the local engine vars and HUD so the display matches the radio. + if (scenario.startFlow.startsWith('vfr')) { + const pool = ['D-EMIL', 'D-EKLM', 'D-ENNY', 'D-ELLA', 'D-EOMT', 'D-ELPC', 'D-EMTO', 'D-EBRA'] + const reg = pool[Math.floor(Math.random() * pool.length)] + const short = `D-${reg.replace(/^D-/, '').slice(-2)}` + backendVariables.callsign = reg + backendVariables.callsign_short = short + patchVariables({ callsign: reg, callsign_short: short }) + } else { + backendVariables.callsign_short = backendVariables.callsign + } pmLog.info('backend variables payload:', backendVariables) // 4. Create a backend session with the flight-plan-derived variables diff --git a/shared/utils/radioSpeech.ts b/shared/utils/radioSpeech.ts index 7fdba52..039b2ad 100644 --- a/shared/utils/radioSpeech.ts +++ b/shared/utils/radioSpeech.ts @@ -741,6 +741,13 @@ export function normalizeRadioPhrase(text: string, options: NormalizeRadioOption out = out.replace(/\b(wind\s+)(\d{3})\/(\d{2,3})(?:KT)?\b/gi, (_m, prefix: string, dir: string, spd: string) => `${prefix}${spellIcaoDigits(dir)} degrees, ${spellIcaoDigits(spd)} knots`); + // Aircraft registration callsign: "D-EMIL" → "Delta Echo Mike India Lima", + // abbreviated "D-IL" → "Delta India Lima". Single prefix letter, hyphen, then + // 1–5 registration letters, each spelled phonetically. Runs before airport/ + // callsign expansion so the registration body isn't read as an ICAO code. + out = out.replace(/\b([A-Z])-([A-Z]{1,5})\b/g, (_m, prefix: string, body: string) => + spellIcaoLetters(prefix + body)); + if (opts.sidSuffixIcao) { out = out.replace(/\b([A-Z]{4,6})\s*(\d)\s*([A-Z])\b/g, (_match, prefix: string, digit: string, letter: string) => { return sidSuffixSpeak(prefix, digit, letter);