Handle prior leaders in Basic battle explosions
This commit is contained in:
1 parent
c3576b2deb
commit
474674f7fe
9 files changed
+194
-13
No files matched your search
+17
-2
@@ -20,12 +20,27 @@ open mode gates.
|
||||
Keep identified Basic-function gaps ahead of Advanced and wider interactions.
|
||||
Prototype labels and final acceptance gates remain.
|
||||
|
||||
**Basic explosion and prior surviving leaders — 27 October. Prototyped / Partial.**
|
||||
The Advanced Arrakis prior-leader handling now also applies provisionally in
|
||||
Basic: a later actual explosion kills surviving discs from earlier battles in
|
||||
that territory. The Basic source composition and its uncertainty are recorded
|
||||
in the [contract](EFFECTIVE_BATTLE_WEAPONS.md#basic-explosion-and-prior-leaders--27-october-2026).
|
||||
|
||||
A public Basic Emperor/Atreides/Guild game uses real entry and two successive
|
||||
battles to check prior and current leaders. An Advanced E3 supplement begins
|
||||
with earned Duke acquisition but explicitly stages the later battle position
|
||||
and cards: a set-aside Duke survives, while a Duke in the current plan dies.
|
||||
Kwisatz's prior location remains unresolved; browser and deployed play are not
|
||||
certified.
|
||||
Eleven selected files pass 173/173 and type/lint checks pass. No build, browser,
|
||||
mobile or deployed verification was performed for this focused rules change.
|
||||
|
||||
**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).
|
||||
concealed exclusions remain. The later Basic interpretation is recorded above;
|
||||
Homeworld, shared Duke, Kwisatz and special custody remain 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
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# Physical cards and effective battle weapons
|
||||
|
||||
## Basic explosion and prior leaders — 27 October 2026
|
||||
|
||||
**Prototyped / Partial.** Under the standing selected-interpretation authority,
|
||||
apply the same prior-leader Arrakis explosion handling to Basic. The [GF9 base
|
||||
rulebook, p11](https://www.gf9games.com/dunegame/wp-content/uploads/Dune-Rulebook.pdf)
|
||||
places surviving leaders in their territory until battles finish. A
|
||||
[separately indexed transcription](https://boardgamegeek.com/thread/2542307/lasgun-shield-explosion-do-leaders-who-previously)
|
||||
attributes destruction of all leaders in the battle territory to the 2019
|
||||
physical Lasgun card, but its actual face was not independently inspected.
|
||||
This Basic composition is provisional and is not an official clarification.
|
||||
Current and earlier leaders die once; a successful Traitor prevents the explosion
|
||||
and no leader bounty is awarded. Existing custody exclusions remain.
|
||||
|
||||
The supplied Advanced source physical30/printed28 returns a surviving
|
||||
Ambassador Duke next to the board after his battle, so his `usedAt` stamp alone
|
||||
does not locate that shared disc in a later explosion. The Advanced Kwisatz
|
||||
`usedAt` marker establishes a per-turn territory limit, but no retrieved primary
|
||||
source establishes where the separate marker stays after its battle. Its
|
||||
current-plan explosion behavior remains; a later explosion is not inferred from
|
||||
the stamp. Homeworld and exceptional custody boundaries remain open.
|
||||
|
||||
A native public Basic game uses actual entry, two battles and both previous
|
||||
surviving discs; its unopened card/Spice order and scalar lotteries are selected.
|
||||
The Duke supplement earns his original acquisition but explicitly stages later
|
||||
battle state and conserved cards. These checks do not establish broad custody,
|
||||
full-mode or deployed acceptance.
|
||||
Eleven related files pass 173/173 with type checking and lint; the first
|
||||
verification attempt caught an unused import before the full passing rerun.
|
||||
|
||||
## Earlier leaders in an Advanced explosion — 27 October 2026
|
||||
|
||||
**Prototyped / Partial.** The user-authorized revised rulebook physical p11 /
|
||||
@@ -13,9 +42,9 @@ 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 separate shared Duke and Kwisatz marker, Homeworld explosions, and
|
||||
exceptional custody interactions remain outside that slice; the later provisional
|
||||
Basic policy is recorded above. This does not certify complete modes.
|
||||
|
||||
The focused native regression uses an original Advanced Emperor/Atreides/Guild
|
||||
setup, paid shipments and movement to earn two successive Imperial Basin battles.
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# Dune implementation status
|
||||
|
||||
## 27 October 2026 — Basic explosion and prior leaders
|
||||
|
||||
Extend the Advanced Arrakis prior-leader explosion to Basic under the standing
|
||||
selected-interpretation authority. Base GF9 rules leave surviving discs in their
|
||||
territory, and a separately indexed card transcription supports territory-wide
|
||||
leader deaths. The card face has not been independently confirmed; the Basic
|
||||
composition is provisional. Shared Duke, Kwisatz, Homeworld and exceptional
|
||||
custody remain separate. [Contract](EFFECTIVE_BATTLE_WEAPONS.md#basic-explosion-and-prior-leaders--27-october-2026).
|
||||
|
||||
An original public Basic Emperor/Atreides/Guild sequence reaches two real
|
||||
battles after actual shipments and movement; only unopened initial cards and
|
||||
Spice positions plus scalar lotteries are selected. A separate Advanced E3
|
||||
supplement uses an earned Duke acquisition, then explicitly stages conserved
|
||||
cards and later battle state to check the set-aside and current-plan cases. No
|
||||
browser or deployment result is claimed.
|
||||
Eleven focused files pass 173/173 and types/lint pass. The private proof is
|
||||
`20261027-basic-explosion-leaders-proof-v2/`; the first attempt stopped at an
|
||||
unused test import, which was removed before the complete second run.
|
||||
|
||||
## 27 October 2026 — Advanced explosion and prior leaders
|
||||
|
||||
Use the user-authorized Advanced rulebook physical11/printed9 to include living
|
||||
|
||||
+13
-2
@@ -1,5 +1,16 @@
|
||||
# Rule decision index
|
||||
|
||||
## Basic explosion and prior leaders — 27 October 2026
|
||||
|
||||
Provisionally extend the existing Advanced Arrakis casualty handling to Basic.
|
||||
The [publisher base rulebook, p11](https://www.gf9games.com/dunegame/wp-content/uploads/Dune-Rulebook.pdf)
|
||||
keeps surviving leaders in their battle territory; a separately indexed
|
||||
transcription attributes territory-wide leader destruction to the physical
|
||||
Lasgun card. The latter has not been independently confirmed from the card face,
|
||||
so this composition is an explicit interpretation, not a publisher ruling.
|
||||
Captured and concealed leaders, shared Duke, Homeworlds and Kwisatz retain their
|
||||
separate boundaries. [Source and scope](EFFECTIVE_BATTLE_WEAPONS.md#basic-explosion-and-prior-leaders--27-october-2026).
|
||||
|
||||
## Earlier leaders in an Advanced Arrakis explosion — 27 October 2026
|
||||
|
||||
Apply the supplied Advanced rulebook physical11/printed9: surviving leaders
|
||||
@@ -7,8 +18,8 @@ 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).
|
||||
location. Homeworld, shared Duke, Kwisatz and exceptional custody remain
|
||||
separate; the later Basic interpretation is recorded above. [Source and scope](EFFECTIVE_BATTLE_WEAPONS.md#earlier-leaders-in-an-advanced-explosion--27-october-2026).
|
||||
|
||||
## Distrans before Richese special bidding — 27 October 2026
|
||||
|
||||
|
||||
+1
-1
@@ -31339,7 +31339,7 @@ function resolveBattle(g: Game, retreat?: DiplomatRetreatSelection) {
|
||||
// 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:')) {
|
||||
if (!b.territory.startsWith('homeworld:')) {
|
||||
let previousLeaders = 0;
|
||||
for (const owner of g.players)
|
||||
for (const leader of owner.leaders)
|
||||
|
||||
+1
-1
@@ -2815,7 +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.',
|
||||
'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. The Basic application is provisional; 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,29 @@
|
||||
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('Basic provisional territory-wide explosion kills earlier surviving leaders in a later native battle', () => {
|
||||
const f = createBattleTerritoryExplosionFixture({ advanced: false });
|
||||
assert.equal(f.setup.advanced,false);
|
||||
const firstLoser = f.afterFirst.players.find(p => p.id === 'atreides')!.leaders.find(l => l.id === f.priorLoser)!;
|
||||
const firstWinner = f.afterFirst.players.find(p => p.id === f.priorWinnerOwner)!.leaders.find(l => l.id === f.priorWinner)!;
|
||||
assert.equal(firstLoser.dead,false); assert.equal(firstLoser.usedAt,f.territory);
|
||||
assert.equal(firstWinner.dead,false); assert.equal(firstWinner.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} dies in 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);
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { handLimit } from '../game/engine';
|
||||
import { DUKE_VIDAL_ID } from '../game/duke-vidal';
|
||||
import { createMoritaniEcazDukeWinner, settleMoritaniEcazDukeWinner } from './fixture-moritani-ecaz-duke-winner';
|
||||
import { pairedE3NexusInventory, pairedE3NexusPlayer, revealPairedE3Nexus, stepPairedE3Nexus } from './fixture-paired-e3-nexus-modules';
|
||||
|
||||
/** Controlled supplement on a genuine Advanced Ecaz/Moritani/Guild setup and
|
||||
* earned Duke acquisition. Stage only the prior set-aside Duke history and
|
||||
* physical Lasgun/Shield custody; battle choice, plans and votes are real. */
|
||||
function source(setAside: boolean) {
|
||||
const f = createMoritaniEcazDukeWinner();
|
||||
const game = structuredClone(f.beforeBattle);
|
||||
const duke = game.dukeVidal!;
|
||||
assert.equal(duke.leader.id, DUKE_VIDAL_ID);
|
||||
assert.equal(duke.leader.dead, false);
|
||||
assert.equal(duke.controller, f.ecaz);
|
||||
if (setAside) {
|
||||
duke.controller = null;
|
||||
duke.acquiredTurn = null;
|
||||
duke.source = null;
|
||||
duke.leader.usedAt = f.location;
|
||||
}
|
||||
const take = (kind: 'lasgun' | 'shield', actor: string) => {
|
||||
const recipient = pairedE3NexusPlayer(game, actor);
|
||||
const already = recipient.hand.find(card => card.kind === kind);
|
||||
if (already) return already;
|
||||
const pools = [game.deck, game.discard, ...game.players.map(player => player.hand)];
|
||||
const pool = pools.find(cards => cards.some(card => card.kind === kind));
|
||||
assert.ok(pool, `The original ${kind} remains in physical custody`);
|
||||
const index = pool.findIndex(card => card.kind === kind);
|
||||
const [card] = pool.splice(index, 1);
|
||||
if (recipient.hand.length >= handLimit(recipient)) {
|
||||
const replacement = recipient.hand.pop(); assert.ok(replacement);
|
||||
pool.push(replacement);
|
||||
}
|
||||
recipient.hand.push(card);
|
||||
return card;
|
||||
};
|
||||
const lasgun = take('lasgun', f.ecaz), shield = take('shield', f.moritani);
|
||||
pairedE3NexusInventory(game);
|
||||
const native = pairedE3NexusPlayer(game, f.ecaz).leaders.find(leader => !leader.dead && !leader.usedAt);
|
||||
assert.ok(native);
|
||||
const chosen = stepPairedE3Nexus(game, f.choice);
|
||||
const plans = f.plans.map(step => step.actor === f.ecaz
|
||||
? { ...step, action: { ...step.action, leader: setAside ? native.id : DUKE_VIDAL_ID, weapon: lasgun.id } }
|
||||
: { ...step, action: { ...step.action, defense: shield.id } });
|
||||
const ready = revealPairedE3Nexus(chosen, plans);
|
||||
return { f, game, ready, priorDuke: structuredClone(duke), lasgun: lasgun.id, shield: shield.id };
|
||||
}
|
||||
|
||||
void test('Advanced later explosion does not kill a surviving Duke already set beside the board', () => {
|
||||
const s = source(true);
|
||||
assert.equal(s.priorDuke.controller, null);
|
||||
assert.equal(s.priorDuke.leader.usedAt, s.f.location);
|
||||
const done = settleMoritaniEcazDukeWinner(s.ready);
|
||||
assert.equal(done.lastBattleContext?.result, 'explosion');
|
||||
assert.equal(done.dukeVidal!.leader.id, DUKE_VIDAL_ID);
|
||||
assert.equal(done.dukeVidal!.leader.dead, false);
|
||||
assert.equal(done.dukeVidal!.leader.deaths, s.priorDuke.leader.deaths);
|
||||
assert.equal(done.dukeVidal!.controller, null);
|
||||
assert.ok(done.players.every(player => player.leaders.every(leader => leader.id !== DUKE_VIDAL_ID)));
|
||||
pairedE3NexusInventory(done);
|
||||
});
|
||||
|
||||
void test('Advanced current-plan Duke still dies in a resolved Lasgun–Shield explosion', () => {
|
||||
const s = source(false);
|
||||
assert.equal(s.priorDuke.controller, s.f.ecaz);
|
||||
const done = settleMoritaniEcazDukeWinner(s.ready);
|
||||
assert.equal(done.lastBattleContext?.result, 'explosion');
|
||||
assert.equal(done.dukeVidal!.leader.id, DUKE_VIDAL_ID);
|
||||
assert.equal(done.dukeVidal!.leader.dead, true);
|
||||
assert.equal(done.dukeVidal!.leader.deaths, s.priorDuke.leader.deaths + 1);
|
||||
assert.equal(done.dukeVidal!.controller, null);
|
||||
assert.ok(done.players.every(player => player.leaders.every(leader => leader.id !== DUKE_VIDAL_ID)));
|
||||
pairedE3NexusInventory(done);
|
||||
});
|
||||
@@ -11,13 +11,14 @@ function livingLeader(g: Game, id: string, strongest: boolean, excluded?: string
|
||||
(!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
|
||||
/** Real public 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);
|
||||
export function createBattleTerritoryExplosionFixture(options: { advanced?: boolean } = {}) {
|
||||
const advanced = options.advanced ?? true;
|
||||
let game = createGame('TERRITORYEXPLOSION', newPlayer('emperor','Emperor','emperor'), advanced);
|
||||
joinGame(game,newPlayer('atreides','Atreides','atreides'));
|
||||
joinGame(game,newPlayer('guild','Guild','guild'));
|
||||
const actions: SalusaGholaStep[] = [];
|
||||
@@ -25,7 +26,7 @@ export function createBattleTerritoryExplosionFixture() {
|
||||
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});
|
||||
act(game.host,advanced ? {type:'start',advancedPreview:true} : {type:'start'});
|
||||
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');
|
||||
|
||||
Reference in new issue
Block a user