Files
Dune/game/sapho-arrival-order.ts
T
Quintine e457478f7d
Verify and publish checkpoint container / container (push) Canceled after 0s
Support Sapho ordering for shared arrivals
2026-10-29 05:29:51 +11:00

89 lines
3.7 KiB
TypeScript

import {
createSaphoTruthOrder,
passSaphoTruthOrder,
quoteSaphoTruthOrder,
reorderSaphoTruthOrder,
SaphoTruthOrderError,
validateSaphoTruthOrder,
type SaphoTruthOrderFrame,
type SaphoTruthOrderMode,
type SaphoTruthOrderRequest,
type SaphoTruthOrderResult,
} from './sapho-truth-order';
/** Public owner identities only: no card, token face or hidden choice is stored. */
export type SaphoArrivalOrder = {
frame: SaphoTruthOrderFrame;
queue: [{ player: string }, { player: string }];
};
export type SaphoArrivalOrderResult = { order: SaphoArrivalOrder; done: boolean };
type Entry = { player: string };
function check(condition: unknown, message: string): asserts condition {
if (!condition) throw new SaphoTruthOrderError(message);
}
/** The original pair is already in provisional storm order. */
export function createSaphoArrivalOrder(event: string, firstOwner: string, secondOwner: string): SaphoArrivalOrder {
const queue: SaphoArrivalOrder['queue'] = [{ player: firstOwner }, { player: secondOwner }];
const frame = createSaphoTruthOrder(queue, event);
check(frame && frame.holders.length === 2, 'Arrival ordering requires two distinct original owners.');
return { frame, queue };
}
function validateShape(order: SaphoArrivalOrder): void {
check(order && typeof order === 'object' && !Array.isArray(order) &&
Array.isArray(order.queue) && order.queue.length === 2 && order.frame &&
Array.isArray(order.frame.holders) && order.frame.holders.length === 2,
'Retain the two original arrival owners and ordering frame.');
validateSaphoTruthOrder(order.frame, order.queue);
check(order.queue[0].player === order.frame.holders[0] ||
order.frame.protectedFirst === order.queue[0].player ||
order.frame.protectedLast === order.queue[1].player,
'The reordered arrival must retain its original Sapho choice.');
}
/** Match a saved frame to the unchanged public parent, not a caller-supplied
* card or a newly sampled token. The queue may differ from original order only
* through a recorded Sapho choice. */
export function validateSaphoArrivalOrder(
order: SaphoArrivalOrder, event: string, originalOwners: readonly string[], selectedFirst: string,
): void {
validateShape(order);
check(order.frame.event === event && Array.isArray(originalOwners) && originalOwners.length === 2 &&
order.frame.holders[0] === originalOwners[0] && order.frame.holders[1] === originalOwners[1] &&
order.queue[0].player === selectedFirst,
'The arrival ordering no longer matches its original owners, event or selected first reaction.');
}
export function quoteSaphoArrivalOrder(order: SaphoArrivalOrder, player: string) {
validateShape(order);
return quoteSaphoTruthOrder(order.frame, order.queue, player);
}
function detach(result: SaphoTruthOrderResult<Entry>): SaphoArrivalOrderResult {
check(result.queue.length === 2, 'Retain both arrival owners.');
return {
order: {
frame: { ...result.frame, holders: [...result.frame.holders], passed: [...result.frame.passed] },
queue: [{ player: result.queue[0].player }, { player: result.queue[1].player }],
},
done: result.done,
};
}
/** Passing and using Sapho both retain the complete public receipt, even after
* the last owner has responded, so later continuation validates the order. */
export function passSaphoArrivalOrder(order: SaphoArrivalOrder, request: SaphoTruthOrderRequest): SaphoArrivalOrderResult {
validateShape(order);
return detach(passSaphoTruthOrder(order.frame, order.queue, request));
}
export function reorderSaphoArrivalOrder(
order: SaphoArrivalOrder, request: SaphoTruthOrderRequest & { mode: SaphoTruthOrderMode },
): SaphoArrivalOrderResult {
validateShape(order);
return detach(reorderSaphoTruthOrder(order.frame, order.queue, request));
}