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}
|
{' '}Tied battle: {g.players.find(p => p.id === g.battle!.tieWinner)?.name}
|
||||||
{g.battle.tieWinner !== g.battle.aggressor ? ' (Habbanya Stronghold advantage)' : ''}.
|
{g.battle.tieWinner !== g.battle.aggressor ? ' (Habbanya Stronghold advantage)' : ''}.
|
||||||
</p>
|
</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 && (
|
{!!g.battle.native && (
|
||||||
<p className="notice">
|
<p className="notice">
|
||||||
Only the native faction may reveal Traitors or Face
|
Only the native faction may reveal Traitors or Face
|
||||||
|
|||||||
@@ -16,6 +16,25 @@ open mode gates.
|
|||||||
|
|
||||||
## Current checkpoint and work
|
## 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
|
Ixian Nexus Betrayal now connects both actual native attempts: Bidding's
|
||||||
extra-card inspection and Advanced Technology. Fresh local `ixian-betrayal`
|
extra-card inspection and Advanced Technology. Fresh local `ixian-betrayal`
|
||||||
uses native Ix/allowed classic or Tleilaxu, the physical Ix deck and Nexus only.
|
uses native Ix/allowed classic or Tleilaxu, the physical Ix deck and Nexus only.
|
||||||
|
|||||||
@@ -1,5 +1,46 @@
|
|||||||
# Dune implementation status
|
# 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
|
## 1 October 2026 — Ixian Nexus Betrayal at original native attempts
|
||||||
|
|
||||||
Fresh local `ixian-betrayal` integrates native Ixian Bidding inspection/draw in
|
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
|
coexistence remain exceptions. Homeworlds are not Dune territories, and
|
||||||
their native allied-entry prohibitions are unchanged.
|
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
|
The page 15 footnote explicitly identifies the changed timing and advisor
|
||||||
coexistence as departures from the original rules and November 2020 FAQ.
|
coexistence as departures from the original rules and November 2020 FAQ.
|
||||||
Page 40 explains that choice; it is the supplied revision's unofficial
|
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.',
|
'Wait for the plan requested by special prescience.',
|
||||||
);
|
);
|
||||||
b.plans[id] = validatePlan(g, p, action);
|
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) {
|
if (b.fullPlan?.target === id) {
|
||||||
recordFullPlanInspection(g, b.fullPlan.owner);
|
recordFullPlanInspection(g, b.fullPlan.owner);
|
||||||
return g;
|
return g;
|
||||||
@@ -30880,6 +30882,9 @@ export function viewGame(state: Game, id: string) {
|
|||||||
})
|
})
|
||||||
: [],
|
: [],
|
||||||
submitted: Object.keys(b.plans),
|
submitted: Object.keys(b.plans),
|
||||||
|
leaderless: g.advanced
|
||||||
|
? Object.keys(b.plans).filter(playerId => b.plans[playerId].leader === null)
|
||||||
|
: [],
|
||||||
plans: b.revealed
|
plans: b.revealed
|
||||||
? b.plans
|
? b.plans
|
||||||
: b.plans[id]
|
: 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.',
|
summary: 'Build a legal combination and decide what to keep after winning.',
|
||||||
steps: [
|
steps: [
|
||||||
'Choose one weapon, one defense, both, or neither. A worthless card can occupy a weapon or defense slot.',
|
'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.',
|
'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'],
|
related: ['battle', 'card-karama', 'moritani-ally-retention'],
|
||||||
|
|||||||
@@ -99,6 +99,30 @@ function allow(state: Game) {
|
|||||||
function seal(g: Game, plan: Plan, id = 'e') {
|
function seal(g: Game, plan: Plan, id = 'e') {
|
||||||
return applyAction(g, id, { type: 'battlePlan', ...plan });
|
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 leaderClaim: PlanClaim = { kind: 'leader', leader: 'emperor-0' };
|
||||||
const supportClaim: PlanClaim = { kind: 'support', compare: 'gte', value: 1 };
|
const supportClaim: PlanClaim = { kind: 'support', compare: 'gte', value: 1 };
|
||||||
const liveCards = (g: Game) =>
|
const liveCards = (g: Game) =>
|
||||||
|
|||||||
Reference in new issue
Block a user