feat(opord): dynamic abort contingency with six options
This commit is contained in:
parent
53dec0b127
commit
d8ee14ee8d
2 changed files with 55 additions and 20 deletions
|
|
@ -135,7 +135,7 @@ function buildBriefingRows(
|
|||
"text",
|
||||
`Movement from the line of departure to the objective takes ${scenario.enRouteMin} minutes.`,
|
||||
);
|
||||
push(rows, "text", `On abort, return to ${scenario.rallyPoint} and await further orders.`);
|
||||
push(rows, "text", `On abort, ${scenario.abortAction}.`);
|
||||
push(rows, "text", `Resupply is scheduled at H+${scenario.resupplyOffsetMin} minutes.`);
|
||||
push(rows, "text", `Exfiltration no later than H+${scenario.exfilOffsetMin} minutes.`);
|
||||
push(
|
||||
|
|
|
|||
|
|
@ -231,6 +231,8 @@ export interface OpordScenario {
|
|||
readonly exfilOffsetMin: number;
|
||||
/** Egress azimuth in degrees (1-359); never equals the main axis azimuth. */
|
||||
readonly withdrawalAzimuth: number;
|
||||
/** Element action on abort; one of the abortActionBank templates. */
|
||||
readonly abortAction: string;
|
||||
}
|
||||
|
||||
/** Formats a 6-digit grid Arma map style: three digits, hyphen, three digits, e.g. "034-672". */
|
||||
|
|
@ -243,6 +245,25 @@ export function cardinalFromAzimuth(azimuth: number): string {
|
|||
return CARDINAL_DIRECTIONS[Math.round(azimuth / 45) % 8];
|
||||
}
|
||||
|
||||
// Shared by scenario generation (stores one entry as abortAction) and question
|
||||
// building (offers the rest as distractors); every template reads only fields
|
||||
// the elite briefing renders.
|
||||
function abortActionBank(
|
||||
scenario: Pick<
|
||||
OpordScenario,
|
||||
"rallyPoint" | "ldPhaseLine" | "otherPhaseLines" | "withdrawalAzimuth" | "casevacLz" | "friendlyUnit"
|
||||
>,
|
||||
): readonly string[] {
|
||||
return [
|
||||
`Return to ${scenario.rallyPoint} and await further orders`,
|
||||
`Retire to phase line ${scenario.otherPhaseLines[1]} and hold`,
|
||||
`Withdraw on azimuth ${pad3(scenario.withdrawalAzimuth)} to ${scenario.rallyPoint}`,
|
||||
`Regroup at ${scenario.casevacLz} and await transport`,
|
||||
`Fall back to ${scenario.friendlyUnit} and consolidate`,
|
||||
`Hold short of phase line ${scenario.ldPhaseLine} and await orders`,
|
||||
];
|
||||
}
|
||||
|
||||
export function generateOpordScenario(seed: number): OpordScenario {
|
||||
const rng = mulberry32(seed);
|
||||
const operationName = `${pick(rng, OPERATION_ADJECTIVES)} ${pick(rng, OPERATION_NOUNS)}`;
|
||||
|
|
@ -264,6 +285,23 @@ export function generateOpordScenario(seed: number): OpordScenario {
|
|||
// azimuth and its cardinal direction, so the two questions never collide.
|
||||
const wrappedWithdrawal = (azimuth + pick(rng, [120, 150, 180, 210])) % 360;
|
||||
const withdrawalAzimuth = wrappedWithdrawal === 0 ? 359 : wrappedWithdrawal;
|
||||
const enemyVehicles = pickInt(rng, 1, 4);
|
||||
const enemySquads = pickInt(rng, 1, 3);
|
||||
const enemyMortars = pickInt(rng, 1, 3);
|
||||
const friendlyUnit = pick(rng, FRIENDLY_UNITS);
|
||||
const transport = pick(rng, TRANSPORT_OPTIONS);
|
||||
const rallyPoint = `RALLY POINT ${pick(rng, RALLY_POINT_NAMES)}`;
|
||||
const aircraftCallsign = `${pick(rng, AIRCRAFT_CALLSIGNS)} ${pick(rng, [1, 2, 3, 4])}`;
|
||||
const airSupportOffsetMin = pick(rng, [15, 20, 30, 45]);
|
||||
const enRouteMin = pickInt(rng, 18, 42);
|
||||
const checkInMinutes = pick(rng, [10, 15, 20, 30]);
|
||||
const casevacLz = `LZ ${pick(rng, LZ_NAMES)}`;
|
||||
const ldPhaseLine = phaseLines[0];
|
||||
const otherPhaseLines = phaseLines.slice(1, 3);
|
||||
const abortAction = pick(
|
||||
rng,
|
||||
abortActionBank({ rallyPoint, ldPhaseLine, otherPhaseLines, withdrawalAzimuth, casevacLz, friendlyUnit }),
|
||||
);
|
||||
|
||||
return {
|
||||
seed,
|
||||
|
|
@ -273,26 +311,27 @@ export function generateOpordScenario(seed: number): OpordScenario {
|
|||
azimuth,
|
||||
distanceKm,
|
||||
hHour,
|
||||
ldPhaseLine: phaseLines[0],
|
||||
otherPhaseLines: phaseLines.slice(1, 3),
|
||||
ldPhaseLine,
|
||||
otherPhaseLines,
|
||||
codeword,
|
||||
abortCodeword,
|
||||
callsign,
|
||||
frequency,
|
||||
enemyVehicles: pickInt(rng, 1, 4),
|
||||
enemySquads: pickInt(rng, 1, 3),
|
||||
enemyMortars: pickInt(rng, 1, 3),
|
||||
friendlyUnit: pick(rng, FRIENDLY_UNITS),
|
||||
transport: pick(rng, TRANSPORT_OPTIONS),
|
||||
rallyPoint: `RALLY POINT ${pick(rng, RALLY_POINT_NAMES)}`,
|
||||
aircraftCallsign: `${pick(rng, AIRCRAFT_CALLSIGNS)} ${pick(rng, [1, 2, 3, 4])}`,
|
||||
airSupportOffsetMin: pick(rng, [15, 20, 30, 45]),
|
||||
enRouteMin: pickInt(rng, 18, 42),
|
||||
checkInMinutes: pick(rng, [10, 15, 20, 30]),
|
||||
casevacLz: `LZ ${pick(rng, LZ_NAMES)}`,
|
||||
enemyVehicles,
|
||||
enemySquads,
|
||||
enemyMortars,
|
||||
friendlyUnit,
|
||||
transport,
|
||||
rallyPoint,
|
||||
aircraftCallsign,
|
||||
airSupportOffsetMin,
|
||||
enRouteMin,
|
||||
checkInMinutes,
|
||||
casevacLz,
|
||||
resupplyOffsetMin,
|
||||
exfilOffsetMin,
|
||||
withdrawalAzimuth,
|
||||
abortAction,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -569,12 +608,8 @@ export function buildOpordQuestions(
|
|||
choiceQuestion(
|
||||
"abort-rally",
|
||||
`If the abort codeword "${scenario.abortCodeword}" is broadcast, what does your element do?`,
|
||||
`Return to ${scenario.rallyPoint} and await further orders`,
|
||||
[
|
||||
"Continue the mission as planned",
|
||||
"Mark the objective with smoke and hold in place",
|
||||
`Request close air support from ${scenario.aircraftCallsign}`,
|
||||
],
|
||||
scenario.abortAction,
|
||||
abortActionBank(scenario).filter((action) => action !== scenario.abortAction),
|
||||
rng,
|
||||
),
|
||||
"callsign-net": () =>
|
||||
|
|
|
|||
Loading…
Reference in a new issue