Bind Thumper promises before the original hidden Spice draw

This commit is contained in:
Quintine committed 2026-10-27 04:33:20 +11:00
1 parent fd38c51ffd
commit 00741a37d0
19 files changed
+985 -34

No files matched your search

+53 -1
View File
@@ -61,7 +61,7 @@ const power = (claim: BattlePowerClaim = { kind: 'voice', use: true }): Expressi
({ kind: 'action', action: { kind: 'battlePower', battle: 'battle:1', territory: 'carthag', claim } });
const windowSource = (effect: WindowCardEffect) => effect === 'karamaShipment'
? JSON.stringify(['karamaShipment', 1, 'recipient', 'original']) : effect === 'karamaAuction' ? 'ordinary-auction:1:1' :
effect === 'harvester' ? 'spice-blow:1' : 'storm:1';
effect === 'thumper' ? 'first-spice-reveal:1' : effect === 'harvester' ? 'spice-blow:1' : 'storm:1';
const windowCard = (effect: WindowCardEffect = 'weather', source = windowSource(effect)): Expression =>
({ kind: 'action', action: { kind: 'windowCardPlay', effect, source } });
const fact = (): Expression => ({ kind: 'fact', fact: { kind: 'spice', compare: 'gte', value: 5 } });
@@ -1520,3 +1520,55 @@ void test('closing a shipment-price opportunity retains actual play and later wh
assert.deepEqual(expired.windowCards!.karamaShipment, { source, played: false, closed: true });
validateTurnActionPromises([expired], seats);
});
void test('Thumper receipt means accepted printed play after this answer and carries no worm result or chosen destination', () => {
const original = windowed(windowCard('thumper'));
const leaf = { kind: 'windowCardPlay', effect: 'thumper', source: 'first-spice-reveal:1' };
for (const extra of [{ worm: true }, { territory: 'carthag' }, { suppressed: true }, { card: 'physical-thumper' }])
assert.throws(() => parseTurnActionLeaf({ ...leaf, ...extra }), TurnActionError);
const played = recordTurnActionWindowCardPlay([original], 'respondent', 1, 'thumper', 'first-spice-reveal:1')[0];
assert.equal(played.fulfilled, true, 'No worm effect or successful outcome is part of the receipt');
assert.equal(original.windowCards!.thumper!.played, false);
const fresh = windowed(windowCard('thumper'), true, 'turn-action:2');
assert.equal(fresh.windowCards!.thumper!.played, false);
assert.equal(closeTurnActionWindowCardPlay([fresh], 'respondent', 1, 'thumper', 'first-spice-reveal:1')[0].fulfilled, false);
assert.equal(recordTurnActionWindowCardPlay([played], 'respondent', 1, 'thumper', 'first-spice-reveal:1')[0], played);
validateTurnActionPromises([played], seats);
});
void test('Thumper No binds only the exact actor turn and original first reveal until actual source closure', () => {
const no = windowed(windowCard('thumper'), false);
for (const [player, turn, source] of [['other', 1, 'first-spice-reveal:1'],
['respondent', 2, 'first-spice-reveal:1'], ['respondent', 1, 'first-spice-reveal:2']] as const) {
assert.equal(recordTurnActionWindowCardPlay([no], player, turn, 'thumper', source)[0], no);
assert.equal(closeTurnActionWindowCardPlay([no], player, turn, 'thumper', source)[0], no);
}
assert.equal(closeTurnActionPayments([no], 'respondent', 1)[0].fulfilled, false);
assert.throws(() => recordTurnActionWindowCardPlay([no], 'respondent', 1, 'thumper', 'first-spice-reveal:1'), TurnActionError);
assert.equal(no.windowCards!.thumper!.played, false);
const closed = closeTurnActionWindowCardPlay([no], 'respondent', 1, 'thumper', 'first-spice-reveal:1')[0];
assert.equal(closed.fulfilled, true);
assert.equal(recordTurnActionWindowCardPlay([closed], 'respondent', 1, 'thumper', 'first-spice-reveal:1')[0], closed);
validateTurnActionPromises([closed], seats);
});
void test('Thumper whole AND No and OR Yes retain their later payment correlation after first-reveal closure', () => {
const no = windowed(group('and', windowCard('thumper'), pay()), false);
const granted = recordTurnActionWindowCardPlay([no], 'respondent', 1, 'thumper', 'first-spice-reveal:1');
const closed = closeTurnActionWindowCardPlay(granted, 'respondent', 1, 'thumper', 'first-spice-reveal:1');
assert.equal(closed[0].fulfilled, false);
assert.deepEqual(branches(closed[0]), [[signedPay('recipient', 3, 0, false)]]);
assert.throws(() => recordTurnActionPayment(closed, 'respondent', 1, payment()), TurnActionError);
assert.deepEqual(closed[0].payments, []);
const expiry = finishTurnActionTurn(closed, 'respondent', 1)[0];
assert.equal(expiry.fulfilled, true);
const alternative = windowed(group('or', windowCard('thumper'), pay()), true, 'turn-action:2');
const absent = closeTurnActionWindowCardPlay([alternative], 'respondent', 1, 'thumper', 'first-spice-reveal:1');
assert.deepEqual(branches(absent[0]), [[signedPay()]]);
assert.equal(recordTurnActionPayment(absent, 'respondent', 1, payment())[0].fulfilled, true);
const and = windowed(group('and', windowCard('thumper'), pay()), true, 'turn-action:3');
const paid = recordTurnActionPayment([and], 'respondent', 1, payment());
assert.equal(paid[0].fulfilled, false);
assert.equal(recordTurnActionWindowCardPlay(paid, 'respondent', 1, 'thumper', 'first-spice-reveal:1')[0].fulfilled, true);
validateTurnActionPromises([expiry], seats);
});