diff --git a/components/elite-count.tsx b/components/elite-count.tsx
index 1a12dc10..da345f4d 100644
--- a/components/elite-count.tsx
+++ b/components/elite-count.tsx
@@ -5,11 +5,13 @@ export function EliteCount({
value,
onChange,
max,
+ min = 0,
label = 'Elite forces included',
}: {
value: number;
onChange: (value: number) => void;
max: number;
+ min?: number;
label?: string;
}) {
const id = useId();
@@ -19,7 +21,7 @@ export function EliteCount({
onChange(Number(e.target.value))}
diff --git a/components/game-table.tsx b/components/game-table.tsx
index 52319719..95fbe63b 100644
--- a/components/game-table.tsx
+++ b/components/game-table.tsx
@@ -6327,7 +6327,9 @@ export function GameTable({
<>
Once per game: revive up to three forces or one
- leader for free.
+ leader for free. These returns do not use your
+ ordinary revival allowances, including the normal
+ one-Sardaukar limit.
Forces to revive
@@ -6346,12 +6348,8 @@ export function GameTable({
)}
@@ -6365,7 +6363,9 @@ export function GameTable({
amount,
elite: eliteAmount,
},
- !me.tanks,
+ !Number.isSafeInteger(amount) || amount < 1 || amount > Math.min(3, me.tanks) ||
+ !Number.isSafeInteger(eliteAmount) || eliteAmount < Math.max(0, amount - (me.tanks - (me.elites?.tanks ?? 0))) ||
+ eliteAmount > Math.min(amount, me.elites?.tanks ?? 0),
)}
{me.leaders
.filter(
diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md
index a4e86098..a3418b48 100644
--- a/docs/CURRENT_STATUS.md
+++ b/docs/CURRENT_STATUS.md
@@ -20,6 +20,14 @@ open mode gates.
Keep identified Basic-function gaps ahead of Advanced and wider interactions.
Prototype labels and final acceptance gates remain.
+**Emperor special revival and Sardaukar — 27 October. Prototyped / Partial.**
+The Advanced Emperor's special Karama can return up to three actual Sardaukar
+without consuming the ordinary one-per-turn elite revival allowance. The
+special retains its real card cost, Tanks and native reserve routing; ordinary
+and other revival sources keep their separate limits. Existing saved counters
+are not reclassified. Wider combinations and final/deployed acceptance remain
+open. [Source interpretation](RULE_DECISIONS.md#emperor-special-revival-and-sardaukar-limit--27-october-2026).
+
**Advanced printed Kull lobby selection — 27 October. Prototyped / Partial.**
The host can explicitly select the existing Advanced printed-Kull preview for
CHOAM and classic factions with the CHOAM and Ix decks and no optional modules.
diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md
index cc097716..cf0f9c4b 100644
--- a/docs/IMPLEMENTATION_STATUS.md
+++ b/docs/IMPLEMENTATION_STATUS.md
@@ -1,5 +1,13 @@
# Dune implementation status
+## 27 October 2026 — Emperor special revival and Sardaukar
+
+The Advanced Emperor's special Karama may return up to three physical
+Sardaukar without using the ordinary one-per-turn elite allowance. Native
+Tanks, reserve routing and the one-use card cost remain authoritative; saved
+historical counters are not rewritten. Normal and other revival sources retain
+their own limits. [Source interpretation](RULE_DECISIONS.md#emperor-special-revival-and-sardaukar-limit--27-october-2026).
+
## 27 October 2026 — Advanced printed Kull lobby selection
The host can explicitly select the existing Advanced printed-Kull prototype
diff --git a/docs/RULE_DECISIONS.md b/docs/RULE_DECISIONS.md
index 25506cad..c7495535 100644
--- a/docs/RULE_DECISIONS.md
+++ b/docs/RULE_DECISIONS.md
@@ -1,5 +1,20 @@
# Rule decision index
+## Emperor special revival and Sardaukar limit — 27 October 2026
+
+The authorized supplied rulebook, physical p. 31 (printed p. 29), exempts
+the Emperor's special Karama revival from turn revival limits. Physical p. 9
+(printed p. 7) and p. 31 give ordinary Sardaukar a one-per-turn limit. Apply
+the specific exemption to that limit as well: up to three actual Sardaukar may
+return through the special power without spending the ordinary elite allowance.
+This is a source-composition interpretation, not an official GF9 FAQ. Ordinary
+revival, Ghola and allied extra revival retain their existing limits. Existing
+saved counters are not retroactively reclassified.
+The Homeworld Salusa Secundus low-population penalty prohibits free Sardaukar
+revival; its interaction with this special power remains unresolved and is not
+certified by this interpretation.
+[Runtime evidence](../tests/emperor-special-revival-exemption-runtime.test.ts).
+
## Sabotage gift overflow — 27 October 2026
Provisionally let a recipient whose hand is full receive Moritani's optional
diff --git a/game/bots.ts b/game/bots.ts
index 1ccf485b..0b05c925 100644
--- a/game/bots.ts
+++ b/game/bots.ts
@@ -1703,11 +1703,8 @@ function policyActions(g: GameView): Action[] {
leader: dead.id,
},
];
- const elite = Math.min(
- me.elites?.tanks ?? 0,
- eliteRevivalRemaining(me, g.advanced),
- );
- const amount = Math.min(3, me.tanks - (me.elites?.tanks ?? 0) + elite);
+ const amount = Math.min(3, me.tanks);
+ const elite = Math.min(amount, me.elites?.tanks ?? 0);
if (amount === 3 || (level === 0 && amount > 0))
return [
{ type: 'card', mode: 'special', card: specialCard.id, amount, elite },
diff --git a/game/engine.ts b/game/engine.ts
index b27f1456..a758b4a4 100644
--- a/game/engine.ts
+++ b/game/engine.ts
@@ -39214,10 +39214,6 @@ export function prepareSpecialKaramaIntent(
}
const n = integer(action.amount, 1, Math.min(3, p.tanks), 'Forces');
const elite = eliteChoice(n, p.tanks, p.elites?.tanks ?? 0, action.elite);
- requireRule(
- elite <= eliteRevivalRemaining(p, g.advanced),
- 'Only one elite force may be revived per turn.',
- );
return { ...base, kind: 'emperorForces', amount: n, elite };
} else if (p.faction === 'harkonnen') {
requireRule(
@@ -39578,7 +39574,6 @@ function resolveSpecialKaramaEffect(g: Game, p: Player, card: Card, intent: Spec
p.tanks -= n;
if (p.elites) {
p.elites.tanks -= elite;
- p.elites.revived += elite;
}
log(g, `${p.name} used special Karama to revive ${n} forces for free.`);
}
diff --git a/game/faction-reference.ts b/game/faction-reference.ts
index b22a2963..eeeb0c66 100644
--- a/game/faction-reference.ts
+++ b/game/faction-reference.ts
@@ -92,8 +92,8 @@ export const FACTION_RULES: Record<
advanced: [
'Five Sardaukar forces have a printed battle strength of two each, except against Fremen, where they count as ordinary forces.',
'Spice support applies per token. A supported Sardaukar contributes two strength; an unsupported Sardaukar contributes one, except when fighting Fremen.',
- 'Once per game during Revival, spend Karama to revive up to three of your own forces or one of your own dead leaders for free, separately from normal revival allowances. The one-Sardaukar-per-turn limit still applies.',
- 'An elite token counts as one force for revival. Only one Sardaukar may be revived per turn.',
+ 'Once per game during Revival, spend Karama to revive up to three of your own forces or one of your own dead leaders for free. These special returns do not use normal revival allowances, including the ordinary one-Sardaukar limit.',
+ 'An elite token counts as one force for ordinary revival. Only one Sardaukar may be ordinarily revived per turn; the Emperor’s special Karama is separate.',
],
},
fremen: {
diff --git a/game/reference.ts b/game/reference.ts
index fe0ef136..71dcc036 100644
--- a/game/reference.ts
+++ b/game/reference.ts
@@ -4320,7 +4320,7 @@ export const RULE_TOPICS: RuleTopic[] = [
'The combat engine now distinguishes ordinary and elite tokens, accepts half-strength dials and seals spice support with the battle plan. The unfinished Advanced preview is available for the six classic factions while other Advanced rules are completed.',
'One spice supports one token at its full strength. Unsupported tokens fight at half strength. Fremen uses full strength without spice; Sardaukar counts as ordinary against Fremen.',
'After resolution, casualties must match both the dial and spice spent. One legal ordinary/elite allocation settles automatically; several legal allocations ask the winner to choose. The losing army is destroyed. Only a traitor winner avoids its committed spice payment.',
- 'Elite tokens are tracked in reserves, territories and tanks. Normal revival, Ghola and the Emperor’s extra revival all share the limit of one elite revival per faction per turn.',
+ 'Elite tokens are tracked in reserves, territories and tanks. Normal revival, Ghola and the Emperor’s extra allied revival share the limit of one elite revival per faction per turn. The Emperor’s own special Karama revival is separate and does not consume that limit.',
'Atreides privately tracks losses and can add KH to a sealed plan after seven battle losses. The accompanying leader or Cheap Hero gains two strength if it survives, and cannot turn traitor. KH can be used in only one territory per turn.',
'Before sealing plans, the table offers separate Karama responses for doubled elite strength and Fremen’s free spice support. Canceling elite strength makes those tokens fight as ordinary forces for this battle; it does not change their token type. Canceling free support makes Fremen pay spice or fight at half strength. Sardaukar already fights as ordinary against Fremen.',
'The KH cancellation window does not disclose Atreides’ activation status or selected plan. Canceling prevents both its bonus and its traitor protection in this battle.',
@@ -4659,7 +4659,7 @@ export const RULE_TOPICS: RuleTopic[] = [
'After Voice and ordinary Prescience finish, Atreides may spend its Advanced special Karama to inspect either combatant’s entire committed plan, even outside its own battle. Target seals first; only Atreides gets actual dial, leader, physical played cards, support and KH. The provisional supplied-source distinction includes No-Field plans: ordinary dial Prescience is still barred, but the separate special reads the sealed plan without revealing/materializing its marker early. Allies receive no automatic inspection.',
'The inspected plan remains privately visible in Atreides’ action panel while the other combatant prepares a plan. No inspection acknowledgement is required. This also applies when Atreides is outside the battle helping an ally; that ally does not receive the inspection automatically. Both committed plans trigger the public reveal, which ends the private inspection. The inspected plan stays committed. Atreides may retain the power by declining this battle’s offer. A canceled ordinary prescience request does not remove the special power.',
'The after-preparation offer, target-first commitment and private-read-versus-public-No-Field-reveal order remain explicit provisional timing interpretations, not a newly retrieved combined FAQ or certification of complete Karama/expansion interactions.',
- 'During Revival, Emperor may revive up to three of its own forces or one of its own dead leaders for free. This is separate from normal revival allowances. The shared one-Sardaukar-per-turn limit still applies.',
+ 'During Revival, Emperor may revive up to three of its own forces or one of its own dead leaders for free. These special returns do not use normal revival allowances, including the ordinary one-Sardaukar limit. Only real forces in the Tanks may return.',
'During Bidding, Harkonnen chooses another player and takes up to four unseen cards. Inspect the combined hand, then return exactly the same number of cards. Newly taken cards may be included in the return.',
'Harkonnen may temporarily exceed eight cards during this choice. The return restores normal limits. Other players see the exchange count, but only each hand’s owner sees its cards.',
'If every card in the combined hand must return, the return happens automatically with a brief notice. Larger hands retain their return choice. Your Harkonnen inspection keeps the cards actually drawn privately readable after the exchange and after refresh; this records what you saw then, not the target’s current hand. Older saved forced returns record the cards held before returning them without claiming which were originally drawn.',
diff --git a/tests/emperor-special-revival-controls.test.tsx b/tests/emperor-special-revival-controls.test.tsx
new file mode 100644
index 00000000..ec70741a
--- /dev/null
+++ b/tests/emperor-special-revival-controls.test.tsx
@@ -0,0 +1,81 @@
+import assert from 'node:assert/strict';
+import test from 'node:test';
+import { registerHooks } from 'node:module';
+import { readFileSync } from 'node:fs';
+import { createElement } from 'react';
+import { renderToStaticMarkup } from 'react-dom/server';
+import { createGame, newPlayer, viewGame, type Game } from '../game/engine';
+import { baseDeck } from '../game/cards';
+
+const aliases = registerHooks({
+ resolve(specifier, context, next) {
+ return next(specifier === 'next/image' ? 'vinext/shims/image' :
+ specifier === 'next/link' ? 'vinext/shims/link' : specifier, context);
+ },
+ load(url, context, next) {
+ if (!url.endsWith('.module.css')) return next(url, context);
+ const css = readFileSync(new URL(url), 'utf8');
+ const classes = Object.fromEntries([...css.matchAll(/\.([a-zA-Z_][\w-]*)/g)].map(match => [match[1], match[1]]));
+ return { format: 'module', source: `export default ${JSON.stringify(classes)}`, shortCircuit: true };
+ },
+});
+const { GameTable } = await import('../components/game-table');
+aliases.deregister();
+
+/** Controlled projection of a live Advanced Revival turn; runtime tests own
+ * the actual force deaths, physical revival, and later ordinary allowance. */
+function game(tanks: number, eliteTanks: number, alreadyRevived: number): Game {
+ const game = createGame('EMPERORREVIVALUI', newPlayer('emperor', 'Emperor', 'emperor'), true);
+ game.players.push(newPlayer('atreides', 'Atreides', 'atreides'));
+ Object.assign(game, { status: 'playing', phase: 4, turn: 2, order: ['emperor', 'atreides'], active: 'emperor' });
+ const emperor = game.players[0];
+ emperor.tanks = tanks;
+ emperor.reserves = 20 - tanks;
+ emperor.elites = { tanks: eliteTanks, reserves: 5 - eliteTanks, forces: {}, revived: alreadyRevived };
+ const deck = baseDeck();
+ const index = deck.findIndex(card => card.effect === 'karama');
+ assert.ok(index >= 0);
+ emperor.hand = [deck.splice(index, 1)[0]];
+ game.deck = deck;
+ return game;
+}
+
+function markup(state: Game, seat = 'emperor') {
+ return renderToStaticMarkup(createElement(GameTable, { game: viewGame(state, seat), busy: false,
+ send: async () => {}, onExit() {} }));
+}
+function specialControls(html: string) {
+ const start = html.indexOf('Once per game: revive up to three forces');
+ assert.ok(start >= 0, 'The Emperor owns a visible special Karama control');
+ const end = html.indexOf('Use special Karama · forces', start);
+ assert.ok(end > start);
+ return html.slice(start, end + 'Use special Karama · forces'.length + 50);
+}
+function eliteInput(html: string): string {
+ const input = html.match(/Sardaukar to revive\s*( ]*>)/)?.[1];
+ assert.ok(input, 'The actual Sardaukar selector is visible');
+ return input;
+}
+
+void test('Emperor special UI includes available Sardaukar despite an already used ordinary starred allowance', () => {
+ const html = specialControls(markup(game(3, 3, 1)));
+ assert.match(html, /ordinary revival allowances, including the normal\s*one-Sardaukar limit/);
+ assert.match(html, /Forces to revive\s* ]*max="3"/);
+ const input = eliteInput(html);
+ assert.match(input, /\bmin="1"/);
+ assert.match(input, /\bmax="1"/);
+ assert.match(input, /\bvalue="0"/);
+ assert.match(html, /disabled=""[^>]*>Use special Karama · forces/,
+ 'The initial zero-elite selection is invalid when only Sardaukar are available');
+});
+
+void test('special UI respects physical stocks and does not expose another player’s hand controls', () => {
+ const mixed = specialControls(markup(game(3, 2, 1)));
+ const mixedInput = eliteInput(mixed);
+ assert.match(mixedInput, /\bmin="0"/);
+ assert.match(mixedInput, /\bmax="1"/);
+ assert.doesNotMatch(mixed, /disabled=""[^>]*>Use special Karama · forces/);
+ const noElites = eliteInput(specialControls(markup(game(2, 0, 1))));
+ assert.match(noElites, /\bmax="0"/);
+ assert.doesNotMatch(markup(game(3, 3, 1), 'atreides'), /Use special Karama · forces/);
+});
diff --git a/tests/emperor-special-revival-exemption-runtime.test.ts b/tests/emperor-special-revival-exemption-runtime.test.ts
new file mode 100644
index 00000000..b7ca7fd6
--- /dev/null
+++ b/tests/emperor-special-revival-exemption-runtime.test.ts
@@ -0,0 +1,121 @@
+import assert from 'node:assert/strict';
+import test from 'node:test';
+import { createGame, joinGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
+import { botActions } from '../game/bots';
+import {
+ actAdvancedRevivalOrder as act,
+ advanceAdvancedRevivalOrder as advance,
+ advancedRevivalOrderClean as clean,
+ advancedRevivalOrderInventory as inventory,
+ advancedRevivalOrderPlayer as player,
+ type AdvancedRevivalOrderStep,
+} from './fixture-advanced-revival-order';
+
+/** A genuine Advanced lobby, native setup, paid shipment and Storm deaths.
+ * Only original unopened starting-deal and Spice card positions are selected;
+ * no played hand, force, Tank, revival counter or clock is assigned. */
+function source() {
+ let game = createGame('EMPERORSPECIAL', newPlayer('emperor', 'Emperor', 'emperor'), true, ['ix']);
+ joinGame(game, newPlayer('atreides', 'Atreides', 'atreides'));
+ joinGame(game, newPlayer('tleilaxu', 'Tleilaxu', 'tleilaxu'));
+ const actions: AdvancedRevivalOrderStep[] = [];
+ const send = (actor: string, action: Action) => { game = act(game, actor, action, actions); };
+ const reach = (until: (state: Game) => boolean) => { game = advance(game, until, actions); };
+ for (const seat of game.players) send(seat.id, { type: 'ready' });
+ send('emperor', { type: 'start', advancedPreview: true });
+ assert.equal(game.status, 'setup');
+ assert.ok(game.players.every(seat => seat.hand.length === 0));
+ const originalCards = inventory(game);
+ const index = game.deck.findIndex(card => card.effect === 'karama');
+ assert.ok(index >= 0);
+ [game.deck[0], game.deck[index]] = [game.deck[index], game.deck[0]];
+ const karama = game.deck[0].id;
+ reach(state => state.status === 'playing');
+ assert.ok(player(game, 'emperor').hand.some(card => card.id === karama));
+ for (let position = 0; position < 4; position++) {
+ const from = game.spiceDeck.findIndex((card, index) => index >= position && 'territory' in card && !card.discovery);
+ assert.ok(from >= position);
+ game.spiceDeck.splice(position, 0, game.spiceDeck.splice(from, 1)[0]);
+ }
+ reach(state => state.turn === 1 && state.phase === 5 && state.active === 'emperor' && clean(state));
+ const beforeShipment = structuredClone(game);
+ send('emperor', { type: 'ship', territory: 'hagga_basin', sector: 13, amount: 3, elite: 3, allyPayment: 0 });
+ reach(clean);
+ assert.equal(player(game, 'emperor').elites?.forces['hagga_basin:13'], 3);
+ assert.equal(player(beforeShipment, 'emperor').spice - player(game, 'emperor').spice, 6);
+ reach(state => state.turn === 2 && state.phase === 1);
+ assert.equal(player(game, 'emperor').tanks, 3);
+ assert.equal(player(game, 'emperor').elites?.tanks, 3);
+ assert.equal(player(game, 'emperor').elites?.revived, 0);
+ reach(state => state.turn === 2 && state.phase === 4 && clean(state) &&
+ (!state.revivalOrder || state.revivalOrder.remaining[0] === 'emperor'));
+ assert.deepEqual(inventory(game), originalCards);
+ return { game, karama, originalCards, actions };
+}
+
+function special(game: Game, card: string, amount: number, elite: number): Game {
+ return act(game, 'emperor', { type: 'card', mode: 'special', card, amount, elite });
+}
+function ordinary(game: Game): Game {
+ return advance(act(game, 'emperor', { type: 'revive', amount: 1, elite: 1 }), clean);
+}
+function reject(game: Game, action: Action) {
+ const before = structuredClone(game);
+ assert.throws(() => act(game, 'emperor', action));
+ assert.deepEqual(game, before);
+}
+
+void test('Advanced Emperor may revive two extra Sardaukar after an ordinary elite revival', () => {
+ const f = source();
+ let game = ordinary(f.game);
+ assert.equal(player(game, 'emperor').elites?.revived, 1);
+ assert.equal(player(game, 'emperor').elites?.tanks, 2);
+ const before = structuredClone(player(game, 'emperor'));
+ game = advance(special(game, f.karama, 2, 2), clean);
+ assert.equal(player(game, 'emperor').elites?.tanks, 0);
+ assert.equal(player(game, 'emperor').elites?.reserves, before.elites!.reserves + 2);
+ assert.equal(player(game, 'emperor').elites?.revived, 1);
+ assert.equal(player(game, 'emperor').revived, before.revived);
+ assert.equal(player(game, 'emperor').spice, before.spice);
+ assert.equal(player(game, 'emperor').specialKaramaUsed, true);
+ assert.equal(game.discard.filter(card => card.id === f.karama).length, 1);
+ assert.deepEqual(inventory(game), f.originalCards);
+});
+
+void test('Advanced Emperor retains ordinary elite allowance after special two-Sardaukar revival', () => {
+ const f = source();
+ let game = advance(special(f.game, f.karama, 2, 2), clean);
+ assert.equal(player(game, 'emperor').elites?.tanks, 1);
+ assert.equal(player(game, 'emperor').elites?.revived, 0);
+ assert.equal(player(game, 'emperor').revived, 0);
+ game = ordinary(game);
+ assert.equal(player(game, 'emperor').elites?.tanks, 0);
+ assert.equal(player(game, 'emperor').elites?.revived, 1);
+ assert.equal(player(game, 'emperor').revived, 1);
+ assert.deepEqual(inventory(game), f.originalCards);
+});
+
+void test('Advanced Emperor may use all three special slots for Sardaukar without exceeding real Tanks', () => {
+ const f = source();
+ reject(f.game, { type: 'card', mode: 'special', card: f.karama, amount: 4, elite: 4 });
+ const game = advance(special(f.game, f.karama, 3, 3), clean);
+ assert.equal(player(game, 'emperor').tanks, 0);
+ assert.equal(player(game, 'emperor').elites?.tanks, 0);
+ assert.equal(player(game, 'emperor').elites?.reserves, 5);
+ assert.equal(player(game, 'emperor').elites?.revived, 0);
+ assert.equal(player(game, 'emperor').revived, 0);
+ assert.deepEqual(inventory(game), f.originalCards);
+});
+
+void test('the minimal bot can use the special revival with three actual Sardaukar', () => {
+ const f = source(), view = viewGame(f.game, 'emperor');
+ view.players.find(seat => seat.id === 'emperor')!.bot = 'Easy';
+ const action = botActions(view).find(candidate => candidate.type === 'card' && candidate.mode === 'special');
+ assert.ok(action);
+ assert.equal(action.amount, 3);
+ assert.equal(action.elite, 3);
+ const game = advance(act(f.game, 'emperor', action), clean);
+ assert.equal(player(game, 'emperor').elites?.tanks, 0);
+ assert.equal(player(game, 'emperor').elites?.revived, 0);
+ assert.deepEqual(inventory(game), f.originalCards);
+});
diff --git a/tests/karama-intents.test.ts b/tests/karama-intents.test.ts
index a9494038..568a7667 100644
--- a/tests/karama-intents.test.ts
+++ b/tests/karama-intents.test.ts
@@ -195,7 +195,7 @@ void test('Emperor force revival commits once after serialization and never duri
executeSpecialKaramaIntent(g, JSON.parse(JSON.stringify(intent)));
assert.equal(g.players[0].tanks, 2);
assert.equal(g.players[0].reserves, 18);
- assert.equal(g.players[0].elites!.revived, 1);
+ assert.equal(g.players[0].elites!.revived, 0);
assert.equal(g.players[0].specialKaramaUsed, true);
assert.equal(g.discard.filter((c) => c.id === intent.card).length, 1);
const after = structuredClone(g);
diff --git a/tests/special-karama.test.ts b/tests/special-karama.test.ts
index 9a75a2de..0036b909 100644
--- a/tests/special-karama.test.ts
+++ b/tests/special-karama.test.ts
@@ -267,7 +267,7 @@ void test('special power usage persists across turns and does not disable ordina
card: karmas[1].id,
});
});
-void test('Emperor special revival is free, separate from normal quota and preserves the one-elite-per-turn limit', () => {
+void test('Emperor special revival is free and separate from the ordinary force and elite quotas', () => {
const before = stock(fixture());
before.players[1].revived = 3;
const spice = before.players[1].spice;
@@ -276,14 +276,16 @@ void test('Emperor special revival is free, separate from normal quota and prese
assert.equal(g.players[1].reserves, 18);
assert.equal(g.players[1].elites!.tanks, 1);
assert.equal(g.players[1].elites!.reserves, 4);
- assert.equal(g.players[1].elites!.revived, 1);
+ assert.equal(g.players[1].elites!.revived, 0);
assert.equal(g.players[1].revived, 3);
assert.equal(g.players[1].spice, spice);
assert.equal(g.response, null);
const already = stock(fixture());
already.players[1].elites!.revived = 1;
- assert.throws(() => revive(already), /one elite/);
- assert.equal(already.players[1].specialKaramaUsed, undefined);
+ assert.equal(revive(already).players[1].elites!.revived, 1);
+ const two = revive(already, { amount: 2, elite: 2 });
+ assert.equal(two.players[1].elites!.tanks, 0);
+ assert.equal(two.players[1].elites!.revived, 1);
assert.equal(
revive(already, { amount: 3, elite: 0 }).players[1].elites!.revived,
1,
@@ -313,7 +315,7 @@ void test('Emperor invalid special revivals preserve the card and usage and cann
for (const extra of [
{ amount: 4 },
{ amount: 0 },
- { amount: 2, elite: 2 },
+ { amount: 2, elite: 3 },
{ amount: 1, target: 'g' },
]) {
assert.throws(() => revive(g, extra));
diff --git a/tests/tleilaxu-special-karama-window.test.ts b/tests/tleilaxu-special-karama-window.test.ts
index ab8be6c3..ffd5f0bc 100644
--- a/tests/tleilaxu-special-karama-window.test.ts
+++ b/tests/tleilaxu-special-karama-window.test.ts
@@ -48,13 +48,20 @@ void test('native quiet Revival permits any player before a request and spends t
void test('ready, self and empty-Tanks targets do not narrow quiet declaration permission', () => {
const f = fixture();
- const ready = act(f.game, f.observer, { type: 'ready' });
- for (const target of [f.owner, f.observer]) {
- assert.equal(player(ready, target).tanks, 0);
+ // Advanced Revival is ordered. Finish the actual current seat instead of
+ // assuming that Emperor may finish ahead of the public queue.
+ const first = f.game.revivalOrder?.remaining[0];
+ assert.ok(first);
+ assert.ok(f.game.revivalOrder!.remaining.length > 1);
+ const ready = act(f.game, first, { type: 'ready' });
+ assert.ok(ready.ready.includes(first));
+ for (const target of [f.owner, f.observer]) assert.equal(player(ready, target).tanks, 0);
+ for (const target of new Set([first, f.owner, f.observer])) {
+ const tanks = player(ready, target).tanks;
const chosen = act(ready, f.owner, { ...f.special, target });
assert.equal(chosen.revivalPrevention?.player, target);
assert.deepEqual(chosen.ready, ready.ready);
- assert.equal(player(chosen, target).tanks, 0);
+ assert.equal(player(chosen, target).tanks, tanks);
}
});