Announce Advanced leaderless battle plans before reveal

This commit is contained in:
Quintine committed 2026-10-01 22:33:01 +10:00
1 parent 3b79267c29
commit 44d90bb9ae
7 files changed
+108 -1

No files matched your search

+7
View File
@@ -4695,6 +4695,13 @@ export function GameTable({
{' '}Tied battle: {g.players.find(p => p.id === g.battle!.tieWinner)?.name}
{g.battle.tieWinner !== g.battle.aggressor ? ' (Habbanya Stronghold advantage)' : ''}.
</p>
{!g.battle.revealed && g.battle.leaderless.length > 0 && (
<p className="notice block" role="status">
{g.battle.leaderless.map(id => g.players.find(p => p.id === id)?.name).join(', ')}
{' '}announced a Battle Plan without a leader or Cheap Hero.
No battle cards can be played; other plan choices remain sealed.
</p>
)}
{!!g.battle.native && (
<p className="notice">
Only the native faction may reveal Traitors or Face
+19
View File
@@ -16,6 +16,25 @@ open mode gates.
## Current checkpoint and work
The authorized Advanced **leaderless Battle Plan announcement** is now
connected. Physical PDF page 11 requires disclosure before reveal when no
leader or Cheap Hero can be used; existing leader/card legality was already
enforced. Sealing that legal plan now records one public announcement and
shows a shared table notice while dial, support and other choices remain sealed.
No extra decision, cost, confirmation or serialized duplicate state is added;
Basic behavior is intentionally unchanged by this Advanced cutover.
Failing-before/passing-after privacy/refresh/duplicate regression passes.
Types/lint, **6,316 offline tests**, the **1,025-case selected union**, final
guide **10/10** and build pass. Actual native Advanced preview setup followed
by a conserved staged exhausted-leader battle proves public-before-reveal,
private other choices, JSON continuation and unchanged physical stock.
Backed-up real room `3Y5U53RW` v9 exercised human phone sealing, opponent
refresh, original reveal and native Collection continuation once. Saved proof
retains all thirty-three cards, typed forces, original identities and one
native stronghold income, with no announcement cost. The actual 390px notice
is 354px wide; all **57 original seats in nineteen QA rooms** reauthenticate.
This source-bound behavior is not a complete Advanced-game or deployed claim.
Ixian Nexus Betrayal now connects both actual native attempts: Bidding's
extra-card inspection and Advanced Technology. Fresh local `ixian-betrayal`
uses native Ix/allowed classic or Tleilaxu, the physical Ix deck and Nexus only.
+41
View File
@@ -1,5 +1,46 @@
# Dune implementation status
## 1 October 2026 — Advanced leaderless declaration before reveal
The supplied authorized Advanced PDF, physical page 11 **Leaders**, requires
announcement before reveal when a player cannot use a leader and is not playing
Cheap Hero; that plan may include no Treachery Cards. Current validation already
enforced leader availability and empty card slots. Its accepted plan lacked the
public announcement. Parent rejected a historical scout claim that live
Homeworld combat was disconnected: the later runtime checkpoint already connects
that path. No duplicate Homeworld implementation was introduced.
Advanced sealing now appends the public announcement before ordinary reveal or
the original special full-plan read continuation. The public view derives only
leaderless submitted player IDs from the existing sealed plans; no second saved
flag or authority is created. A shared pre-reveal table notice names the declared
combatant while keeping dial, support and other choices private. The original
plan guard prevents duplicate submissions and announcements. Basic behavior
remains unchanged by this Advanced-source cutover. Existing legal bots, Voice,
Prescience, full-plan inspections, combat and casualty continuations are retained.
The meaningful privacy regression fails before the implementation with missing
public announcement after an actual legal seal; it passes afterward, retaining
opponent/observer masking, JSON refresh, read immutability, duplicate rejection
and original reveal. Parent types/lint and **6,316/6,316** offline cases pass;
the selected ninety-file union is **1,025/1,025**. Final reference **10/10**
and build pass.
Actual native normal Advanced-preview start/setup plus a conserved staged battle
with previously used living leaders exercises legal rejection, one pre-reveal
declaration, JSON normalization, duplicate rejection and canonical thirty-three
card/typed-force conservation. The stage is not a claimed natural full game.
Fresh exact-version private-backed-up room `3Y5U53RW` starts through the real
HTTP preview action, keeps its actual setup/dealt cards/identities, then exercises
390px human Emperor leaderless sealing and the opponent's refresh. The other
plan stays private until human Atreides sealing; original Traitor declines
advance to Collection v9 once. Read-only saved proof confirms one chronicle
announcement before reveal, all cards/typed forces, unchanged identities and
the original single native Arrakeen income, not a fictitious unchanged setup
wallet. The ready phone notice is 354px wide within a 390px page. The actual
guide explains this timing; all **57 original seats in nineteen QA rooms**
reauthenticate. No reset, public-mode change or deployment is performed.
## 1 October 2026 — Ixian Nexus Betrayal at original native attempts
Fresh local `ixian-betrayal` integrates native Ixian Bidding inspection/draw in
+11
View File
@@ -50,6 +50,17 @@ opt-in profile or a publisher ruling:
coexistence remain exceptions. Homeworlds are not Dune territories, and
their native allied-entry prohibitions are unchanged.
The supplied revision's **Leaders, physical PDF page 11** also requires a
player unable to use a leader and not playing Cheap Hero to announce that fact
before revealing plans; no Treachery Cards can be part of that plan. Existing
leader/card legality remains unchanged. Advanced now publishes the legal
leaderless declaration when the original plan is sealed, before any reveal
or special full-plan read continuation, without exposing dial/support or
other private choices. The current battle status and chronicle preserve it
across refresh; no extra reaction, confirmation or plan submission is added.
Basic announcement behavior remains intentionally unchanged by this Advanced
source cutover.
The page 15 footnote explicitly identifies the changed timing and advisor
coexistence as departures from the original rules and November 2020 FAQ.
Page 40 explains that choice; it is the supplied revision's unofficial
+5
View File
@@ -29521,6 +29521,8 @@ function applyActionInner(
'Wait for the plan requested by special prescience.',
);
b.plans[id] = validatePlan(g, p, action);
if (g.advanced && b.plans[id].leader === null)
log(g, `${p.name} announced a Battle Plan without a leader or Cheap Hero; no battle cards can be played.`);
if (b.fullPlan?.target === id) {
recordFullPlanInspection(g, b.fullPlan.owner);
return g;
@@ -30880,6 +30882,9 @@ export function viewGame(state: Game, id: string) {
})
: [],
submitted: Object.keys(b.plans),
leaderless: g.advanced
? Object.keys(b.plans).filter(playerId => b.plans[playerId].leader === null)
: [],
plans: b.revealed
? b.plans
: b.plans[id]
+1 -1
View File
@@ -2299,7 +2299,7 @@ export const RULE_TOPICS: RuleTopic[] = [
summary: 'Build a legal combination and decide what to keep after winning.',
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.',
'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.',
'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'],
+24
View File
@@ -99,6 +99,30 @@ function allow(state: Game) {
function seal(g: Game, plan: Plan, id = 'e') {
return applyAction(g, id, { type: 'battlePlan', ...plan });
}
void test('Advanced leaderless announcement is public before reveal without exposing sealed choices', () => {
let game = fixture(true);
for (const leader of game.players[0].leaders) leader.usedAt = 'carthag';
game = seal(game, { dial: 1, support: 0, leader: null, weapon: null, defense: null }, 'a');
assert.equal(game.battle!.revealed, false);
for (const observer of ['e', 'b']) {
const view = viewGame(game, observer);
assert.deepEqual(view.battle!.leaderless, ['a']);
assert.deepEqual(view.battle!.plans, {});
assert.deepEqual(view.battle!.cards, []);
}
assert.equal(viewGame(game, 'a').battle!.plans.a.dial, 1);
const saved = JSON.stringify(game);
game = JSON.parse(saved);
assert.deepEqual(viewGame(game, 'b').battle!.leaderless, ['a']);
assert.equal(JSON.stringify(game), saved);
assert.throws(() => seal(game, { dial: 1, support: 0, leader: null, weapon: null, defense: null }, 'a'), RuleError);
assert.equal(JSON.stringify(game), saved);
game = seal(game, { dial: 1, support: 0, leader: game.players[1].leaders[1].id, weapon: null, defense: null });
assert.equal(game.battle!.revealed, true);
assert.deepEqual(viewGame(game, 'b').battle!.leaderless, ['a']);
assert.equal(viewGame(game, 'b').battle!.plans.a.dial, 1);
});
const leaderClaim: PlanClaim = { kind: 'leader', leader: 'emperor-0' };
const supportClaim: PlanClaim = { kind: 'support', compare: 'gte', value: 1 };
const liveCards = (g: Game) =>