Announce Advanced leaderless battle plans before reveal
This commit is contained in:
1 parent
3b79267c29
commit
44d90bb9ae
7 files changed
+108
-1
No files matched your search
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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'],
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
Reference in new issue
Block a user