Support Sapho ordering at Testing Station
Verify and publish checkpoint container / container (push) Successful in 10m32s

This commit is contained in:
Quintine committed 2026-10-29 09:24:16 +11:00
1 parent 220d78c779
commit 1972622c11
11 files changed
+445 -9

No files matched your search

+63 -2
View File
@@ -30,6 +30,14 @@ export type DiscoveryStorm = {
order: string[];
choices: { owner: string; delta: -1 | 0 | 1 }[];
};
/** One provisional Sapho reorder, bound to the original physical order. */
sapho?: {
event: string;
nextEvent: string;
originalOrder: string[];
holder: string;
mode: 'first' | 'last';
};
};
export type DiscoveryStormContext = Pick<
Game,
@@ -85,6 +93,7 @@ export function discoveryStormSignature(
frame.stage,
];
if (frame.queue) values.push(frame.queue.order, frame.queue.choices);
if (frame.sapho) values.push(frame.sapho);
return JSON.stringify(values);
}
export function discoveryStormDistance(frame: DiscoveryStorm): number {
@@ -164,7 +173,9 @@ export function validateDiscoveryStorm(
need(
record(frame) &&
Object.keys(frame).sort().join(',') === (frame.queue
? 'base,delta,event,from,owner,queue,signature,stage,token,turn'
? (frame.sapho
? 'base,delta,event,from,owner,queue,sapho,signature,stage,token,turn'
: 'base,delta,event,from,owner,queue,signature,stage,token,turn')
: 'base,delta,event,from,owner,signature,stage,token,turn') &&
typeof frame.event === 'string' &&
frame.event.length > 0 &&
@@ -211,6 +222,20 @@ export function validateDiscoveryStorm(
distance += choice.delta;
need(distance >= 0 && distance <= 40, 'Every station choice must retain a legal current distance.');
}
if (frame.sapho) {
const sapho = frame.sapho;
need(record(sapho) && Object.keys(sapho).sort().join(',') === 'event,holder,mode,nextEvent,originalOrder' &&
typeof sapho.event === 'string' && sapho.event.length > 0 &&
typeof sapho.nextEvent === 'string' && sapho.nextEvent.length > 0 && sapho.nextEvent !== sapho.event &&
Array.isArray(sapho.originalOrder) && sapho.originalOrder.length === 2 &&
new Set(sapho.originalOrder).size === 2 &&
sapho.originalOrder.every(id => typeof id === 'string' && g.players.some(p => p.id === id)) &&
sapho.originalOrder.includes(sapho.holder) && ['first', 'last'].includes(sapho.mode) &&
(sapho.mode === 'first' ? sapho.originalOrder[1] === sapho.holder : sapho.originalOrder[0] === sapho.holder) &&
JSON.stringify(queue.order) === JSON.stringify([...sapho.originalOrder].reverse()) &&
frame.event === (queue.choices.length ? `${sapho.nextEvent}:1` : sapho.nextEvent),
'The station must retain the original Sapho ordering and event.');
}
}
need(
discoveryStormDistance(frame) >= 0 &&
@@ -250,7 +275,7 @@ export function validateDiscoveryStorm(
if (frame.stage === 'choose') {
const occupants = stationOccupants(g);
need(
JSON.stringify(occupants) === JSON.stringify(frame.queue?.order ?? [frame.owner]) &&
JSON.stringify(occupants) === JSON.stringify(frame.sapho?.originalOrder ?? frame.queue?.order ?? [frame.owner]) &&
g.stormPending === discoveryStormDistance(frame),
'The station choice no longer belongs to its original occupant and storm.',
);
@@ -260,6 +285,42 @@ export function validateDiscoveryStorm(
'The adjusted storm must retain the chosen distance.',
);
}
/** Available position changes before either original station occupant chooses.
* Card ownership, disposal and any external interrupt belong to the caller. */
export function discoveryStormSaphoModes(
g: DiscoveryStormContext,
frame: DiscoveryStorm,
holder: string,
): Array<'first' | 'last'> {
validateDiscoveryStorm(g, frame);
if (frame.stage !== 'choose' || !frame.queue || frame.queue.choices.length || frame.sapho) return [];
const index = frame.queue.order.indexOf(holder);
return index === 0 ? ['last'] : index === 1 ? ['first'] : [];
}
/** Bind a single meaningful reorder and invalidate all old station actions. */
export function reorderDiscoveryStorm(
g: DiscoveryStormContext,
frame: DiscoveryStorm,
input: {holder: string; event: unknown; mode: 'first' | 'last'; nextEvent: string},
): DiscoveryStorm {
const modes = discoveryStormSaphoModes(g, frame, input.holder);
need(input.event === frame.event && modes.includes(input.mode) &&
typeof input.nextEvent === 'string' && input.nextEvent.length > 0 && input.nextEvent !== frame.event,
'Choose an available Sapho ordering for the current station event.');
const originalOrder = [...frame.queue!.order];
const order = [...originalOrder].reverse();
const next: DiscoveryStorm = {
...frame, event: input.nextEvent, owner: order[0],
queue: {order, choices: []},
sapho: {event: frame.event, nextEvent: input.nextEvent, originalOrder, holder: input.holder, mode: input.mode},
signature: '',
};
next.signature = discoveryStormSignature(next);
validateDiscoveryStorm(g, next);
return next;
}
export function chooseDiscoveryStorm(
g: DiscoveryStormContext,
frame: DiscoveryStorm,