Allow Ecaz Cunning at original quiet phase boundaries

This commit is contained in:
Quintine committed 2026-10-15 13:48:02 +11:00
1 parent a24600ae1d
commit 60f338e2e1
14 files changed
+342 -80

No files matched your search

+23 -13
View File
@@ -11,7 +11,7 @@ import { resolveEcazDukeRevival } from '../game/ecaz-duke-revival';
import {
createNexusEcazDukeReceipt, nexusEcazDukeEvent, nexusEcazDukeModeSupported,
nexusEcazDukeSignature, quoteNexusEcazDuke, validateNexusEcazDuke,
type NexusEcazDukeCapturedReceipt,
type NexusEcazDukeReceipt,
} from '../game/nexus-ecaz-duke';
import { createNexusDukeDeathFixture } from './fixture-nexus-duke-capture';
@@ -96,12 +96,12 @@ void test('a released witness captive stays unavailable while used but can be li
assert.equal(dukeController(expireDuke(liberated, 4)), null);
});
void test('captured v2 receipts reject impossible custody and return transitions even after a fresh signature', () => {
void test('captured receipts reject impossible custody and return transitions even after a fresh signature', () => {
const g = position();
g.dukeVidal = captureDuke(acquireDuke(g.dukeVidal!, 'g', 3, 'ally'), 'h', 'g', 3, 'carthag');
const receipt = createNexusEcazDukeReceipt(g, 'e');
assert.ok('version' in receipt);
const mutations: ((saved: NexusEcazDukeCapturedReceipt) => void)[] = [
type Captured = Extract<NexusEcazDukeReceipt, { custody: 'captured' }>;
const receipt = createNexusEcazDukeReceipt(g, 'e') as Captured;
const mutations: ((saved: Captured) => void)[] = [
saved => { saved.before.leader.capturedBy = 'g'; },
saved => { saved.before.leader.concealed!.controller = 'h'; },
saved => { saved.before.leader.concealed!.deaths++; },
@@ -112,7 +112,6 @@ void test('captured v2 receipts reject impossible custody and return transitions
saved => { Object.assign(saved.after.leader, { capturedBy: 'h' }); },
saved => { Object.assign(saved.before.leader, { gholaBy: 'g' }); },
saved => { Object.assign(saved.before.leader.concealed!, { extra: 1 }); },
saved => { Object.assign(saved, { version: 3 }); },
];
for (const mutate of mutations) {
const saved = structuredClone(receipt);
@@ -129,7 +128,7 @@ void test('captured v2 receipts reject impossible custody and return transitions
});
void test('a privately executed return remains a Tanks resurrection with the unchanged old receipt schema', () => {
void test('a privately executed return remains a Tanks resurrection without leaking actual prior custody', () => {
const g = position();
const captured = captureDuke(acquireDuke(g.dukeVidal!, 'g', 3, 'ally'), 'h', 'g', 3, 'arrakeen');
g.dukeVidal = captured;
@@ -171,11 +170,6 @@ void test('Advanced Harkonnen admission is only the existing standalone no-Skill
excluded.dukeVidal!.leader.dead = false;
assert.throws(() => quoteEcazDukeAcquisition(excluded, 'e'));
}
for (const phase of [4, 5, 7]) {
const outside = position();
outside.phase = phase;
assert.notEqual(quoteNexusEcazDuke(outside, 'e')!.blocked, null);
}
});
void test('the actual Ecaz Ghola Treachery return sets the shared disc aside before a separate Cunning acquisition', () => {
@@ -228,6 +222,22 @@ void test('the actual Ecaz Ghola Treachery return sets the shared disc aside bef
assert.equal(g.nexusCards!.cards!.hands[owner], null);
assert.equal(g.nexusCards!.cards!.discard.filter(card => card === 'ecaz').length, 1);
const receipt = g.nexusEcazDukeHistory!.at(-1)!;
assert.equal('version' in receipt, false, 'ordinary Ecaz Ghola return is not a foreign-Ghola or captive producer');
validateNexusEcazDuke(g, receipt, g.nexusEcazDukeHistory!.length - 1);
});
void test('legacy captured Battle records cannot be re-signed as earlier-phase liberation', () => {
const g = position();
g.dukeVidal = captureDuke(acquireDuke(g.dukeVidal!, 'g', 3, 'ally'), 'h', 'g', 3, 'carthag');
const fields = structuredClone(createNexusEcazDukeReceipt(g, 'e')) as unknown as Record<string, unknown>;
fields.version = 2;
delete fields.custody;
const legacy = fields as NexusEcazDukeReceipt;
legacy.signature = nexusEcazDukeSignature(legacy);
validateNexusEcazDuke(g, legacy, 0);
const invalid = structuredClone(legacy);
invalid.phase = 5;
invalid.event = nexusEcazDukeEvent(invalid.turn, invalid.phase, invalid.owner,
invalid.sequence, invalid.before.controller, invalid.before.acquiredTurn);
invalid.signature = nexusEcazDukeSignature(invalid);
assert.throws(() => validateNexusEcazDuke(g, invalid, 0));
});