Resolve prior leaders in Advanced battle explosions
This commit is contained in:
1 parent
f50e436506
commit
c3576b2deb
11 files changed
+332
No files matched your search
@@ -20,6 +20,22 @@ open mode gates.
|
||||
Keep identified Basic-function gaps ahead of Advanced and wider interactions.
|
||||
Prototype labels and final acceptance gates remain.
|
||||
|
||||
**Advanced explosion and prior surviving leaders — 27 October. Prototyped / Partial.**
|
||||
On Arrakis, an actual Lasgun–Shield explosion now also kills still-present
|
||||
living leader discs left by earlier battles in that territory, without repeating
|
||||
current leader deaths or awarding bounty. Traitor precedence and captured /
|
||||
concealed exclusions remain. Basic, Homeworld, shared Duke, Kwisatz and special
|
||||
custody interpretations are separate. [Source and scope](EFFECTIVE_BATTLE_WEAPONS.md#earlier-leaders-in-an-advanced-explosion--27-october-2026).
|
||||
|
||||
An original Advanced Emperor/Atreides/Guild setup reaches two real battles in
|
||||
Imperial Basin: both surviving discs from the first battle die in the second
|
||||
explosion, together with the two newly selected leaders. Separate controlled
|
||||
leader-history cases cover other territories, prior deaths, Traitor precedence
|
||||
and concealed captive custody. No browser, deployed or complete-mode claim.
|
||||
Eight focused files pass 162/162 and type/lint checks pass. The older controlled
|
||||
Ghola/Revival test now clears its phase-owned ordering frame when explicitly
|
||||
skipping to Mentat; its elite-limit assertions are unchanged.
|
||||
|
||||
**Distrans before Richese special bidding — 27 October. Prototyped / Partial.**
|
||||
An untouched Once Around or Silent cache/Black Market lot or normal Black Market
|
||||
lot provisionally allows the existing transfer before the first bid, pass or sealed submission. Preserve
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# Physical cards and effective battle weapons
|
||||
|
||||
## Earlier leaders in an Advanced explosion — 27 October 2026
|
||||
|
||||
**Prototyped / Partial.** The user-authorized revised rulebook physical p11 /
|
||||
printed p9 leaves surviving leader discs in their battle territory and makes
|
||||
them subject to later territory-wide effects, explicitly including a
|
||||
Lasgun–Shield explosion. In an Advanced Arrakis battle that actually explodes,
|
||||
living prior-used player leaders at that territory die once alongside the two
|
||||
current plan leaders, including prior losing leaders. Prior-used native and
|
||||
foreign-ghola roster discs follow the same location marker; captive or concealed
|
||||
discs are excluded because their earlier stamp alone does not establish current
|
||||
physical presence. No bounty is added. Actual Traitor outcomes retain precedence
|
||||
and do not explode. Existing dead-leader Skill cleanup runs normally.
|
||||
|
||||
The separate shared Duke and Kwisatz marker, Homeworld explosions, Basic source
|
||||
cutover and exceptional custody interactions remain outside this slice; this
|
||||
does not certify complete modes or change their existing policy.
|
||||
|
||||
The focused native regression uses an original Advanced Emperor/Atreides/Guild
|
||||
setup, paid shipments and movement to earn two successive Imperial Basin battles.
|
||||
The prior winner's and loser's surviving discs are checked before the second
|
||||
explosion. Additional controlled staged histories cover other territories,
|
||||
already-dead leaders, current-leader deduplication, mutual Traitors and a
|
||||
concealed captive. Neither fixture establishes full mode or deployed acceptance.
|
||||
Eight related files pass 162/162, with type checking and lint. One older
|
||||
controlled Ghola test clears the phase-owned Revival frame before its direct
|
||||
Mentat jump; it retains its original elite-limit assertions.
|
||||
|
||||
The 7 September foundation now feeds a bounded, playable Mirror Weapon battle path (28 September 2026). The historical focused test counts below predate that integration.
|
||||
|
||||
## Contract and integration
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# Dune implementation status
|
||||
|
||||
## 27 October 2026 — Advanced explosion and prior leaders
|
||||
|
||||
Use the user-authorized Advanced rulebook physical11/printed9 to include living
|
||||
prior-used roster leaders still in an Arrakis territory in an actual
|
||||
Lasgun–Shield explosion. Current plan discs are killed once, Traitors retain
|
||||
precedence, no bounty is added, and captured/concealed prior discs remain outside
|
||||
this location inference. Basic, Homeworld, shared Duke, Kwisatz and special
|
||||
custody boundaries remain open. [Contract](EFFECTIVE_BATTLE_WEAPONS.md#earlier-leaders-in-an-advanced-explosion--27-october-2026).
|
||||
|
||||
The native Advanced Emperor/Atreides/Guild regression reaches two actual
|
||||
Imperial Basin battles, using paid shipments and ground movement. It selects
|
||||
unopened starting cards, Spice positions and native scalar lotteries; it does
|
||||
not install played leaders or armies. Separate controlled staged-history tests
|
||||
cover noncombatants, other territory, dead leaders, Traitor precedence and a
|
||||
concealed captive. Complete modes, browser and deployment remain unverified.
|
||||
Eight selected files pass 162/162 and types/lint pass. A historical controlled
|
||||
Ghola fixture clears the Revival order at its explicit phase jump; its original
|
||||
elite-limit assertions remain. Private proof is
|
||||
`20261027-advanced-explosion-leaders-proof-v2/`.
|
||||
|
||||
## 27 October 2026 — Distrans before Richese special bidding
|
||||
|
||||
Prototype the existing pre-bid timing interpretation for untouched Once Around
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# Rule decision index
|
||||
|
||||
## Earlier leaders in an Advanced Arrakis explosion — 27 October 2026
|
||||
|
||||
Apply the supplied Advanced rulebook physical11/printed9: surviving leaders
|
||||
remain in their battle territory and suffer a subsequent territory-wide
|
||||
Lasgun–Shield explosion. An actual Arrakis explosion kills each still-present
|
||||
living prior-used roster disc once; Traitors retain precedence and no bounty is
|
||||
added. Earlier stamps on captured or concealed discs do not prove present
|
||||
location. Basic, Homeworld, shared Duke, Kwisatz and exceptional custody remain
|
||||
separate. [Source and scope](EFFECTIVE_BATTLE_WEAPONS.md#earlier-leaders-in-an-advanced-explosion--27-october-2026).
|
||||
|
||||
## Distrans before Richese special bidding — 27 October 2026
|
||||
|
||||
Provisionally extend the existing pre-bid interpretation to an untouched Once
|
||||
|
||||
@@ -31336,6 +31336,21 @@ function resolveBattle(g: Game, retreat?: DiplomatRetreatSelection) {
|
||||
observeOccupation(g);
|
||||
dead(al);
|
||||
dead(dl);
|
||||
// Surviving discs from earlier battles remain in their Arrakis territory
|
||||
// through the Battle phase. Captives have changed custody and do not use
|
||||
// their old battle-location stamp as proof of physical presence.
|
||||
if (g.advanced && !b.territory.startsWith('homeworld:')) {
|
||||
let previousLeaders = 0;
|
||||
for (const owner of g.players)
|
||||
for (const leader of owner.leaders)
|
||||
if (!leader.dead && !leader.capturedBy && !leader.concealed &&
|
||||
leader.usedAt === b.territory) {
|
||||
dead(leader);
|
||||
previousLeaders++;
|
||||
}
|
||||
if (previousLeaders)
|
||||
log(g, `${previousLeaders} surviving leader disc${previousLeaders === 1 ? '' : 's'} from earlier battles in this territory ${previousLeaders === 1 ? 'was' : 'were'} also killed by the explosion.`);
|
||||
}
|
||||
for (const [p, plan] of [
|
||||
[a, ap],
|
||||
[d, dp],
|
||||
|
||||
@@ -2815,6 +2815,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
steps: [
|
||||
'Choose one weapon, one defense, both, or neither. A worthless card can occupy a weapon or defense slot.',
|
||||
'Choose an available leader or Cheap Hero when required. Without either, battle cards cannot be played. In Advanced, sealing a legal leaderless plan announces that fact to the whole table before reveal; its dial, spice and other choices remain sealed. Refresh retains the same announcement without submitting another plan.',
|
||||
'In Advanced on Arrakis, a surviving leader remains in its battle territory for the rest of the Battle phase. A later Lasgun–Shield explosion there also kills previously surviving leader discs still present, even if a different leader fights that later battle. A successful Traitor call prevents the explosion.',
|
||||
'A loser normally discards all played cards. If allied with Moritani and the battle has a winner, the defeated ally may retain one played card that a winner could keep. A winner may discard selected played cards; Cheap Heroes are discarded after use.',
|
||||
],
|
||||
related: ['battle', 'card-karama', 'moritani-ally-retention'],
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { viewGame } from '../game/engine';
|
||||
import { actSalusaGhola, advanceSalusaGhola, salusaGholaClean,
|
||||
type SalusaGholaStep } from './fixture-salusa-ghola';
|
||||
import { createAdvisorBattleFlipFixture } from './fixture-advisor-battle-flip';
|
||||
|
||||
void test('Advanced explosion does not kill or reveal a captured disc with a stale territory stamp', () => {
|
||||
const f = createAdvisorBattleFlipFixture({ explosion: true });
|
||||
let game = structuredClone(f.game);
|
||||
// Controlled custody supplement: a real BG leader in its native roster has
|
||||
// already been captured by the actually seated Harkonnen. Preserve the
|
||||
// capture snapshot that hides its physical custody from the other player;
|
||||
// the inherited battle, plans, cards, armies and all costs are native.
|
||||
const owner = game.players.find(p => p.id === 'beneGesserit')!;
|
||||
const captive = owner.leaders[0]; assert.ok(captive && !captive.dead && !captive.capturedBy);
|
||||
captive.usedAt = 'arrakeen';
|
||||
captive.concealed = { captor: 'harkonnen', controller: owner.id, dead: captive.dead,
|
||||
deaths: captive.deaths, usedAt: captive.usedAt };
|
||||
captive.capturedBy = 'harkonnen';
|
||||
const id = captive.id, physicalBefore = structuredClone(captive);
|
||||
const viewBefore = viewGame(game,'atreides').players.find(p => p.id === owner.id)!.leaders.find(l => l.id === id)!;
|
||||
assert.equal(viewBefore.capturedBy,undefined);
|
||||
const steps: SalusaGholaStep[] = [];
|
||||
game = advanceSalusaGhola(game,g => salusaGholaClean(g) && !!g.battle && !g.battle.revealed &&
|
||||
!g.battle.preparation && (!g.battle.preLeader || g.battle.preLeader.closed),steps);
|
||||
for (const plan of f.plans) game = actSalusaGhola(game,plan.actor,plan.action,steps);
|
||||
game = advanceSalusaGhola(game,g => !g.battle && salusaGholaClean(g),steps);
|
||||
const after = game.players.find(p => p.id === owner.id)!.leaders.find(l => l.id === id)!;
|
||||
assert.equal(after.dead,physicalBefore.dead);
|
||||
assert.equal(after.deaths,physicalBefore.deaths);
|
||||
assert.equal(after.capturedBy,'harkonnen');
|
||||
assert.ok(after.concealed);
|
||||
const viewAfter = viewGame(game,'atreides').players.find(p => p.id === owner.id)!.leaders.find(l => l.id === id)!;
|
||||
assert.equal(viewAfter.capturedBy,undefined);
|
||||
assert.equal(viewAfter.dead,viewBefore.dead);
|
||||
assert.equal(viewAfter.deaths,viewBefore.deaths);
|
||||
assert.ok(game.log.some(entry => /Lasgun and shield exploded/.test(entry.text)));
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createAdvisorBattleFlipFixture as fixture,
|
||||
commitAdvisorBattleFlip as commit,
|
||||
advisorBattleFlipPlayer as player,
|
||||
} from './fixture-advisor-battle-flip';
|
||||
|
||||
// The original Advanced game, forces, hands and Lasgun/Shield battle are real
|
||||
// actions from the shared fixture. Prior leaders' usedAt/dead history and,
|
||||
// for the mutual-Traitor case, exact physical Traitor custody are staged below:
|
||||
// this supplement does not claim an earned first battle or Traitor deal.
|
||||
function staged(mutualTraitors = false) {
|
||||
const f = fixture({ explosion: true });
|
||||
const game = structuredClone(f.game);
|
||||
if (mutualTraitors) {
|
||||
// Swap original identities across actual held/reserve slots; no Traitor is
|
||||
// invented or duplicated. The battle driver now calls the two real votes.
|
||||
const pools = [game.traitorReserve ?? [], ...game.players.flatMap(p => [p.traitors, p.traitorChoices])];
|
||||
for (const [actor, identity] of [[f.atreides, 'harkonnen-3'], [f.harkonnen, 'atreides-0']] as const) {
|
||||
const hand = player(game, actor).traitors;
|
||||
if (hand.includes(identity)) continue;
|
||||
const source = pools.find(pool => pool.includes(identity));
|
||||
assert.ok(source && hand.length > 0);
|
||||
const index = source.indexOf(identity);
|
||||
[hand[0], source[index]] = [source[index], hand[0]];
|
||||
}
|
||||
f.mutualTraitors = true;
|
||||
}
|
||||
const bg = player(game, f.bg);
|
||||
const [prior, elsewhere, unused, alreadyDead] = bg.leaders;
|
||||
assert.ok(prior && elsewhere && unused && alreadyDead);
|
||||
assert.ok([prior, elsewhere, unused, alreadyDead].every(leader => !leader.dead && !leader.usedAt));
|
||||
prior.usedAt = f.territory;
|
||||
elsewhere.usedAt = 'carthag';
|
||||
alreadyDead.dead = true;
|
||||
alreadyDead.deaths = 1;
|
||||
const current = player(game, f.atreides).leaders.find(leader => leader.id === f.atreidesPlan.leader);
|
||||
assert.ok(current && !current.dead && !current.usedAt);
|
||||
current.usedAt = f.territory;
|
||||
return { f, game, prior: prior.id, elsewhere: elsewhere.id, unused: unused.id,
|
||||
alreadyDead: alreadyDead.id, current: current.id };
|
||||
}
|
||||
|
||||
void test('Advanced territory-wide explosion kills a prior noncombatant leader exactly once', () => {
|
||||
const s = staged(), done = commit(s.game, s.f).game;
|
||||
assert.equal(done.lastBattleContext?.result, 'explosion');
|
||||
const bg = player(done, s.f.bg);
|
||||
const byId = (id: string) => { const leader = bg.leaders.find(item => item.id === id); assert.ok(leader); return leader; };
|
||||
const prior = byId(s.prior);
|
||||
assert.equal(prior.dead, true);
|
||||
assert.equal(prior.deaths, 1);
|
||||
assert.equal(prior.usedAt, undefined);
|
||||
const current = player(done, s.f.atreides).leaders.find(leader => leader.id === s.current)!;
|
||||
assert.equal(current.dead, true);
|
||||
assert.equal(current.deaths, 1, 'The current plan leader is not killed twice');
|
||||
assert.equal(current.usedAt, undefined);
|
||||
assert.equal(byId(s.elsewhere).dead, false);
|
||||
assert.equal(byId(s.elsewhere).usedAt, 'carthag');
|
||||
assert.equal(byId(s.unused).dead, false);
|
||||
assert.equal(byId(s.unused).usedAt, undefined);
|
||||
assert.equal(byId(s.alreadyDead).dead, true);
|
||||
assert.equal(byId(s.alreadyDead).deaths, 1, 'A dead leader stays in the Tanks without another death');
|
||||
});
|
||||
|
||||
void test('mutual Traitors take precedence over a revealed Lasgun and Shield', () => {
|
||||
const s = staged(true), done = commit(s.game, s.f).game;
|
||||
assert.equal(done.lastBattleContext?.result, 'mutualTraitors');
|
||||
const prior = player(done, s.f.bg).leaders.find(leader => leader.id === s.prior)!;
|
||||
assert.equal(prior.dead, false);
|
||||
assert.equal(prior.deaths, 0);
|
||||
assert.equal(prior.usedAt, s.f.territory);
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { actSalusaGhola, advanceSalusaGhola, salusaGholaClean,
|
||||
type SalusaGholaStep } from './fixture-salusa-ghola';
|
||||
import { createBattleTerritoryExplosionFixture } from './fixture-battle-territory-explosion';
|
||||
|
||||
void test('Advanced explosion kills leaders left from a completed earlier battle in the same territory', () => {
|
||||
const f = createBattleTerritoryExplosionFixture();
|
||||
const previous = f.afterFirst.players.find(p => p.id === 'atreides')!.leaders.find(l => l.id === f.priorLoser)!;
|
||||
const winner = f.afterFirst.players.find(p => p.id === f.priorWinnerOwner)!.leaders.find(l => l.id === f.priorWinner)!;
|
||||
assert.equal(previous.dead,false); assert.equal(previous.usedAt,f.territory);
|
||||
assert.equal(winner.dead,false); assert.equal(winner.usedAt,f.territory);
|
||||
const actions: SalusaGholaStep[] = [];
|
||||
let game = structuredClone(f.game);
|
||||
for (const plan of f.plans) game = actSalusaGhola(game,plan.actor,plan.action,actions);
|
||||
game = advanceSalusaGhola(game,g => !g.battle && salusaGholaClean(g),actions);
|
||||
assert.ok(actions.filter(step => step.action.type === 'traitorCall').every(step => step.action.call === false));
|
||||
for (const [owner,id] of [['atreides',f.priorLoser],[f.priorWinnerOwner,f.priorWinner],
|
||||
['emperor',f.emperorLeader],['guild',f.guildLeader]] as const) {
|
||||
const leader = game.players.find(p => p.id === owner)!.leaders.find(l => l.id === id)!;
|
||||
assert.equal(leader.dead,true,`${owner}'s ${id} is killed by the territory-wide explosion`);
|
||||
assert.equal(leader.deaths,1);
|
||||
}
|
||||
assert.ok(game.log.some(entry => /Lasgun and shield exploded/.test(entry.text)));
|
||||
assert.equal(game.discard.filter(card => card.id === f.shield || card.id === f.lasgun).length,2);
|
||||
assert.equal(game.players.flatMap(p => Object.entries(p.forces).filter(([key]) => key.startsWith(`${f.territory}:`)))
|
||||
.reduce((sum,[,amount]) => sum + amount,0),0);
|
||||
});
|
||||
@@ -2221,6 +2221,9 @@ void test('Ghola and normal revival share the elite limit for the entire turn, n
|
||||
);
|
||||
g = applyAction(g, 'p0', { type: 'revive', amount: 2, elite: 0 });
|
||||
conservation(g);
|
||||
// This controlled fixture jumps directly past Revival to Mentat. Retire the
|
||||
// phase-owned ordering frame that ordinary phase completion would clear.
|
||||
delete g.revivalOrder;
|
||||
g.phase = 8;
|
||||
g.ready = [];
|
||||
g = readyAll(g);
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { createGame, joinGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
|
||||
import { actSalusaGhola, advanceSalusaGhola, salusaGholaClean,
|
||||
type SalusaGholaStep } from './fixture-salusa-ghola';
|
||||
|
||||
const seat = (g: Game, id: string) => { const p = g.players.find(p => p.id === id); assert.ok(p); return p; };
|
||||
const readyForPlan = (g: Game) => salusaGholaClean(g) && !!g.battle && !g.battle.revealed &&
|
||||
!g.battle.preparation && (!g.battle.preLeader || g.battle.preLeader.closed);
|
||||
function livingLeader(g: Game, id: string, strongest: boolean, excluded?: string) {
|
||||
const leaders = seat(g, id).leaders.filter(l => !l.dead && !l.capturedBy && l.id !== excluded &&
|
||||
(!l.usedAt || l.usedAt === 'imperial_basin')).sort((a,b) => strongest ? b.strength-a.strength : a.strength-b.strength);
|
||||
assert.ok(leaders[0]); return leaders[0].id;
|
||||
}
|
||||
/** Real public Advanced E/A/G source: three actual forces meet in Imperial
|
||||
* Basin; the first battle's surviving leader discs then remain in that same
|
||||
* territory while the two remaining armies fight. Only the unopened three-card
|
||||
* starting deal, two unopened land Spice positions and scalar lotteries are
|
||||
* selected; no played forces, leaders, wallet, battle or clock is assigned. */
|
||||
export function createBattleTerritoryExplosionFixture() {
|
||||
let game = createGame('TERRITORYEXPLOSION', newPlayer('emperor','Emperor','emperor'), true);
|
||||
joinGame(game,newPlayer('atreides','Atreides','atreides'));
|
||||
joinGame(game,newPlayer('guild','Guild','guild'));
|
||||
const actions: SalusaGholaStep[] = [];
|
||||
const act = (actor: string, action: Action) => { game = actSalusaGhola(game,actor,action,actions); };
|
||||
const reach = (until: (g: Game) => boolean) => { game = advanceSalusaGhola(game,until,actions); };
|
||||
for (const p of game.players) act(p.id,{type:'ready'});
|
||||
const lobby = structuredClone(game);
|
||||
act(game.host,{type:'start',advancedPreview:true});
|
||||
const setup = structuredClone(game);
|
||||
assert.equal(game.status,'setup'); assert.ok(game.players.every(p => p.hand.length === 0));
|
||||
const shield = game.deck.find(c => c.kind === 'shield');
|
||||
const ordinary = game.deck.find(c => c.kind === 'projectile');
|
||||
const lasgun = game.deck.find(c => c.kind === 'lasgun');
|
||||
assert.ok(shield && ordinary && lasgun);
|
||||
const selected = [shield,ordinary,lasgun];
|
||||
const startingSelection = selected.map((card,position) => {
|
||||
const from = game.deck.findIndex(c => c.id === card.id); assert.ok(from >= position);
|
||||
[game.deck[position],game.deck[from]] = [game.deck[from],game.deck[position]];
|
||||
return {position,from,card:card.id};
|
||||
});
|
||||
reach(g => g.status === 'playing');
|
||||
const afterSetup = structuredClone(game);
|
||||
assert.deepEqual(game.players.flatMap(p => p.hand.map(c => c.id)),selected.map(c => c.id));
|
||||
const spiceSelection = [0,1].map(position => {
|
||||
const from = game.spiceDeck.findIndex((c,index) => index >= position &&
|
||||
'territory' in c && !c.discovery && c.territory !== 'imperial_basin');
|
||||
assert.ok(from >= position); const [c] = game.spiceDeck.splice(from,1);
|
||||
game.spiceDeck.splice(position,0,c); return {position,from,card:structuredClone(c)};
|
||||
});
|
||||
reach(g => g.phase === 5 && salusaGholaClean(g));
|
||||
const shipments: SalusaGholaStep[] = [];
|
||||
for (let n = 0; game.phase === 5 && n < 100; n++) {
|
||||
if (!salusaGholaClean(game)) { reach(salusaGholaClean); continue; }
|
||||
const owner = game.active!;
|
||||
if (owner === 'atreides') {
|
||||
act(owner,{type:'move',from:'arrakeen:10',territory:'imperial_basin',sector:9,amount:1});
|
||||
} else {
|
||||
const action: Action = {type:'ship',territory:'imperial_basin',sector:9,
|
||||
amount:owner === 'emperor' ? 2 : 1,elite:0,allyPayment:0};
|
||||
shipments.push({actor:owner,action}); act(owner,action);
|
||||
}
|
||||
reach(salusaGholaClean);
|
||||
act(owner,{type:'endMovement'});
|
||||
}
|
||||
assert.equal(game.phase,6);
|
||||
reach(g => salusaGholaClean(g) && !g.battle && !!g.active);
|
||||
const beforeFirst = structuredClone(game), chooser = game.active!;
|
||||
const choice = viewGame(game,chooser).battleChoices.find(c => c.territory === 'imperial_basin' &&
|
||||
(c.attacker === 'atreides' || c.defender === 'atreides'));
|
||||
assert.ok(choice, 'Choose the actual first contest against Atreides');
|
||||
const other = choice.attacker === 'atreides' ? choice.defender : choice.attacker;
|
||||
act(chooser,{type:'chooseBattle',territory:'imperial_basin',target:chooser === 'atreides' ? other : 'atreides'});
|
||||
reach(readyForPlan);
|
||||
const priorLoser = livingLeader(game,'atreides',false), priorWinner = livingLeader(game,other,true);
|
||||
act('atreides',{type:'battlePlan',leader:priorLoser,dial:0,support:0});
|
||||
act(other,{type:'battlePlan',leader:priorWinner,dial:0,support:0});
|
||||
reach(g => !g.battle && salusaGholaClean(g) && g.phase === 6 && !!g.active);
|
||||
const afterFirst = structuredClone(game);
|
||||
assert.equal(game.lastBattleContext?.winner,other);
|
||||
assert.ok(seat(game,'atreides').leaders.some(l => l.id === priorLoser && !l.dead && l.usedAt === 'imperial_basin'));
|
||||
assert.ok(seat(game,other).leaders.some(l => l.id === priorWinner && !l.dead && l.usedAt === 'imperial_basin'));
|
||||
const secondChooser = game.active!;
|
||||
const next = viewGame(game,secondChooser).battleChoices.find(c => c.territory === 'imperial_basin' &&
|
||||
[c.attacker,c.defender].includes('emperor') && [c.attacker,c.defender].includes('guild'));
|
||||
assert.ok(next, 'The actual two remaining armies must battle');
|
||||
act(secondChooser,{type:'chooseBattle',territory:'imperial_basin',
|
||||
target:secondChooser === 'emperor' ? 'guild' : 'emperor'});
|
||||
reach(readyForPlan);
|
||||
const beforeExplosion = structuredClone(game);
|
||||
const emperorLeader = livingLeader(game,'emperor',false,other === 'emperor' ? priorWinner : undefined);
|
||||
const guildLeader = livingLeader(game,'guild',false,other === 'guild' ? priorWinner : undefined);
|
||||
const plans = [
|
||||
{actor:'emperor',action:{type:'battlePlan',leader:emperorLeader,dial:0,support:0,defense:shield.id} as Action},
|
||||
{actor:'guild',action:{type:'battlePlan',leader:guildLeader,dial:0,support:0,weapon:lasgun.id} as Action},
|
||||
];
|
||||
return {game,beforeExplosion,beforeFirst,afterFirst,lobby,setup,afterSetup,startingSelection,spiceSelection,
|
||||
priorLoser,priorWinner,priorWinnerOwner:other,emperorLeader,guildLeader,plans,actions,shipments,
|
||||
shield:shield.id,lasgun:lasgun.id,territory:'imperial_basin',
|
||||
staging:'Only unopened starting cards and Spice positions and scalar lotteries are selected. The board entry, first battle and survivor discs use public native actions.'};
|
||||
}
|
||||
Reference in new issue
Block a user