Bind the original Fremen/ally/turn grant before Karama allowance or prevention. A prevented grant leaves intrinsic free rates and prior payments intact. Permit late grants under the explicit provisional Recruits current-rate policy, with shared controls, minimal bot support and native public chronology evidence. Complete rules and browser/deployed acceptance remain open.
99 lines
5.3 KiB
TypeScript
99 lines
5.3 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { viewGame } from '../game/engine';
|
|
import {
|
|
createFremenRevivalGrantFixture as fixture,
|
|
actFremenRevivalGrant as act,
|
|
passFremenRevivalGrantResponses as pass,
|
|
advanceFremenRevivalGrant as advance,
|
|
fremenRevivalGrantClean as clean,
|
|
fremenRevivalGrantPlayer as player,
|
|
fremenRevivalGrantInventory as inventory,
|
|
} from './fixture-fremen-revival-grant';
|
|
|
|
for (const advanced of [false, true]) {
|
|
void test(`${advanced ? 'Advanced' : 'Basic'} original allied grant waits for its native Karama response then permits three free`, () => {
|
|
const f = fixture({ advanced }), before = structuredClone(f.game);
|
|
let g = act(f.game, f.fremen, f.grant);
|
|
assert.equal(g.response?.kind, 'fremenRevivalGrant');
|
|
assert.deepEqual(g.response?.fremenGrant, { turn: 2, owner: f.fremen, recipient: f.ally });
|
|
assert.equal(g.freeRevival.includes(f.ally), false);
|
|
assert.ok(viewGame(g, f.observer).responseControls?.cancelCards.includes(f.karama));
|
|
assert.throws(() => act(g, f.ally, { type: 'revive', amount: 3, elite: 0 }), /response|power|interaction/i);
|
|
g = pass(g);
|
|
assert.equal(g.freeRevival.filter(id => id === f.ally).length, 1);
|
|
const spice = player(g, f.ally).spice;
|
|
g = advance(act(g, f.ally, { type: 'revive', amount: 3, elite: 0 }), clean);
|
|
assert.equal(player(g, f.ally).spice, spice);
|
|
assert.equal(player(g, f.ally).revived, 3);
|
|
assert.equal(player(g, f.ally).freeForcesRevived, 3);
|
|
assert.deepEqual(inventory(g), f.originalInventory);
|
|
assert.deepEqual(f.game, before);
|
|
assert.throws(() => act(g, f.fremen, f.grant), /already been granted/);
|
|
});
|
|
void test(`${advanced ? 'Advanced' : 'Basic'} real Karama denies only the original allied grant and cannot be bypassed by redeclaring`, () => {
|
|
const f = fixture({ advanced });
|
|
let g = act(f.game, f.fremen, f.grant);
|
|
g = act(g, f.observer, { type: 'card', card: f.karama, mode: 'cancel' });
|
|
assert.ok(!g.response);
|
|
assert.equal(g.freeRevival.includes(f.ally), false);
|
|
assert.deepEqual(g.revivalRules?.fremenGrantBlocked, [f.ally]);
|
|
assert.equal(g.revivalRules?.freeBlocked?.includes(f.ally) ?? false, false);
|
|
assert.equal(viewGame(g, f.ally).revival.freeRemaining, 2);
|
|
assert.equal(g.discard.filter(c => c.id === f.karama).length, 1);
|
|
const before = structuredClone(g);
|
|
assert.throws(() => act(g, f.fremen, f.grant), /Karama prevented/);
|
|
assert.deepEqual(g, before);
|
|
g = advance(act(g, f.ally, { type: 'revive', amount: 2, elite: 0 }), clean);
|
|
assert.equal(player(g, f.ally).freeForcesRevived, 2);
|
|
assert.deepEqual(inventory(g), f.originalInventory);
|
|
});
|
|
void test(`${advanced ? 'Advanced' : 'Basic'} grant prevention expires through the next actual Revival opening`, () => {
|
|
const f = fixture({ advanced });
|
|
let g = act(f.game, f.fremen, f.grant);
|
|
g = act(g, f.observer, { type: 'card', card: f.karama, mode: 'cancel' });
|
|
g = advance(g, state => state.turn === 3 && state.phase === 4 && clean(state));
|
|
assert.equal(player(g, f.fremen).ally, f.ally);
|
|
assert.equal(g.revivalRules?.fremenGrantBlocked?.includes(f.ally) ?? false, false);
|
|
assert.equal(viewGame(g, f.fremen).fremenRevivalGrant?.blocked, null);
|
|
g = pass(act(g, f.fremen, f.grant));
|
|
assert.equal(g.freeRevival.filter(id => id === f.ally).length, 1);
|
|
assert.deepEqual(inventory(g), f.originalInventory);
|
|
});
|
|
void test(`${advanced ? 'Advanced' : 'Basic'} later Recruits grant preserves earned paid/free history and increases the current allowance`, () => {
|
|
const f = fixture({ advanced, recruits: true, priorRevival: true });
|
|
assert.ok(f.recruitsPlay);
|
|
let g = act(f.game, f.fremen, f.recruitsPlay);
|
|
const spice = player(g, f.ally).spice;
|
|
assert.equal(viewGame(g, f.ally).revival.freeRemaining, 2);
|
|
assert.equal(viewGame(g, f.fremen).fremenRevivalGrant?.blocked, null);
|
|
g = pass(act(g, f.fremen, f.grant));
|
|
assert.equal(viewGame(g, f.ally).revival.freeRemaining, 4);
|
|
assert.equal(player(g, f.ally).spice, spice);
|
|
assert.equal(player(g, f.ally).revived, 3);
|
|
assert.equal(player(g, f.ally).freeForcesRevived, 2);
|
|
g = advance(act(g, f.ally, { type: 'revive', amount: 3, elite: 0 }), clean);
|
|
assert.equal(player(g, f.ally).spice, spice);
|
|
assert.equal(player(g, f.ally).revived, 6);
|
|
assert.equal(player(g, f.ally).freeForcesRevived, 5);
|
|
assert.equal(player(g, f.ally).tanks, 0);
|
|
assert.equal(g.discard.filter(c => c.id === f.recruits).length, 1);
|
|
assert.deepEqual(inventory(g), f.originalInventory);
|
|
});
|
|
void test(`${advanced ? 'Advanced' : 'Basic'} prevention after Recruits leaves intrinsic doubled free returns usable`, () => {
|
|
const f = fixture({ advanced, recruits: true });
|
|
assert.ok(f.recruitsPlay);
|
|
let g = act(f.game, f.fremen, f.recruitsPlay);
|
|
g = act(g, f.fremen, f.grant);
|
|
g = act(g, f.observer, { type: 'card', card: f.karama, mode: 'cancel' });
|
|
assert.ok(!g.response);
|
|
assert.equal(viewGame(g, f.ally).revival.freeRemaining, 4);
|
|
const spice = player(g, f.ally).spice;
|
|
g = advance(act(g, f.ally, { type: 'revive', amount: 4, elite: 0 }), clean);
|
|
assert.equal(player(g, f.ally).spice, spice);
|
|
assert.equal(player(g, f.ally).freeForcesRevived, 4);
|
|
assert.equal(g.freeRevival.includes(f.ally), false);
|
|
assert.deepEqual(inventory(g), f.originalInventory);
|
|
});
|
|
}
|