Files
OpenSquawk/shared/utils/silenceTimer.ts
itsrubberduck eb7fd82ad4 fix(live-atc): arm the silence timer on readback states too
The timer was only armed for states carrying auto_advance_on_silence. Readback
states carry none — they wait for the pilot — so nothing was armed and ATC
never asked again when the readback did not arrive.

Which window applies to a state is now a pure function: the flow-authored one
for an auto-advance state, the much shorter server-published one for a readback
state. That also makes both cases testable instead of inline in the composable.
2026-07-27 00:30:59 +02:00

61 lines
2.0 KiB
TypeScript

/**
* Which silence timer, if any, applies to the state the session is resting on.
*
* Two different waits look the same from the outside but mean opposite things:
*
* - **auto-advance** — the pilot owes nothing. The takeoff roll, a climb to the
* cleared level: ATC carries on by itself once enough time has passed, and the
* window is long because nothing is wrong.
*
* - **readback** — the pilot owes a mandatory readback and has not given it. A
* controller chases that within seconds, so the window is short. These states
* carry no `auto_advance_timeout_ms` (they wait for the pilot rather than
* advancing on their own), which is why the timer was never armed for them at
* all and the re-request never fired.
*/
export type SilenceTimerKind = 'auto_advance' | 'readback'
export interface SilenceTimerState {
auto_advance_on_silence?: boolean
auto_advance_timeout_ms?: number
readback_required?: string[]
/** Server-side policy, published on readback states by the runtime tree. */
readback_silence_ms?: number
}
export interface SilenceTimerWindow {
kind: SilenceTimerKind
ms: number
}
/** Never fire faster than this, whatever the flow or server says. */
const MIN_WINDOW_MS = 1000
export const DEFAULT_AUTO_ADVANCE_MS = 30_000
/** Only used when the backend published no window (older server). */
export const DEFAULT_READBACK_SILENCE_MS = 12_000
export function silenceWindowFor(
state: SilenceTimerState | null | undefined,
): SilenceTimerWindow | null {
if (!state) return null
// Auto-advance wins: a state carrying it has an explicit flow-authored window.
if (state.auto_advance_on_silence) {
return {
kind: 'auto_advance',
ms: Math.max(MIN_WINDOW_MS, Number(state.auto_advance_timeout_ms ?? DEFAULT_AUTO_ADVANCE_MS)),
}
}
if (state.readback_required?.length) {
return {
kind: 'readback',
ms: Math.max(MIN_WINDOW_MS, Number(state.readback_silence_ms ?? DEFAULT_READBACK_SILENCE_MS)),
}
}
return null
}