Enable advanced mixed-faction paired arrivals
Verify and publish checkpoint container / container (push) Canceled after 0s
Verify and publish checkpoint container / container (push) Canceled after 0s
This commit is contained in:
1 parent
49f9ebdea0
commit
eb68f28a3a
9 files changed
+312
-65
No files matched your search
@@ -28,47 +28,46 @@ function reject(g: Game, player: string, action: Action, pattern?: RegExp) {
|
||||
}
|
||||
const overlap = /Ambassadors combined with another arrival reaction/;
|
||||
|
||||
void test('the reproduced Tleilaxu arrival and native Ix/Fremen movement declarations reject known unsupported overlap before prevention windows or RNG', (t) => {
|
||||
const games = ['tleilaxu', 'ixians', 'fremen'].map((faction) => {
|
||||
const g = movementArrivalGame(faction as 'tleilaxu' | 'ixians' | 'fremen');
|
||||
if (faction !== 'tleilaxu') nativeArrivalSource(g);
|
||||
if (faction === 'fremen') addArrivalAmbassador(g, 'atreides');
|
||||
holdArrivalCard(g, 'choam', 'Karama');
|
||||
return g;
|
||||
});
|
||||
const random = t.mock.method(crypto, 'getRandomValues', () => {
|
||||
throw Error('Unsupported arrival sampled RNG');
|
||||
});
|
||||
const uuid = t.mock.method(crypto, 'randomUUID', () => {
|
||||
throw Error('Unsupported arrival allocated an event');
|
||||
});
|
||||
for (const g of games) {
|
||||
reject(g, g.active!, arrivalMove(g), overlap);
|
||||
assert.equal(g.pendingChoamMove, undefined);
|
||||
assert.equal(g.pendingIxMove, undefined);
|
||||
assert.equal(g.pendingFremenMove, undefined);
|
||||
assert.equal(g.response, null);
|
||||
assert.equal(g.decision, null);
|
||||
void test('Tleilaxu and native Ix/Fremen declarations open their prevention windows before a paired arrival commits', () => {
|
||||
for (const faction of ['tleilaxu', 'ixians', 'fremen'] as const) {
|
||||
const before = movementArrivalGame(faction);
|
||||
if (faction !== 'tleilaxu') nativeArrivalSource(before);
|
||||
if (faction === 'fremen') addArrivalAmbassador(before, 'atreides');
|
||||
holdArrivalCard(before, 'choam', 'Karama');
|
||||
const forces = structuredClone(arrivalPlayer(before, faction).forces);
|
||||
const elites = structuredClone(arrivalPlayer(before, faction).elites);
|
||||
const ambassadors = structuredClone(before.ecazAmbassadors);
|
||||
const terror = structuredClone(before.moritaniTerror);
|
||||
const g = applyAction(before, faction, arrivalMove(before));
|
||||
assert.deepEqual(arrivalPlayer(g, faction).forces, forces);
|
||||
assert.deepEqual(arrivalPlayer(g, faction).elites, elites);
|
||||
assert.equal(arrivalPlayer(g, faction).moved, 0);
|
||||
assert.deepEqual(g.ecazAmbassadors, ambassadors);
|
||||
assert.deepEqual(g.moritaniTerror, terror);
|
||||
if (faction === 'tleilaxu') {
|
||||
assert.equal(g.decision?.kind, 'choamMovement');
|
||||
assert.ok(g.pendingChoamMove);
|
||||
} else {
|
||||
assert.equal(g.response?.kind, faction === 'ixians' ? 'ixMovement' : 'fremenMovement');
|
||||
assert.ok(faction === 'ixians' ? g.pendingIxMove : g.pendingFremenMove);
|
||||
}
|
||||
}
|
||||
assert.equal(random.mock.callCount(), 0);
|
||||
assert.equal(uuid.mock.callCount(), 0);
|
||||
});
|
||||
|
||||
void test('known unsupported entry rejects before putting a newly played Ornithopter into escrow or creating its event', (t) => {
|
||||
const g = movementArrivalGame(),
|
||||
card = holdArrivalCard(g, 'tleilaxu', 'Ornithopter');
|
||||
const uuid = t.mock.method(crypto, 'randomUUID', () => {
|
||||
throw Error('Movement card allocated an event');
|
||||
});
|
||||
reject(
|
||||
g,
|
||||
void test('a paired arrival can put a newly played Ornithopter into escrow before the prevention choice', () => {
|
||||
const before = movementArrivalGame(),
|
||||
card = holdArrivalCard(before, 'tleilaxu', 'Ornithopter');
|
||||
const g = applyAction(
|
||||
before,
|
||||
'tleilaxu',
|
||||
{ ...arrivalMove(g), movementCard: card, ornithopter: 'range3' },
|
||||
overlap,
|
||||
{ ...arrivalMove(before), movementCard: card, ornithopter: 'range3' },
|
||||
);
|
||||
assert.ok(arrivalPlayer(g, 'tleilaxu').hand.some((c) => c.id === card));
|
||||
assert.equal(g.ornithopter, undefined);
|
||||
assert.equal(uuid.mock.callCount(), 0);
|
||||
assert.equal(g.decision?.kind, 'choamMovement');
|
||||
assert.ok(g.ornithopter);
|
||||
assert.equal(arrivalPlayer(g, 'tleilaxu').hand.some((c) => c.id === card), false);
|
||||
assert.equal(g.discard.some((c) => c.id === card), false);
|
||||
assert.deepEqual(arrivalPlayer(g, 'tleilaxu').forces, { 'arrakeen:10': 3 });
|
||||
assert.equal(arrivalPlayer(g, 'tleilaxu').moved, 0);
|
||||
});
|
||||
|
||||
for (const tokens of ['ambassador', 'terror'] as const)
|
||||
@@ -132,8 +131,9 @@ void test('a saved CHOAM decline releases an uncommitted unsupported arrival wit
|
||||
ornithopter: 'range3',
|
||||
});
|
||||
const flight = structuredClone(g.ornithopter);
|
||||
// Model the persisted checkpoint: the unsupported second token was already
|
||||
// present in a legacy saved CHOAM window, whose declaration spent nothing.
|
||||
// Model a legacy saved CHOAM window where a still-unsupported Semuta
|
||||
// overlay and second token were already present after an unspent declaration.
|
||||
g.semutaPreview = true;
|
||||
addArrivalTerror(g);
|
||||
const before = structuredClone(g),
|
||||
resources = structuredClone(arrivalResources(g));
|
||||
|
||||
Reference in new issue
Block a user