mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-04 16:22:48 +08:00
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>
This commit is contained in:
@@ -602,7 +602,7 @@
|
||||
<ClientOnly>
|
||||
<div
|
||||
class="ptt-pad flex h-52 lg:h-60 items-center justify-center rounded-2xl border text-center transition cursor-pointer"
|
||||
:class="isRecording ? 'border-red-400/40 ring-4 ring-red-400/40 bg-red-500/10' : 'border-white/10 ring-1 ring-white/5 bg-black/40'"
|
||||
:class="isRecording ? 'border-green-400/40 ring-4 ring-green-400/40 bg-green-500/10' : 'border-white/10 ring-1 ring-white/5 bg-black/40'"
|
||||
@touchstart.prevent="startRecording(false)"
|
||||
@touchend.prevent="stopRecording"
|
||||
@touchcancel.prevent="stopRecording"
|
||||
@@ -614,22 +614,22 @@
|
||||
<v-icon
|
||||
:icon="isRecording ? 'mdi-access-point' : 'mdi-radio-handheld'"
|
||||
size="44"
|
||||
:class="isRecording ? 'text-red-400 animate-pulse' : 'text-cyan-300'"
|
||||
:class="isRecording ? 'text-green-400 animate-pulse' : 'text-cyan-300'"
|
||||
/>
|
||||
<p
|
||||
class="text-[11px] uppercase tracking-[0.35em] mt-1"
|
||||
:class="isRecording ? 'text-red-300' : 'text-white/40'"
|
||||
:class="isRecording ? 'text-green-300' : 'text-white/40'"
|
||||
>
|
||||
{{ isRecording ? 'Transmitting' : 'Hold to transmit' }}
|
||||
</p>
|
||||
<p
|
||||
v-if="bridgePttConnected"
|
||||
class="text-[10px] uppercase tracking-[0.25em] flex items-center justify-center gap-1.5"
|
||||
:class="isRecording ? 'text-red-300' : 'text-cyan-300/70'"
|
||||
:class="isRecording ? 'text-green-300' : 'text-cyan-300/70'"
|
||||
>
|
||||
<span
|
||||
class="inline-block h-1.5 w-1.5 rounded-full"
|
||||
:class="isRecording ? 'bg-red-400 animate-pulse' : 'bg-cyan-300/60'"
|
||||
:class="isRecording ? 'bg-green-400 animate-pulse' : 'bg-cyan-300/60'"
|
||||
/>
|
||||
{{ isRecording ? 'Hotkey transmitting' : 'Hotkey armed' }}
|
||||
</p>
|
||||
@@ -3199,7 +3199,10 @@ const speakPilotReadback = (text: string) => {
|
||||
|
||||
const handlePilotTransmission = async (message: string, source: 'text' | 'ptt' = 'text') => {
|
||||
const transcript = message.trim()
|
||||
if (!transcript) return
|
||||
// Ignore empty or content-free transmissions: silence, a stray PTT tap, or
|
||||
// Whisper hallucinating punctuation on near-silent audio. A genuine short call
|
||||
// ("roger", "wilco") still contains letters/digits and passes.
|
||||
if (!transcript || !/[a-z0-9]/i.test(transcript)) return
|
||||
|
||||
const prefix = source === 'ptt' ? 'Pilot (PTT)' : 'Pilot'
|
||||
setLastTransmission(`${prefix}: ${transcript}`)
|
||||
@@ -3499,11 +3502,9 @@ const startMonitoring = async (flightPlan: any, scenario: Scenario) => {
|
||||
currentScreen.value = 'monitor'
|
||||
persistSelectedPlan(flightPlan)
|
||||
|
||||
// Start every scenario from a known baseline frequency. Tuning to the first
|
||||
// controller is part of the exercise: the pilot must dial the correct
|
||||
// frequency themselves, and the first call is rejected (telling them which
|
||||
// frequency to switch to) until they are on it. We deliberately do NOT
|
||||
// auto-tune to the expected frequency here.
|
||||
// Start from a known baseline; the first controller's frequency is tuned in
|
||||
// automatically below, once the initial state-walk tells us which pilot state
|
||||
// the scenario opens on.
|
||||
frequencies.value.active = '121.900'
|
||||
frequencies.value.standby = '118.100'
|
||||
|
||||
@@ -3519,6 +3520,17 @@ const startMonitoring = async (flightPlan: any, scenario: Scenario) => {
|
||||
} catch (err) {
|
||||
console.warn('Initial state advance failed:', err)
|
||||
}
|
||||
|
||||
// Pre-tune COM1 active to the frequency the opening pilot call is expected on,
|
||||
// so the very first transmission isn't met with a confusing 'wrong frequency'
|
||||
// rejection. Later handoffs stay manual — tuning to the next controller
|
||||
// remains part of the exercise.
|
||||
await nextTick()
|
||||
const firstFreq = expectedFrequencyForState()
|
||||
if (firstFreq) {
|
||||
frequencies.value.active = firstFreq
|
||||
pmLog.info('Pre-tuned COM1 active to first frequency:', firstFreq)
|
||||
}
|
||||
}
|
||||
|
||||
const startDemoFlight = () => {
|
||||
@@ -3745,6 +3757,10 @@ const startRecording = async (isIntercom = false) => {
|
||||
return
|
||||
}
|
||||
|
||||
// Barge-in: keying the mic cuts any ATC speech still playing, mirroring a real
|
||||
// half-duplex radio where transmitting overrides the controller's output.
|
||||
stopCurrentSpeech()
|
||||
|
||||
// Pre-recording path: ring buffer + active capture, encoded to WAV on release
|
||||
if (prerecEnabled.value) {
|
||||
if (!prerecCtx) {
|
||||
|
||||
@@ -226,11 +226,11 @@ function freqSpeak(raw: string): string {
|
||||
return `${leftSpoken} decimal ${rightSpoken}`;
|
||||
}
|
||||
|
||||
const VIA_TAXI_ROUTE_PATTERN = /\b((?:expect\s+taxi\s+)?via\s+)([A-Z0-9\s/\-]+?)(?=(?:,|\s+(?:hold short|cross|then|contact|monitor|with|for|to|left|right)\b|\.)|$)/gi;
|
||||
const VIA_TAXI_ROUTE_PATTERN = /\b((?:expect\s+taxi\s+)?via\s+)([A-Z0-9\s/,\-]+?)(?=\s*,?\s*(?:hold short|cross|then|contact|monitor|with|for|to|left|right)\b|\s*\.|$)/gi;
|
||||
|
||||
const TAXI_ROUTE_LABEL_PATTERN = /\b(taxi(?:-?in)?\s+route[:\s]+)([A-Z0-9\s/\-]+?)(?=(?:,|\s+(?:hold short|cross|then|contact|monitor|with|for|to|left|right)\b|\.)|$)/gi;
|
||||
const TAXI_ROUTE_LABEL_PATTERN = /\b(taxi(?:-?in)?\s+route[:\s]+)([A-Z0-9\s/,\-]+?)(?=\s*,?\s*(?:hold short|cross|then|contact|monitor|with|for|to|left|right)\b|\s*\.|$)/gi;
|
||||
|
||||
const STAND_ROUTE_PATTERN = /\b(taxi\s+to\s+stand\s+[A-Z0-9]+\s+via\s+)([A-Z0-9\s/\-]+?)(?=(?:,|\s+(?:hold short|cross|then|contact|monitor|with|for|to|left|right)\b|\.)|$)/gi;
|
||||
const STAND_ROUTE_PATTERN = /\b(taxi\s+to\s+stand\s+[A-Z0-9]+\s+via\s+)([A-Z0-9\s/,\-]+?)(?=\s*,?\s*(?:hold short|cross|then|contact|monitor|with|for|to|left|right)\b|\s*\.|$)/gi;
|
||||
|
||||
const TAXI_SEGMENT_SINGLE = /^[A-Z]{1,2}$/;
|
||||
const TAXI_SEGMENT_WITH_DIGITS = /^[A-Z]{1,3}\d{1,3}$/;
|
||||
|
||||
Reference in New Issue
Block a user