1
0
Fork 0

feat(opord): dynamic abort contingency with six options

This commit is contained in:
Jason Fraley 2026-09-08 12:39:57 -04:00
parent 53dec0b127
commit d8ee14ee8d
2 changed files with 55 additions and 20 deletions

View file

@ -135,7 +135,7 @@ function buildBriefingRows(
"text", "text",
`Movement from the line of departure to the objective takes ${scenario.enRouteMin} minutes.`, `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", `Resupply is scheduled at H+${scenario.resupplyOffsetMin} minutes.`);
push(rows, "text", `Exfiltration no later than H+${scenario.exfilOffsetMin} minutes.`); push(rows, "text", `Exfiltration no later than H+${scenario.exfilOffsetMin} minutes.`);
push( push(

View file

@ -231,6 +231,8 @@ export interface OpordScenario {
readonly exfilOffsetMin: number; readonly exfilOffsetMin: number;
/** Egress azimuth in degrees (1-359); never equals the main axis azimuth. */ /** Egress azimuth in degrees (1-359); never equals the main axis azimuth. */
readonly withdrawalAzimuth: number; 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". */ /** 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]; 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 { export function generateOpordScenario(seed: number): OpordScenario {
const rng = mulberry32(seed); const rng = mulberry32(seed);
const operationName = `${pick(rng, OPERATION_ADJECTIVES)} ${pick(rng, OPERATION_NOUNS)}`; 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. // azimuth and its cardinal direction, so the two questions never collide.
const wrappedWithdrawal = (azimuth + pick(rng, [120, 150, 180, 210])) % 360; const wrappedWithdrawal = (azimuth + pick(rng, [120, 150, 180, 210])) % 360;
const withdrawalAzimuth = wrappedWithdrawal === 0 ? 359 : wrappedWithdrawal; 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 { return {
seed, seed,
@ -273,26 +311,27 @@ export function generateOpordScenario(seed: number): OpordScenario {
azimuth, azimuth,
distanceKm, distanceKm,
hHour, hHour,
ldPhaseLine: phaseLines[0], ldPhaseLine,
otherPhaseLines: phaseLines.slice(1, 3), otherPhaseLines,
codeword, codeword,
abortCodeword, abortCodeword,
callsign, callsign,
frequency, frequency,
enemyVehicles: pickInt(rng, 1, 4), enemyVehicles,
enemySquads: pickInt(rng, 1, 3), enemySquads,
enemyMortars: pickInt(rng, 1, 3), enemyMortars,
friendlyUnit: pick(rng, FRIENDLY_UNITS), friendlyUnit,
transport: pick(rng, TRANSPORT_OPTIONS), transport,
rallyPoint: `RALLY POINT ${pick(rng, RALLY_POINT_NAMES)}`, rallyPoint,
aircraftCallsign: `${pick(rng, AIRCRAFT_CALLSIGNS)} ${pick(rng, [1, 2, 3, 4])}`, aircraftCallsign,
airSupportOffsetMin: pick(rng, [15, 20, 30, 45]), airSupportOffsetMin,
enRouteMin: pickInt(rng, 18, 42), enRouteMin,
checkInMinutes: pick(rng, [10, 15, 20, 30]), checkInMinutes,
casevacLz: `LZ ${pick(rng, LZ_NAMES)}`, casevacLz,
resupplyOffsetMin, resupplyOffsetMin,
exfilOffsetMin, exfilOffsetMin,
withdrawalAzimuth, withdrawalAzimuth,
abortAction,
}; };
} }
@ -569,12 +608,8 @@ export function buildOpordQuestions(
choiceQuestion( choiceQuestion(
"abort-rally", "abort-rally",
`If the abort codeword "${scenario.abortCodeword}" is broadcast, what does your element do?`, `If the abort codeword "${scenario.abortCodeword}" is broadcast, what does your element do?`,
`Return to ${scenario.rallyPoint} and await further orders`, scenario.abortAction,
[ abortActionBank(scenario).filter((action) => action !== scenario.abortAction),
"Continue the mission as planned",
"Mark the objective with smoke and hold in place",
`Request close air support from ${scenario.aircraftCallsign}`,
],
rng, rng,
), ),
"callsign-net": () => "callsign-net": () =>