Exercise Ecaz card variant and canonicalize advisor stances

This commit is contained in:
Quintine committed 2026-09-29 21:22:34 +10:00
1 parent 6ac5dabdee
commit c961a93f62
10 files changed
+185 -40

No files matched your search

+29
View File
@@ -387,6 +387,19 @@ void test('new advisors cannot flip into occupied fighters during their arrival
/cannot become fighters/,
);
});
void test('an unlocked ordinary advisor move keeps the same private and public views after JSON', () => {
let g = fixture();
army(g, 'a', { 'arrakeen:10': 1, 'imperial_basin:10': 1 });
army(g, 'b', { 'arrakeen:10': 2, 'imperial_basin:10': 1 }, [
'arrakeen', 'imperial_basin',
]);
g.active = 'b';
g = move(g, 'b', 'arrakeen:10', 'imperial_basin', 10, 1);
assert.deepEqual(g.players[1].advisors?.imperial_basin, {});
for (const player of g.players)
assert.deepEqual(viewGame(g, player.id), viewGame(JSON.parse(JSON.stringify(g)) as Game, player.id));
});
void test('advisor setup waits for Fremen, follows their placement, and becomes fighters only when alone', () => {
for (const occupied of [false, true]) {
let g = createGame('ADSETUP2', newPlayer('a', 'Atreides', 'atreides'));
@@ -506,6 +519,22 @@ void test('Guild cross-shipping matches existing advisors and preserves their ar
);
});
void test('an unlocked Guild advisor arrival projects identically after JSON continuation', () => {
let g = fixture();
g.players[2].faction = 'guild';
g.players[2].ally = 'b';
g.players[1].ally = 'h';
army(g, 'a', { 'red_chasm:7': 1 });
army(g, 'b', { 'arrakeen:10': 2, 'red_chasm:7': 1 }, ['arrakeen', 'red_chasm']);
g.active = 'b';
g = allow(applyAction(g, 'b', {
type: 'guildShip', from: 'arrakeen:10', territory: 'red_chasm',
sector: 7, amount: 2,
}));
assert.deepEqual(g.players[1].advisors?.red_chasm, {});
assert.deepEqual(viewGame(g, 'a'), viewGame(JSON.parse(JSON.stringify(g)) as Game, 'a'));
});
void test('accompanying advisors may select a valid sector of the shipment territory, with no reserve loss on a rejected choice', () => {
let g = fixture();
g = applyAction(g, 'a', {
+25
View File
@@ -94,6 +94,31 @@ function assertFailedEvidence(
return report;
}
void test('Ecaz card samples keep all three physical identities through saved resume', (t) => {
const area = temporary(t);
const out = join(area, 'ecaz-cards');
const result = run(out, '--profile', 'ecaz-treachery', '--players', '2',
'--rules', 'advanced', '--seed', '1000', '--max-actions', '1');
assert.equal(result.status, 1);
assertFailedEvidence(out, 'ecaz-treachery-2-advanced', 1137, false);
const snapshot = join(out, 'failed-ecaz-treachery-2-advanced.json');
const game = json<{
ecazTreachery: boolean;
deck: { id: string }[];
discard: { id: string }[];
players: { hand: { id: string }[] }[];
}>(snapshot);
assert.equal(game.ecazTreachery, true);
assert.deepEqual(
[...game.deck, ...game.discard, ...game.players.flatMap(player => player.hand)]
.map(card => card.id).filter(id => id.startsWith('ecaz-')).sort(),
['ecaz-harass-withdraw', 'ecaz-recruits', 'ecaz-reinforcements'],
);
const resumed = join(area, 'resumed');
assert.equal(run(resumed, '--resume', snapshot, '--seed', '1000', '--max-actions', '1').status, 1);
assertFailedEvidence(resumed, 'ecaz-treachery-2-advanced', 1137, true);
});
void test('Moritani Skills samples preserve their full module on saved continuation and reject Advanced before running', (t) => {
const area = temporary(t);
const out = join(area, 'skills');
+3
View File
@@ -727,6 +727,9 @@ void test('Ecaz Occupy permits its unlocked BG ally to choose fighters beside Ec
settled(advisors);
continue;
}
const unlockedAdvisors = move(g, { ...action, fighters: false });
assert.deepEqual(p(unlockedAdvisors, 'a').advisors?.sietch_tabr, {});
assert.deepEqual(viewGame(unlockedAdvisors, 'a'), viewGame(reload(unlockedAdvisors), 'a'));
let done = applyAction(reload(g), 'a', action);
assert.equal(done.response?.kind, 'advisorFlip');
done = allow(reload(done));
+20
View File
@@ -206,6 +206,26 @@ void test('Junction cross-transport preserves BG advisor locks and forbids mergi
homeworldGameIntegrity(g);
});
void test('an unlocked Junction advisor group has identical views across JSON recovery', () => {
let g = fixture();
g.active = 'beneGesserit';
g.movementRemaining = ['beneGesserit', 'atreides', 'guild'];
const bg = player(g, 'beneGesserit');
bg.forces = { 'arrakeen:10': 2, 'tueks_sietch:5': 1 };
bg.reserves = 17;
bg.advisors = { arrakeen: {}, tueks_sietch: {} };
g = offered(g);
const option = viewGame(g, bg.id).junctionTransport!;
g = applyAction(g, bg.id, {
type: 'junctionShip', event: option.event, offer: option.offer!.event,
destination: 'tueks_sietch:5',
sources: { 'arrakeen:10': { normal: 1, elite: 0 } },
});
assert.deepEqual(player(g, bg.id).advisors!.tueks_sietch, {});
assert.deepEqual(viewGame(g, bg.id), viewGame(JSON.parse(JSON.stringify(g)) as Game, bg.id));
homeworldGameIntegrity(g);
});
void test('Junction distinguishes native Fremen Arrakis arrival from a foreign Homeworld departure for BG and technology', () => {
let native = fixture(true);
native.active = 'fremen';