mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-06 09:16:26 +08:00
feat(live-atc): teach the rare events on demand
A rejected take-off and a go-around both happen by chance about once in five hundred flights, which is the point of them — you cannot practise reacting to something you were told to expect. That also made them impossible to teach. Two switches under Special scenarios force either on the next flight. They are read when the session is created rather than watched, so flipping one mid-flight applies to the following one — which is how an instructor sets it up without the pilot seeing it coming. Only a switched-on value is sent: the backend reads the variable's absence as "leave it to chance", so sending false would pin the roll off instead of leaving it alone. Styled amber rather than the usual cyan, and the consequence is spelled out while either is armed: neither scenario continues to the stand.
This commit is contained in:
@@ -21,6 +21,8 @@ const prerecEnabled = defineModel<boolean>('prerecEnabled', { required: true })
|
||||
const prerecSeconds = defineModel<number>('prerecSeconds', { required: true })
|
||||
const aiTrafficEnabled = defineModel<boolean>('aiTrafficEnabled', { required: true })
|
||||
const autoTuneEnabled = defineModel<boolean>('autoTuneEnabled', { required: true })
|
||||
const forceRto = defineModel<boolean>('forceRto', { required: true })
|
||||
const forceGoAround = defineModel<boolean>('forceGoAround', { required: true })
|
||||
|
||||
/**
|
||||
* Shown while AI traffic is on. These are deliberate v1 boundaries from the
|
||||
@@ -170,6 +172,42 @@ const AI_TRAFFIC_LIMITS = [
|
||||
</v-expand-transition>
|
||||
</div>
|
||||
|
||||
<!-- Both of these happen on their own roughly once in five hundred
|
||||
flights, which is the point of them — you cannot practise reacting
|
||||
to something you were told to expect. These switches are for
|
||||
teaching them deliberately, so they apply to the NEXT flight rather
|
||||
than the one in progress. -->
|
||||
<div class="pt-2 border-t border-white/10 space-y-3">
|
||||
<div>
|
||||
<p class="text-xs uppercase tracking-[0.3em] text-white/40">Special scenarios</p>
|
||||
<p class="text-[11px] text-white/50 mt-1">
|
||||
Force a rare event on the next flight instead of waiting for it. Both
|
||||
otherwise occur by chance, about once in five hundred flights.
|
||||
</p>
|
||||
</div>
|
||||
<v-switch
|
||||
v-model="forceRto"
|
||||
color="amber"
|
||||
inset
|
||||
label="Rejected take-off (Tower cancels)"
|
||||
hide-details
|
||||
/>
|
||||
<v-switch
|
||||
v-model="forceGoAround"
|
||||
color="amber"
|
||||
inset
|
||||
label="Go-around (Tower breaks off the approach)"
|
||||
hide-details
|
||||
/>
|
||||
<v-expand-transition>
|
||||
<p v-if="forceRto || forceGoAround" class="text-[11px] text-amber-300/80">
|
||||
Applies when you start the next flight. A rejected take-off ends with
|
||||
the aircraft stopped on the runway; a go-around sends you back to
|
||||
Approach — neither continues to the stand.
|
||||
</p>
|
||||
</v-expand-transition>
|
||||
</div>
|
||||
|
||||
<div class="pt-2 border-t border-white/10 space-y-3">
|
||||
<div>
|
||||
<p class="text-xs uppercase tracking-[0.3em] text-white/40">Voice input</p>
|
||||
|
||||
@@ -36,6 +36,13 @@ export interface LiveAtcSessionDeps {
|
||||
isRecording: Ref<boolean>
|
||||
/** Settings toggle: dial in the new frequency automatically after a handoff. */
|
||||
autoTuneEnabled: Ref<boolean>
|
||||
/**
|
||||
* Special-scenario switches. Read once when the session is created, so
|
||||
* toggling one mid-flight applies to the next flight — which is how an
|
||||
* instructor sets one up without the pilot seeing it coming.
|
||||
*/
|
||||
forceRto: Ref<boolean>
|
||||
forceGoAround: Ref<boolean>
|
||||
bridgeConnected: Ref<boolean>
|
||||
bridgePosition: Ref<{ lat: number; lon: number } | null>
|
||||
/** ?token=… from the route — auths the frequency-sim-control channel (design §4). */
|
||||
@@ -65,7 +72,8 @@ export function useLiveAtcSession(
|
||||
|
||||
const {
|
||||
state, freq, speech, radioBackend, api, config, prefetchAtisAudio,
|
||||
isRecording, autoTuneEnabled, bridgeConnected, bridgePosition, bridgeToken,
|
||||
isRecording, autoTuneEnabled, forceRto, forceGoAround,
|
||||
bridgeConnected, bridgePosition, bridgeToken,
|
||||
persistSelectedPlan, maybeShowFirstRunHelp,
|
||||
} = deps
|
||||
|
||||
@@ -696,6 +704,12 @@ export function useLiveAtcSession(
|
||||
handoff_freq: v.handoff_freq || '131.150',
|
||||
}
|
||||
|
||||
// Special scenarios. Only sent when switched on: the backend treats the
|
||||
// variable's absence as "leave it to chance", and sending false would pin
|
||||
// the roll off rather than leaving it alone.
|
||||
if (forceRto.value) backendVariables.force_rto = true
|
||||
if (forceGoAround.value) backendVariables.force_go_around = true
|
||||
|
||||
// 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
|
||||
|
||||
@@ -142,6 +142,8 @@
|
||||
v-model:prerec-seconds="prerecSeconds"
|
||||
v-model:ai-traffic-enabled="aiTrafficEnabled"
|
||||
v-model:auto-tune-enabled="autoTuneEnabled"
|
||||
v-model:force-rto="forceRto"
|
||||
v-model:force-go-around="forceGoAround"
|
||||
@set-theme="setPmTheme"
|
||||
/>
|
||||
|
||||
@@ -321,6 +323,11 @@ const aiTrafficEnabled = ref(false)
|
||||
// working the radio is part of what is being practised, so turning it over to
|
||||
// the aircraft has to be a deliberate choice.
|
||||
const autoTuneEnabled = ref(false)
|
||||
// Special scenarios: force a rare intervention on the next flight rather than
|
||||
// waiting for its ~0.2% chance. Read when the session is created, so toggling
|
||||
// mid-flight affects the next one — which is also how you teach them.
|
||||
const forceRto = ref(false)
|
||||
const forceGoAround = ref(false)
|
||||
|
||||
// ── Bug Report ───────────────────────────────────────────────────────────────
|
||||
// Owned here rather than by the dialog: the HUD button starts the screenshot
|
||||
@@ -599,6 +606,8 @@ const session = useLiveAtcSession(engine, {
|
||||
prefetchAtisAudio,
|
||||
isRecording,
|
||||
autoTuneEnabled,
|
||||
forceRto,
|
||||
forceGoAround,
|
||||
bridgeConnected,
|
||||
bridgePosition,
|
||||
bridgeToken,
|
||||
|
||||
Reference in New Issue
Block a user