mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-02 14:56:16 +08:00
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.
61 lines
2.0 KiB
TypeScript
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
|
|
}
|