import assert from 'node:assert/strict'; import test from 'node:test'; import { createSaphoArrivalOrder, passSaphoArrivalOrder, quoteSaphoArrivalOrder, reorderSaphoArrivalOrder, validateSaphoArrivalOrder, } from '../game/sapho-arrival-order'; const owners = ['ecaz', 'moritani'] as const; const initial = () => createSaphoArrivalOrder('arrival:event', owners[0], owners[1]); const request = (player: string, revision: number) => ({ event: 'arrival:event', revision, player }); void test('both public arrival owners can pass and retain the original storm order', () => { const first = initial(); assert.deepEqual(first.frame.holders, owners); validateSaphoArrivalOrder(first, 'arrival:event', owners, 'ecaz'); const a = passSaphoArrivalOrder(first, request('ecaz', 0)); assert.equal(a.done, false); const b = passSaphoArrivalOrder(a.order, request('moritani', 1)); assert.equal(b.done, true); assert.deepEqual(b.order.queue.map(row => row.player), owners); assert.deepEqual(b.order.frame.passed, owners); validateSaphoArrivalOrder(b.order, 'arrival:event', owners, 'ecaz'); assert.deepEqual(first.frame.passed, [], 'a later response does not mutate the prior frame'); }); for (const [player, mode] of [['moritani', 'first'], ['ecaz', 'last']] as const) { void test(`${player} can select ${mode} without displacing a prior protected choice`, () => { const start = initial(); assert.ok(quoteSaphoArrivalOrder(start, player).modes.includes(mode)); const used = reorderSaphoArrivalOrder(start, { ...request(player, 0), mode }); assert.equal(used.done, false); assert.deepEqual(used.order.queue.map(row => row.player), ['moritani', 'ecaz']); validateSaphoArrivalOrder(used.order, 'arrival:event', owners, 'moritani'); const other = player === 'ecaz' ? 'moritani' : 'ecaz'; assert.deepEqual(quoteSaphoArrivalOrder(used.order, other).modes, []); const passed = passSaphoArrivalOrder(used.order, request(other, 1)); assert.equal(passed.done, true); validateSaphoArrivalOrder(passed.order, 'arrival:event', owners, 'moritani'); assert.deepEqual(start.queue.map(row => row.player), owners); }); } void test('an arrival frame rejects stale, foreign, duplicate and no-op actions', () => { const start = initial(); assert.throws(() => createSaphoArrivalOrder('arrival:event', 'ecaz', 'ecaz')); assert.throws(() => passSaphoArrivalOrder(start, request('outsider', 0))); assert.throws(() => passSaphoArrivalOrder(start, request('ecaz', 1))); assert.throws(() => reorderSaphoArrivalOrder(start, { ...request('ecaz', 0), mode: 'first' })); const passed = passSaphoArrivalOrder(start, request('ecaz', 0)); assert.throws(() => passSaphoArrivalOrder(passed.order, request('ecaz', 1))); assert.throws(() => passSaphoArrivalOrder(passed.order, { ...request('moritani', 1), event: 'old' })); }); void test('an arrival receipt remains bound to the parent owners, event and chosen first reaction', () => { const used = reorderSaphoArrivalOrder(initial(), { ...request('moritani', 0), mode: 'first' }).order; const restored = JSON.parse(JSON.stringify(used)) as typeof used; validateSaphoArrivalOrder(restored, 'arrival:event', owners, 'moritani'); assert.throws(() => validateSaphoArrivalOrder(restored, 'other:event', owners, 'moritani')); assert.throws(() => validateSaphoArrivalOrder(restored, 'arrival:event', ['moritani', 'ecaz'], 'moritani')); assert.throws(() => validateSaphoArrivalOrder(restored, 'arrival:event', owners, 'ecaz')); const tampered = structuredClone(restored); delete tampered.frame.protectedFirst; assert.throws(() => validateSaphoArrivalOrder(tampered, 'arrival:event', owners, 'moritani')); const duplicate = structuredClone(restored); duplicate.queue[1].player = duplicate.queue[0].player; assert.throws(() => validateSaphoArrivalOrder(duplicate, 'arrival:event', owners, 'moritani')); });