Admit clean Ix Stone and Mirror with existing revealed priorities

This commit is contained in:
Quintine committed 2026-10-16 08:00:45 +11:00
1 parent 842fd72761
commit f50dda6078
16 files changed
+411 -54

No files matched your search

+16
View File
@@ -3096,6 +3096,11 @@ export function GameTable({
tokens or change Stone Burner’s comparison.
</p>
)}
{g.expansions.includes('ix') && <p className="fine">
Provisional original Stone/Tooth order: Stone chooses first,
then uncopied Tooth. A Mirror copy still chooses before its
original, as printed. Traitor decisions follow all choices.
</p>}
<a href="/rules?topic=stone-burner">
Read Stone Burner rules
</a>
@@ -3135,6 +3140,11 @@ export function GameTable({
leaders, including yours. Chemistry as a defense protects;
Snoopers do not. Traitor decisions follow.
</p>
{g.expansions.includes('ix') && <p className="fine">
A Mirror copy chooses before the original Tooth.
With uncopied Stone and Tooth, this prototype provisionally
chooses Stone first. Both answers precede Traitor decisions.
</p>}
{actionButton('Activate Poison Tooth', {
type: 'decision',
event: g.decision.event,
@@ -5368,6 +5378,12 @@ export function GameTable({
Discard it after this battle, even if you win.
</p>
)}
{g.expansions.includes('ix') &&
(selectedSpecialWeapon || selectedBattleWeapon?.kind === 'poisonTooth') && <p className="fine">
Provisional original Stone/Tooth order: Stone chooses
first, then uncopied Tooth. Mirror copies still choose
before their originals, as printed.
</p>}
{selectedSpecialWeapon && (
<div className="my-3 flex flex-col gap-3">
<p>
+29 -26
View File
@@ -29,38 +29,41 @@ optional skill bands, combinations and public expansion starts remain open.
Rules implementation continues; comprehensive assurance and administration
remain deferred until all rules are implemented.
**Independent Ecaz battle-card interaction wave — 16 October.**
- Richese-family Harass/Stone uses visibly provisional post-withdrawal counts,
including own/opposing/copied Stone and Traitor-canceled returns.
- Same-plan Harass/Reinforcements pays the original typed reserve quote before
any return; future returns cannot fund or change it.
- In the existing native Advanced Occupy envelope, Reinforcements composes
with original or Mirror-copied Stone. Its +2 normal score adds no physical
undialed tokens, but the original own-reserve cost still pays.
**Selected Richese weapon/card interaction wave — 16 October.**
- Clean selected CHOAM/Ix profiles now admit Stone/Mirror with native
Ixians/Tleilaxu in Basic/Advanced; Ecaz/Moritani stays Advanced-only.
Original Stone-first then uncopied Tooth reuses existing CHOAM priority
under explicitly provisional choose-recommended interpretation. Printed
Mirror copy-first and the user's physical-Mirror retention stay independent.
- Richese-family Harass/Stone uses provisional post-withdrawal counts.
Same-plan Harass/Reinforcements pays its original reserve quote before returns.
Native Advanced Occupy Reinforcements/Stone adds no physical +2 tokens.
Original fixed Ecaz commitment, variable ally casualties, support, Traitor
precedence and mandatory utility-card disposal stay separate. The earlier
questions remain unanswered; explicit product interpretations are not publisher
rulings. No state field, new profile or public start is added.
Original fixed Ecaz commitment, native variable counters, support, Traitor
precedence, reserve cost and physical-card disposal/retention remain native.
Earlier questions remain unanswered; no new publisher ruling, state field,
public start or played-game conversion is implied. New Ix Tech/Stronghold,
other module bands and winner-changing allocation boundaries remain guarded.
Latest affected sixteen-file167/167, types/lint and build pass. Eight fresh
Node programs/all four profiles cover both leads/original or Mirror attacks
with36 accepted continuation actions. The corrected390px native-control journey
starts within hand capacity: physical5-vs4 wins, Ecaz reserves15→12/Tanks6,
Guild Tanks1, original support9→8, Ecaz2/Guild3 remain. Reinforcements discards
once; Stone retention clears battle/decision into collection, without overflow
or browser/application error. An overfull initial fixture was corrected by
returning unselected cards to the conserved deck; that initial hand is not
legal-hand evidence.
Latest affected thirteen-file99/99, TypeScript, lint and build pass. Eight fresh
Node native programs/all four policies accept42 continuation actions; two
original47/10-cache programs also complete without independent Ecaz cards.
Two real390px control journeys prove Advanced copied Tooth activation versus
unused original (both deaths, actual cyborg losses, physical Mirror kept,
spice34) and Basic Stone-before-Tooth (four-vs-zero, army/Stone kept, spice39).
Both clear battle/decision into the original Ix Collection opening, with legal
hands and no overflow/browser/application error. These use genuine setup with
explicit conserved battle staging, not unstaged complete-game acceptance.
Current parse/composition anchor and23 topic paths resolve; all ten relevant
facets remain Partial. Exact policies and earlier bounded human/policy proof:
Actual generated guide import, policy anchor and23 Stone/Mirror evidence paths
resolve; all ten facets stay Partial. Exact policies/earlier bounded proof:
[clean Ix](MIRROR_WEAPON_ENGINE_AUDIT.md#clean-selected-ix-composition--16-october-2026),
[Harass/Stone](HARASS_WITHDRAW_RUNTIME.md#provisional-stone-burner-timing--16-october-2026),
[Harass/Reinforcements](REINFORCEMENTS_RUNTIME.md#combined-utility-plan--16-october-2026),
[Reinforcements/Stone](REINFORCEMENTS_RUNTIME.md#native-stone-composition--16-october-2026).
Ix/Poison Tooth, own concealed No-Field Harass and other profile/module limits
remain. No human server, saved room, D1, deployment or new assurance campaign
changed; final assurance follows all rules.
Own concealed No-Field Harass and other profile/module limits remain. No human
server, saved room, D1, deployment or new assurance campaign changed; final
assurance follows all rules.
**Ixian Nexus purchased-card interaction wave — 16 October.**
Purchased-card replacement now composes:
+3 -2
View File
@@ -303,8 +303,9 @@ active Occupy, or the **selected lead's own pool only** after prevention.
Its total dial offset is removed before native typed casualty enumeration.
Mirror copies use the same comparison and original physical-card retention.
Public opposing No-Field preflight enumerates possible native pools, never the
actual secret denomination. Ix Stone/Poison Tooth timing and genuinely
winner-changing typed allocations retain their existing guards.
actual secret denomination. Clean selected CHOAM/Ix now uses the
[existing provisional original/copy priority](MIRROR_WEAPON_ENGINE_AUDIT.md#clean-selected-ix-composition--16-october-2026);
winner-changing allocations and Ix combined modules retain their guards.
Harass/Stone now uses the explicitly
[provisional post-withdrawal policy](HARASS_WITHDRAW_RUNTIME.md#provisional-stone-burner-timing--16-october-2026):
remove only the actual card user's undialed pool, never recompute original
+3 -2
View File
@@ -101,8 +101,9 @@ This is a legal strategy path, not strength calibration.
battles are already implemented for this card.
- Richese card-family configurations now admit Harass under the
[provisional post-withdrawal policy](#provisional-stone-burner-timing--16-october-2026).
The ordinary invariant-allocation guard and separate Ix/Poison Tooth timing
boundary remain. No public expansion start or played-game retrofit is added.
The ordinary invariant-allocation guard remains. Clean selected Ix follows
the [separate priority contract](MIRROR_WEAPON_ENGINE_AUDIT.md#clean-selected-ix-composition--16-october-2026);
other profile/module limits, public starts and played-game retrofit stay gated.
A card user's own still-concealed No-Field remains guarded until its original
reveal; this wave does not certify Harass on a concealed native marker.
- Co-present armies remain guarded in Basic or without the supported Advanced
+32
View File
@@ -1,5 +1,37 @@
# Dune implementation status
## 16 October 2026 — Clean selected Ix Stone and physical Mirror
Replaced the blanket Ix guard only for clean selected CHOAM/Ix profiles,
including native Ixians/Tleilaxu Basic/Advanced and Advanced Ecaz combinations.
Reuse existing CHOAM original Stone-first/uncopied-Tooth priority; standing
choose-recommended selects it provisionally while the earlier question remains
unanswered. Printed Mirror copy-first and the user's28 September physical
retention are unchanged. Public typed-allocation and new Ix Tech/Stronghold/
other module boundaries stay; no state field or public start added.
Thirteen-file99/99, TypeScript, lint and build pass with the existing
module-register/large-chunk/Unknown-route warnings. Natural Storm can change
tie priority: the native Occupy fixture now picks a public-safe original Stone
commitment, rather than relying on an already sealed zero dial or weakening
the guard. Six new cases include fixed/variable reserve-cost composition.
Eight fresh Node native programs/all four policies accept42 continuation actions.
Two original47/10-cache programs additionally complete without independent Ecaz
cards. Two actual390px journeys pass: Advanced copied Tooth activates before
the unused original, kills both leaders once, loses two actual cyborgs and keeps
winning physical Mirror at spice34; Basic mixed Ix Stone chooses kill before
unused Tooth, wins physical4-vs0, retains army/Stone and reaches spice39.
Both original battles/decisions clear into the unchanged Ix Collection opening.
No overflow/browser/application error or over-capacity hand. These are original
native setups with explicit conserved battle staging, not unstaged full games.
Actual generated guide import/policy anchor and23 topic evidence paths resolve;
all ten facets stay Partial. Obsolete blanket-Ix rejection removed, not re-pinned.
No human server, D1, saved room, deployment/public start or broad/HTTP/recovery
assurance campaign changed. Owned hosts/scripts removed.
[Source, profile and scoped proof](MIRROR_WEAPON_ENGINE_AUDIT.md#clean-selected-ix-composition--16-october-2026).
## 16 October 2026 — Native Occupy Stone and Reinforcements
Removed the same-plan/opposing Stone rejection while keeping existing mode
+64
View File
@@ -4,6 +4,70 @@ Audited 6 September 2026 as a proposed contract. The 28 September bounded runtim
**28 September ruling and runtime:** the user selected ordinary winner retention for the physical Mirror even when its copy activates Tooth or Artillery. This is an explicit product interpretation, not a located GF9 clarification. `game/effective-weapons.ts` keeps physical identity apart from copied attacks; the engine now connects copied Tooth/Stone choices, Stone comparison, custody, battle controls and legal AI in the bounded configuration.
## Clean selected Ix composition — 16 October 2026
Clean CHOAM/Ix profiles now admit original Stone and physical Mirror with
native Ixians/Tleilaxu and classics in Basic or Advanced. Existing native
Ecaz/Moritani combinations remain Advanced-only. This preserves the original
selected47-card deck plus separate Richese10; choosing the independent Ecaz
cards instead adds its genuine three cards for50, never a trimmed substitute.
Fresh/public start gates and actual physical card ownership remain unchanged.
**Existing policy, not a newly invented queue:** CHOAM's ordinary deck already
contains Tooth without Ix. Before this cutover, an actual original CHOAM setup
proved uncopied Stone chooses first, then Tooth. The standing 14 October
choose-recommended instruction extends that existing prototype policy to clean
Ix. The earlier priority question remains unanswered; Stone-before-Tooth is
visibly provisional, not a new publisher ruling or a user answer. Mirror
copy-first is separately printed on its physical face and remains unchanged.
The public profile and physical allocation guards never inspect an opposing
hand or sealed plan. Winner-changing typed allocation remains blocked, even
when the actual hidden plan would happen to make it finishable. New clean-Ix
admission excludes Homeworlds, Nexus, Leader Skills, Discoveries, active
Sandtrout, Tech Tokens and Stronghold Cards. The existing non-Ix Tech/Stronghold
band is intentionally unchanged. Other combinations remain separate.
Physical Mirror retention still follows the user's28 September interpretation;
the original Tooth/Artillery retain their actual mandatory-discard rules.
Original fixed Ecaz commitment, native variable counters and same-plan reserve
fees stay separate in Advanced Occupy.
### Scoped clean-Ix proof
The affected thirteen-file offline union passes **99/99**. TypeScript, lint and
production build pass; build retains module-register, large-chunk and
Unknown-route warnings. Six new behavioral cases cover both natives/modes/
physical slots, independent copied answers, original priority, retention,
public allocation ambiguity and native fixed/variable Occupy reserve cost.
Natural Storm tie priority required a public-safe original Stone fixture plan;
the allocation guard was preserved, not bypassed using the sealed zero dial.
Eight fresh Node programs/all four profiles use genuine native setup with
explicit conserved rule staging and42 accepted continuation actions. Two
additional fresh original47-card/10-cache programs, without the independent
Ecaz variant, complete copied Stone in Basic and Advanced. These are native
setup plus staged battle evidence, not unstaged complete-game certification.
Two actual390px journeys start before commitment with legal hands:
- Advanced Mirror activates copied Tooth first; original Tooth independently
stays unused. Both leaders die once, Ix loses four actual counters including
two cyborgs, ordinary dial5/support5 wins, bounty9 leaves spice34, and the
physical Mirror is retained.
- Basic mixed Ix Stone compares four undialed counters with zero, chooses kill
before the original unused Tooth, keeps its four-counter army and physical
Stone, and receives bounty9/spice39. Neither typed strength nor leader value
replaces the physical comparison.
Both original battles/decisions clear into the unchanged Ix Spice Collection
opening. Provisional priority is visible to Stone, Mirror and original Tooth
before commitment and in revealed controls. No root overflow or browser/
application error; all hands remain within capacity. Actual generated Mirror
and Stone guidance imports, the policy anchor and23 evidence paths resolve;
all ten facets stay Partial. No human server, saved room, D1, deployment,
public start or new broad/HTTP/recovery/privacy/custody assurance campaign
changed. Owned hosts/scripts are removed; private artifacts stay outside Git.
## Primary evidence and a retention correction
The actual face was visually reread at `/tmp/dune-rules/choam-mirror-reading.png`, alongside Stone Burner at `choam-stone-reading.png`. Both derive from the [physical-component photograph](https://cdn.anyfinder.eu/assets/QsUJtVktEC87xwK08oLlwnKyaozUWfuaLzG0LFEn67ENEEVgNrDGoITy3608Dqe7) recorded with its hash and limitations in `RICHESE_COMPONENTS.md`. This is publisher text visible on a physical component; the photographer, publication date and complete printing history remain unverified. No photograph is imported into the application.
+2 -1
View File
@@ -658,6 +658,7 @@ dedicated retrieved worked FAQs. [Contract and remaining limits](HOMEWORLD_OCCUP
| Ecaz Loyalty | Before initial Traitor dealing, randomly set aside one native Ecaz card face up permanently. No Karama effect; no change to its leader disc. Resolving after existing pre-Traitor setup is an explicit sequencing composition; native ordinary-disc Skills now precede this draw through public assignment. Older dealt saves are not retrofitted; wider Skills/assassination combinations remain gated. | [Official sources and runtime](ECAZ_LOYALTY.md), [native skill composition](LEADER_SKILLS_RUNTIME.md#native-ecaz-ordinary-disc-composition--4-october-2026) |
| Sapho battle order | First/last reorders remaining Battle-phase choosers at clean boundaries. Phase priority persists through aftermath; actual legal pairs determine eligibility. Separating the chooser from physical attacker/tie advantage is an explicit implementation interpretation of the card's distinct alternatives, not a dedicated FAQ ruling. Last does not prevent others from choosing the holder as an opponent. | [Runtime scope and source interpretation](SAPHO_BATTLE_ORDER.md) |
| Harass & Withdraw | Either slot but neither category; private Prescience answers null. Return uniquely determined own undialed physical forces before weapon losses, canceled by an opposing successful Traitor. Mandatory after-use discard overrides winner retention. Explosion ordering and own-Traitor disposal are explicit timing/specificity inferences, consistent with the recorded Planetologist precedent. | [Runtime and sources](HARASS_WITHDRAW_RUNTIME.md) |
| Clean Ix Stone / Tooth / Mirror | Reuse the existing CHOAM prototype order: original Stone first, then uncopied Tooth. The earlier priority question is unanswered; standing choose-recommended extends that order provisionally, not as a publisher ruling or user answer. Printed Mirror copy-first and user-selected physical retention stay independent. Clean selected Ix/Richese supports native Ixians/Tleilaxu in both modes; Ecaz/Moritani stays Advanced-only. Typed winner ambiguity, new Ix Tech/Stronghold and other module bands remain guarded. | [Source and profile distinction](MIRROR_WEAPON_ENGINE_AUDIT.md#clean-selected-ix-composition--16-october-2026) |
| Expansion-faction setup | Selecting Ecaz/Moritani factions does not select their independent three-card variant. Ordinary decks contain 33/35/47 cards; Richese's ten-card cache stays separate. Ecaz allocates six total forces within Imperial Basin sectors 9–11, retaining fourteen reserves. Flexible sector allocation is the literal printed territory constraint; Fremen→Ecaz→Advanced BG order is an implementation inference, while Moritani-last is printed. | [Source and profile contract](EXPANSION_FACTIONS_PROTOTYPE.md) |
| Ixian Technology on Richese lots | Superseded 8 October by the authorized supplied Advanced physical p34, which expressly excludes Richese auctions from Ixian Technology. Richese cache/Black Market lots now open directly; the once-per-round Technology use remains for ordinary lots. The temporary per-lot decline scaffolding and its tests were deleted. | [Auction source boundary](RICHESE_AUCTION_RULES.md#13-september-saved-per-lot-technology-decline--superseded-8-october) |
| Spice Banker battle spending | Secret own-spice commitment is separate from support; only the surviving selected disc gains strength. Core Battle Plan payment rules spend it on death/loss and waive it for a sole traitor-call winner. This payment composition is explicit; upper deferred-income custody remains pending. | [Source contract and prototype](SPICE_BANKER_RUNTIME.md) |
@@ -759,7 +760,7 @@ These are unresolved in the cited records. Do not infer an answer from elapsed t
| Boundary | Still unresolved | Existing record |
| --- | --- | --- |
| Reinforcements cost | The earlier all-outcome versus sole-Traitor-winner question remains unanswered. The bounded prototype provisionally charges on every revealed outcome, chooses normal reserves first and counts the transfer as battle losses. Same-plan Harass pays the original quote before returns; future returns cannot fund or change it. Native Advanced Occupy Stone/Mirror composition keeps the fee but ignores +2 normal score in physical-token comparison. These are product interpretations, not a new publisher ruling or user answer. Other profile/module gates remain; eligibility never depends on concealed Traitors. | [Source review](ECAZ_TREACHERY_RULES.md#14-september-reinforcements-cost-review) · [Combined policy](REINFORCEMENTS_RUNTIME.md#combined-utility-plan--16-october-2026) · [Stone composition](REINFORCEMENTS_RUNTIME.md#native-stone-composition--16-october-2026) |
| Harass / Stone Burner | The earlier question (keep gated or compare after withdrawal) remains unanswered. Under the standing 14 October choose-recommended instruction, the development prototype now provisionally compares undialed counters remaining after successful withdrawal. Opposing Traitor cancels the return; original fixed Occupy commitment stays. Public preflight considers possible fixed Ecaz withdrawal without reading hidden custody. Allocation ambiguity, Ix/Poison Tooth and other module boundaries remain. This is an implementation interpretation, not a publisher ruling or a user answer. | [Provisional timing](HARASS_WITHDRAW_RUNTIME.md#provisional-stone-burner-timing--16-october-2026) |
| Harass / Stone Burner | The earlier keep-gated versus compare-after-withdrawal question is unanswered. Standing choose-recommended provisionally compares counters left after successful withdrawal. Opposing Traitor cancels the return; original fixed Occupy commitment stays. Public preflight considers possible fixed Ecaz withdrawal without hidden custody. Clean Ix priority follows its separate explicitly provisional contract; allocation ambiguity and other profile/module limits remain. This is implementation interpretation, not a new publisher ruling or user answer. | [Withdrawal](HARASS_WITHDRAW_RUNTIME.md#provisional-stone-burner-timing--16-october-2026) · [Clean Ix priority](MIRROR_WEAPON_ENGINE_AUDIT.md#clean-selected-ix-composition--16-october-2026) |
| Recruits late accounting and rates | The earlier one/two-further-free question remains unanswered. The content-first prototype now preserves paid spice, deducts recorded actual free usage from the doubled rate and all returns from the total cap: two-free/one-paid Atreides can take two more free. This is visibly provisional, not a numerical user ruling. Unknown ledgers, pending repricing, late rate changes and same-turn replay remain guarded. | [Source alternatives](RECRUITS_RULES.md), [provisional runtime](RECRUITS_RUNTIME.md) |
| Sapho remaining timing | First versus unspent Advanced Guild priority, partly completed combined turns, cyclic auction duration and later battle timing stay guarded. The existing pre-plan opportunity supports [early aggressor](SAPHO_AGGRESSOR.md). Prior questions remain unanswered. | [Source audit](JUICE_OF_SAPHO_SOURCE_UPDATE.md), [battle review](JUICE_OF_SAPHO_BATTLE_REVIEW.md) |
| Semuta Drug | Full-hand exchange is user-approved; neutral all-seat acknowledgements remain the explicit application policy. Other discard producers, nested continuation coverage and full-game acceptance remain unfinished. | [Source update and selected exchange](SEMUTA_SOURCE_UPDATE_20260907.md), [privacy review](SEMUTA_PRIVACY_REVIEW.md) |
+18 -2
View File
@@ -29,8 +29,10 @@ checks possible fixed Ecaz withdrawal without inspecting hidden cards.
The earlier question remains unanswered. This is an interpretation selected
under the standing choose-recommended priority, not newly retrieved publisher
authority. Ix/Poison Tooth, own concealed No-Field Harass and other profile/
module boundaries remain separate. The 6 September evidence above
authority. Clean Ix now follows the separate
[selected-deck priority contract](MIRROR_WEAPON_ENGINE_AUDIT.md#clean-selected-ix-composition--16-october-2026);
own concealed No-Field Harass and other profile/module boundaries remain.
The 6 September evidence above
is historical; the current checkpoint has its own bounded proof.
@@ -44,6 +46,20 @@ undialed tokens or support. Original fixed Ecaz commitment, variable ally losses
mode choices, Traitor precedence and physical Stone/Mirror retention stay.
Other profile/module gates and the explicit reserve-cost interpretation remain.
## Clean Ix priority cutover — 16 October 2026
The blanket Ix timing guard is replaced only for the clean selected CHOAM/Ix
profile. It reuses the already connected original Stone-first/uncopied-Tooth
order, while printed Mirror copy-first remains independent. The earlier
priority question is unanswered; standing choose-recommended selects the
existing prototype order provisionally, not a new publisher ruling.
See the [exact profile and source distinction](MIRROR_WEAPON_ENGINE_AUDIT.md#clean-selected-ix-composition--16-october-2026).
Public precommit still quantifies over every feasible opposing force/dial/
support possibility, never a hidden already-sealed plan. Original typed force,
support, death, bounty, substitution and retention paths remain native.
New Ix Tech/Stronghold and other combined-module bands remain guarded.
## Primary evidence and exact effect
The physical publisher face was visually read at `/tmp/dune-rules/choam-stone-reading.png`, enlarged from the [component photograph](https://cdn.anyfinder.eu/assets/QsUJtVktEC87xwK08oLlwnKyaozUWfuaLzG0LFEn67ENEEVgNrDGoITy3608Dqe7) linked by the [product gallery](https://www.tabletopfinder.eu/en/boardgame/32692/dune-choam-richese). Photograph provenance/hash are recorded in `RICHESE_ACQUISITION_RULES.md`. The visible publisher wording is the evidence; uploader printing/date remains unverified.
+2 -2
View File
@@ -94,9 +94,9 @@ export function cardPresentation(card: VisibleCard): CardPresentation {
: richese.card.effect === 'semutaDrug'
? 'The explicit Semuta development preview offers neutral responses after clean public ordinary-card discards, paid Box disposal, retired Ornithopter movement (including a following Bene Gesserit Intrusion), Distrans, ordinary and queued definite Truthtrance answers, four Sapho scopes, unready pre-leader Residual Poison, printed Karama cancelling CHOAM Charity/Inflation/Bene Gesserit Charity, native capture/Auditor/battle-income/Ix-substitution/Basic allied-retention powers, or settling a normal-auction purchase or winning bid, a completed CHOAM sale, clean Kulon, proactive or reactive La La La/Baliset, proactive/final-Mentat Trip to Gamont with an ordinary/elite force or concealed No-Field, accepted Jubba Cloak before storm protection, Ix-deck Thumper/Amal, three battle discard stages (including before original Tech award, native Advanced capture, Face Dance, quoted CHOAM income or Auditor) and private normal-auction or Black Market Ixian ally replacements. Acquiring a printed cancellation cost never restores its canceled effect. Tech requires a supported fresh 3–6-seat CHOAM/optional-Ix profile. Native Ixian casualty substitution keeps its original before-Tooth order. Native Basic Moritani cleanup offers only unkept played cards after the original choice and Karama response, preserving its kept card and unchanged original retention pool. The Basic Richese/Moritani CHOAM/Ecaz entry also adds private Sabotage and Robbery overflow after clean shipment/movement arrivals, preserving the original victim/draw and arrival suffix. Commitment is owner-only; the approved full-hand exchange spends Semuta without increasing your completed hand. Other pending rewards, producers, nested responses and normal expansion starts remain unfinished.'
: richese.card.effect === 'mirrorWeapon'
? 'Mirror Weapon works in bounded CHOAM/Richese-deck classic-faction battles; copied Tooth and Stone choices resolve separately, and a winning physical Mirror may be kept even after copying Tooth or Artillery. This winner-retention treatment is a user-selected interpretation, not an official clarification. Combined modes and full Richese starts remain gated.'
? 'Mirror Weapon works in supported CHOAM/Richese-deck tables, including clean selected Ix profiles and Advanced native Ecaz/Moritani. Copied Tooth or Stone chooses before its original; a winning physical Mirror may be kept after copied Tooth or Artillery under the user-selected retention interpretation. Original Stone/Tooth uses the visibly provisional Stone-first order. Other profile/module and full-start gates remain.'
: richese.card.effect === 'stoneBurner'
? 'Stone Burner has development battle controls. Combined allocation timing and Ix timing remain guarded; full expansion starts stay disabled.'
? 'Stone Burner has development controls, including clean selected CHOAM/Ix profiles. Original Stone provisionally chooses before uncopied Tooth; Mirror copies still choose first as printed. Winner-changing physical allocations and other profile/module boundaries remain guarded; full expansion starts stay disabled.'
: 'Richese effect integration and verification are incomplete. Karama, Distrans, Nullentropy Box, Ornithopter, Residual Poison, Portable Snooper, Stone Burner and Mirror Weapon have bounded card handlers; full expansion starts remain disabled.',
topics: RULE_TOPICS.filter(
(topic) =>
+3 -2
View File
@@ -21041,8 +21041,9 @@ function stonePublicPools(
: [pool];
}
function stoneTimingBlock(g: Game): string | null {
return g.expansions.includes('ix')
? 'Stone Burner with the Ix expansion is awaiting a ruling on its simultaneous revealed choice with Poison Tooth. This implementation boundary applies before either hidden plan is submitted.'
if (!g.expansions.includes('ix')) return null;
return mirrorWeaponModeBlock(g)
? 'Stone Burner with Ix needs the clean selected CHOAM/Ix profile without combined optional modules; the public allocation guard still applies.'
: null;
}
function stoneOccupyContext(g: Game): StoneBurnerContext | undefined {
+7 -2
View File
@@ -3,6 +3,7 @@ import type { FactionId } from './catalog';
const supported = new Set<FactionId>([
'atreides', 'harkonnen', 'emperor', 'guild', 'beneGesserit', 'fremen',
'choam', 'richese', 'ecaz', 'moritani',
'ixians', 'tleilaxu',
]);
/** Public configuration only: never inspect an opponent's hand or sealed plan. */
@@ -15,12 +16,16 @@ export function mirrorWeaponModeBlock(g: {
leaderSkills?: unknown;
discoveries?: unknown;
sandtrout?: boolean;
techTokens?: unknown;
strongholdCards?: unknown;
}): string | null {
if (!g.expansions.includes('choam') ||
g.expansions.some(id => id !== 'choam' && id !== 'ecaz') ||
g.expansions.some(id => id !== 'choam' && id !== 'ecaz' && id !== 'ix') ||
(g.expansions.includes('ecaz') && g.advanced !== true) ||
g.players.some((p) => !supported.has(p.faction)))
return 'Mirror Weapon currently needs CHOAM/Richese with classic or native Ecaz/Moritani factions; Ix timing remains unresolved.';
return 'Mirror Weapon needs selected CHOAM/Richese with supported native factions; Ecaz/Moritani combinations require Advanced.';
if (g.expansions.includes('ix') && (g.techTokens || g.strongholdCards))
return 'Ix-deck Stone/Mirror with Tech Tokens or Stronghold Cards remains outside this clean profile.';
if (g.homeworlds || g.nexusCards || g.leaderSkills || g.discoveries || g.sandtrout)
return 'Mirror Weapon with combined optional modules is still being integrated.';
return null;
+25 -4
View File
@@ -1108,7 +1108,8 @@ export const RULE_TOPICS: RuleTopic[] = [
'Compare the number of physical force tokens not dialed into battle. The aggressor wins a tie. Dialed losses and spice support still follow their normal rules; elite strength is not an extra physical token.',
'With Harass & Withdraw, this prototype provisionally compares undialed tokens remaining after successful withdrawal. An opposing successful Traitor cancels the return and decides the outcome instead. In Occupy, only the card user’s pool returns; original fixed Ecaz commitment and the other ally’s undialed counters remain. This timing is an explicit implementation interpretation, not a publisher ruling.',
'In the existing native Advanced Occupy prototype, Reinforcements still pays its original own-reserve cost with same-plan, opposing or Mirror-copied Stone. Its +2 normal-score bonus adds no physical undialed tokens, dial, support or on-board casualties. Original mode choices, Traitor precedence and physical Stone/Mirror retention remain unchanged; other profile/module boundaries stay.',
'The plan editor checks the chosen dial and support against every projected public opposing force possibility. If legal force allocations could change the winner, commitment is blocked until that combined timing is supported. Ix expansion timing involving Poison Tooth also remains guarded pending its ordering ruling. Opposing hidden No-Field values are not inspected.',
'The plan editor checks dial/support against every public opposing force possibility, never a hidden plan or No-Field value. Winner-changing physical allocations remain guarded. Clean selected CHOAM/Ix profiles now reuse the existing provisional original Stone-first, then uncopied Tooth order; Mirror copies still choose before their originals as printed. The earlier priority question remains unanswered.',
'Clean Ix admission excludes Homeworlds, Nexus, Leader Skills, Discoveries, active Sandtrout, Tech Tokens and Stronghold Cards. Native Ecaz/Moritani combinations remain Advanced-only. Other non-Ix profile behavior and full expansion/public-start gates are unchanged.',
'Traitor and Lasgun–shield explosion precedence remain intact. Ordinary bounty applies unless another effect suppresses it. Stone Burner uses the general winning battle-card retention rule: a winner may keep it, and a losing played card is discarded. Normal Moritani ally retention may apply.',
'The post-reveal choice is bound to the current battle event and cannot be repeated after refresh. Review both revealed plans and select one mode; there is no separate victim or death confirmation.',
],
@@ -1158,6 +1159,8 @@ export const RULE_TOPICS: RuleTopic[] = [
'tests/stone-harass-runtime.test.ts',
'tests/ecaz-occupy-harass-quote.test.ts',
'tests/stone-reinforcements.test.ts',
'tests/ix-revealed-weapons.test.ts',
'tests/fixture-ix-revealed-weapons.ts',
],
},
],
@@ -1184,9 +1187,9 @@ export const RULE_TOPICS: RuleTopic[] = [
: definition.card.effect === 'residualPoison'
? 'Use the Residual Poison panel before either combatant commits a leader. A shared preparation step provides an opportunity regardless of hand contents; advanced Harkonnen tables remain guarded pending the secret-captive ruling. In the explicit Semuta preview, a clean death with no readiness or inspection commitments pauses after public disposal for the neutral offer, then resumes the same preparation.'
: definition.card.effect === 'stoneBurner'
? 'Use Stone Burner in the battle weapon selector and review the chosen dial/support guard before sealing. Choose the leader effect after revelation; combined timing remains guarded.'
? 'Use Stone Burner in the weapon selector and review public dial/support feasibility before sealing. Clean selected CHOAM/Ix now uses visibly provisional original Stone-first then uncopied Tooth order; copy-first and physical allocation guards remain.'
: definition.card.effect === 'mirrorWeapon'
? 'Choose Mirror Weapon in the battle weapon slot in a supported CHOAM/Richese-deck table. Its attack copies the opposing revealed weapon; copied Tooth or Stone choices resolve first and independently. Under the user-selected interpretation, a winning physical Mirror may be retained even after a copied Tooth or Artillery attack. Full Richese starts and combined modules remain guarded.'
? 'Choose physical Mirror in a supported CHOAM/Richese-deck table, including clean selected Ix and Advanced Ecaz/Moritani. Copied Tooth or Stone chooses before its original. The user-selected winning physical-Mirror retention still applies after copied Tooth or Artillery. Original Stone/Tooth provisionally uses Stone-first order. Other profile/module and public-start gates remain.'
: definition.card.effect === 'portableSnooper'
? 'Choose Portable Snooper as your ordinary poison defense, or use the late-defense panel after reveal before your own traitor decision. It uses ordinary winner retention and cannot stop Poison Tooth.'
: definition.card.effect === 'semutaDrug'
@@ -1321,7 +1324,25 @@ export const RULE_TOPICS: RuleTopic[] = [
},
],
}
: {}),
: definition.card.effect === 'mirrorWeapon'
? { checklist: [
{ area: 'Implementation' as const, status: 'Partial' as const,
detail: 'Canonical physical Mirror, copy-first answers and physical retention compose with clean selected Ixian/Tleilaxu pools and original Advanced Occupy. Public feasibility and other profile/module boundaries remain.',
evidence: ['game/mirror-weapon-mode.ts', 'game/effective-weapons.ts', 'game/engine.ts'] },
{ area: 'Player controls' as const, status: 'Partial' as const,
detail: 'Actual weapon selectors, independent copied choices and original retention are usable. Ix original Stone/Tooth priority is visibly provisional before commitment and during revelation.',
evidence: ['components/battle-preparation.tsx', 'components/game-table.tsx'] },
{ area: 'AI' as const, status: 'Partial' as const,
detail: 'All four profiles have legal clean-Ix candidate and original/copy continuation paths. This is not strategy or difficulty calibration.',
evidence: ['game/bots.ts', 'tests/ix-revealed-weapons.test.ts'] },
{ area: 'Documentation' as const, status: 'Partial' as const,
detail: 'Printed copy-first, user-selected physical retention and provisional original Stone/Tooth priority are distinguished. Combined modules and full-mode acceptance remain separate.',
evidence: ['docs/MIRROR_WEAPON_ENGINE_AUDIT.md', 'docs/STONE_BURNER_ENGINE_AUDIT.md'] },
{ area: 'Verification' as const, status: 'Partial' as const,
detail: 'Original Matrix/engine proof plus genuine native setup with explicit conserved battle staging covers Basic/Advanced, both native force pools, original/copy modes, public ambiguity and Advanced fixed/variable Occupy. Mobile and policy proof is bounded, not unstaged complete-game or final assurance certification.',
evidence: ['tests/ix-revealed-weapons.test.ts', 'tests/fixture-ix-revealed-weapons.ts', 'tests/mirror-weapon-engine.test.ts'] },
] }
: {}),
related: [
'richese-cards',
'choam-modules',
+65
View File
@@ -0,0 +1,65 @@
import assert from 'node:assert/strict';
import {applyAction, viewGame, type Action, type Game} from '../game/engine';
import {harassWithdrawGame} from './fixture-harass-withdraw';
import {stoneHarassCustody, takeStoneHarassCard, STONE_CARD, MIRROR_CARD} from './fixture-stone-harass';
import {finishOccupyCardsAftermath} from './fixture-ecaz-occupy-cards';
export const IX_TOOTH = 'ix-poison-tooth';
export type IxRevealedCase = {
game: Game;
native: 'ixians' | 'tleilaxu';
holder: 'a' | 'd';
other: 'a' | 'd';
kind: 'mirrorStone' | 'mirrorTooth' | 'stoneTooth';
plans: {actor: string; action: Action}[];
};
/** Original native Ix/Richese setup and exact deck, then the upstream fixture's
* explicit phase/board staging. Actual chooseBattle owns every battle control;
* this is a conserved rule fixture, not an unstaged complete-game claim. */
export function ixRevealedWeaponFixture({advanced = false, native = 'ixians', holder = 'a',
kind = 'mirrorStone'}: {advanced?: boolean; native?: 'ixians' | 'tleilaxu';
holder?: 'a' | 'd'; kind?: IxRevealedCase['kind']} = {}): IxRevealedCase {
const game = harassWithdrawGame({advanced, factions: [native, 'atreides', 'richese'],
normal: native === 'ixians' ? 2 : 4, elite: native === 'ixians' ? 2 : 0,
territory: 'shield_wall', sector: 8});
for (const player of game.players) {
game.deck.push(...player.hand);
player.hand = [];
}
const other = holder === 'a' ? 'd' : 'a';
const holderCard = kind === 'stoneTooth' ? STONE_CARD : MIRROR_CARD;
const sourceCard = kind === 'mirrorStone' ? STONE_CARD : IX_TOOTH;
takeStoneHarassCard(game, holder, holderCard);
takeStoneHarassCard(game, other, sourceCard);
const plans = (['a', 'd'] as const).map(actor => ({actor, action: {
// Full ordinary commitment remains preflight-safe against every opposing
// mixed Ix allocation; a hidden already-sealed zero dial is not a shortcut.
type: 'battlePlan', dial: actor === 'a' ? 0 : 5,
support: advanced && actor === 'd' ? 5 : 0,
leader: game.players.find(player => player.id === actor)!.leaders[0].id,
weapon: actor === holder ? holderCard : sourceCard,
} satisfies Action}));
stoneHarassCustody(game);
return {game, native, holder, other, kind, plans};
}
export function revealIxWeapons(source: IxRevealedCase): Game {
let game = source.game;
for (const {actor, action} of source.plans) game = applyAction(game, actor, action);
assert.equal(game.battle?.revealed, true);
return game;
}
export function finishIxWeaponBattle(state: Game): Game {
let game = state;
assert.equal(game.decision, null, 'All native reveal choices must precede traitor calls.');
for (const id of viewGame(game, 'a').battle!.traitorVoters)
game = applyAction(game, id, {type: 'traitorCall', call: false});
game = finishOccupyCardsAftermath(game, {stopAtFaceDance: true});
if (game.decision?.kind === 'faceDance') {
game = applyAction(game, game.decision.player, {type: 'decision', reveal: false});
game = finishOccupyCardsAftermath(game);
}
assert.equal(game.battle, null);
assert.equal(game.decision, null);
stoneHarassCustody(game);
return game;
}
+10 -5
View File
@@ -78,7 +78,7 @@ export function stoneHarassCustody(game: Game): void {
...(player.faceDancers ?? []).map(card => card.leader)]),
].sort(), traitorDeck(game.players, game.expansions.includes('ix')).sort());
for (const player of game.players) {
// Both native fixture rosters use four-card hands.
// All admitted native fixture rosters use four-card hands.
assert.ok(player.hand.length <= 4, 'Conserved card staging must also respect native hand capacity.');
assert.equal(player.reserves + player.tanks +
Object.values(player.forces).reduce((sum, amount) => sum + amount, 0), 20);
@@ -273,6 +273,7 @@ export function finishStoneHarassBattle(state: Game, options: {
export type StoneHarassOccupyOptions = {
lead?: 'ecaz' | 'ally';
allyFaction?: 'guild' | 'ixians' | 'tleilaxu';
/** Stage Harass in the selected lead's plan; false keeps the same card in hand. */
harass?: boolean;
stone?: 'opponent' | 'harass';
@@ -281,6 +282,7 @@ export type StoneHarassOccupyOptions = {
support?: number;
ecazForces?: number;
allyForces?: number;
allyElite?: number;
opponentForces?: number;
opponentElite?: number;
opponentDial?: number;
@@ -301,17 +303,20 @@ export function stoneHarassOccupyFixture(
options: StoneHarassOccupyOptions = {},
): StoneHarassOccupyCase {
const lead: 'ecaz' | 'ally' = options.lead ?? 'ecaz';
const allyFaction = options.allyFaction ?? 'guild';
const expansions: Game['expansions'] = ['ecaz', 'choam', ...(allyFaction === 'guild' ? [] : ['ix'] as const)];
// The original four-seat advanced lobby supplies Ecaz, its real ally, a
// classic opponent and Richese's own card cache; every later step is native.
let initial = createGame('STONEOCCUPY', newPlayer('ecaz', 'ecaz', 'ecaz'), true, ['ecaz', 'choam']);
for (const faction of ['guild', 'emperor', 'richese'] as const)
let initial = createGame('STONEOCCUPY', newPlayer('ecaz', 'ecaz', 'ecaz'), true, expansions);
for (const faction of [allyFaction, 'emperor', 'richese'] as const)
joinGame(initial, newPlayer(faction, faction, faction));
for (const player of initial.players) initial = applyAction(initial, player.id, { type: 'ready' });
initial.ecazTreachery = true;
initial = initializeEcazOccupyGameForAudit(initial);
const occupy = ecazOccupyFixture({ initial, expansions: ['ecaz', 'choam'], ecazTreachery: true,
allyFaction: 'guild', opponentFaction: 'emperor',
const occupy = ecazOccupyFixture({ initial, expansions, ecazTreachery: true,
allyFaction, opponentFaction: 'emperor',
ecazForces: options.ecazForces ?? 5, allyForces: options.allyForces ?? 4,
...(options.allyElite === undefined ? {} : { allyElite: options.allyElite }),
...(options.opponentForces === undefined ? {} : { opponentForces: options.opponentForces }),
...(options.opponentElite === undefined ? {} : { opponentElite: options.opponentElite }) });
// Isolate selected battle cards after genuine setup/auctions, before the
+131
View File
@@ -0,0 +1,131 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {applyAction, viewGame} from '../game/engine';
import {mirrorWeaponModeBlock} from '../game/mirror-weapon-mode';
import {STONE_CARD, MIRROR_CARD, stoneHarassOccupyFixture, takeStoneHarassCard,
sealStoneHarassPlans, finishStoneHarassBattle} from './fixture-stone-harass';
import {IX_TOOTH, ixRevealedWeaponFixture, revealIxWeapons,
finishIxWeaponBattle} from './fixture-ix-revealed-weapons';
void test('native Basic and Advanced Ix or Tleilaxu copied Stone keeps copy-first choices and physical custody in either slot', () => {
for (const advanced of [false, true]) for (const native of ['ixians', 'tleilaxu'] as const)
for (const holder of ['a', 'd'] as const) {
const source = ixRevealedWeaponFixture({advanced, native, holder, kind: 'mirrorStone'});
let game = revealIxWeapons(source);
assert.equal(game.decision?.kind, 'stoneBurner');
assert.equal(game.decision?.player, holder);
assert.equal(game.decision?.physicalId, MIRROR_CARD);
assert.equal(game.decision?.copiedFrom, STONE_CARD);
game = applyAction(game, holder, {type: 'decision', event: game.decision!.event, mode: 'ignore'});
assert.equal(game.decision?.kind, 'stoneBurner');
assert.equal(game.decision?.player, source.other);
game = applyAction(game, source.other, {type: 'decision', event: game.decision!.event, mode: 'kill'});
game = finishIxWeaponBattle(game);
assert.equal(game.lastBattleContext?.winner, 'a', 'four native undialed counters beat zero ordinary counters');
assert.equal(game.players.find(player => player.id === holder)!.hand.some(card => card.id === MIRROR_CARD), holder === 'a');
assert.equal(game.discard.filter(card => card.id === MIRROR_CARD).length, holder === 'a' ? 0 : 1);
for (const actor of ['a', 'd']) {
const selected = source.plans.find(step => step.actor === actor)!.action.leader;
assert.equal(game.players.find(player => player.id === actor)!.leaders.find(leader => leader.id === selected)!.dead, true);
}
}
});
void test('native Ix-deck Mirror activates copied Tooth first without converting physical Mirror into a mandatory discard', () => {
for (const advanced of [false, true]) for (const native of ['ixians', 'tleilaxu'] as const)
for (const holder of ['a', 'd'] as const) {
const source = ixRevealedWeaponFixture({advanced, native, holder, kind: 'mirrorTooth'});
let game = revealIxWeapons(source);
assert.equal(game.decision?.kind, 'poisonTooth');
assert.equal(game.decision?.player, holder);
assert.equal(game.decision?.physicalId, MIRROR_CARD);
game = applyAction(game, holder, {type: 'decision', event: game.decision!.event, activate: true});
assert.ok(game.decision?.kind === 'poisonTooth');
assert.equal(game.decision?.player, source.other);
assert.equal(game.decision?.physicalId, IX_TOOTH);
game = applyAction(game, source.other, {type: 'decision', event: game.decision!.event, activate: false});
assert.equal(game.battle!.poisonTooth![holder], true);
assert.equal(game.battle!.poisonTooth![source.other], false);
game = finishIxWeaponBattle(game);
assert.equal(game.lastBattleContext?.winner, 'd', 'after both leaders die the original ordinary dial5 beats native zero');
assert.equal(game.players.find(player => player.id === holder)!.hand.some(card => card.id === MIRROR_CARD), holder === 'd');
assert.equal(game.players.find(player => player.id === source.other)!.hand.some(card => card.id === IX_TOOTH), source.other === 'd');
for (const actor of ['a', 'd']) {
const selected = source.plans.find(step => step.actor === actor)!.action.leader;
assert.equal(game.players.find(player => player.id === actor)!.leaders.find(leader => leader.id === selected)!.dead, true);
}
}
});
void test('original Ix-deck Stone precedes uncopied Tooth under the existing provisional policy, without forcing Tooth activation', () => {
for (const advanced of [false, true]) for (const native of ['ixians', 'tleilaxu'] as const)
for (const holder of ['a', 'd'] as const) {
const source = ixRevealedWeaponFixture({advanced, native, holder, kind: 'stoneTooth'});
let game = revealIxWeapons(source);
assert.equal(game.decision?.kind, 'stoneBurner');
assert.equal(game.decision?.player, holder);
game = applyAction(game, holder, {type: 'decision', event: game.decision!.event, mode: 'kill'});
assert.equal(game.decision?.kind, 'poisonTooth');
assert.equal(game.decision?.player, source.other);
game = applyAction(game, source.other, {type: 'decision', event: game.decision!.event, activate: false});
game = finishIxWeaponBattle(game);
assert.equal(game.lastBattleContext?.winner, 'a');
assert.equal(game.players.find(player => player.id === source.other)!.hand.some(card => card.id === IX_TOOTH), source.other === 'a');
assert.equal(game.discard.filter(card => card.id === IX_TOOTH).length, source.other === 'a' ? 0 : 1);
}
});
void test('Ix public precommit still rejects winner-changing cyborg allocations independently of the hidden opposing plan', () => {
const source = ixRevealedWeaponFixture({advanced: true, kind: 'stoneTooth'});
const own = {...source.plans[0].action, dial: 2, support: 0};
const messages: string[] = [];
for (const dial of [0, 5]) {
const game = applyAction(source.game, 'd', {...source.plans[1].action, dial, support: dial});
const before = structuredClone(game);
assert.throws(() => applyAction(game, 'a', own), error => {
assert.match(String(error), /change the winner/);
messages.push(String(error));
return true;
});
assert.deepEqual(game, before);
}
assert.equal(messages[0], messages[1]);
});
void test('new Ix admission leaves optional-module and Basic Ecaz limits closed while retaining the existing non-Ix band', () => {
const source = ixRevealedWeaponFixture().game;
assert.equal(mirrorWeaponModeBlock(source), null);
for (const moduleName of ['homeworlds', 'nexusCards', 'leaderSkills', 'discoveries', 'techTokens', 'strongholdCards'] as const)
assert.notEqual(mirrorWeaponModeBlock({...source, [moduleName]: {}}), null);
assert.notEqual(mirrorWeaponModeBlock({...source, sandtrout: true}), null);
assert.notEqual(mirrorWeaponModeBlock({...source, expansions: ['ix']}), null);
assert.notEqual(mirrorWeaponModeBlock({...source, expansions: ['ix', 'choam', 'ecaz'], advanced: false}), null);
assert.equal(mirrorWeaponModeBlock({expansions: ['choam'], advanced: true,
players: [{faction: 'emperor'}, {faction: 'richese'}, {faction: 'guild'}], techTokens: {}, strongholdCards: {}}), null);
});
void test('native Ixian or Tleilaxu Occupy keeps fixed remainder, copied Stone queue and actual card-user reserve cost separate', () => {
for (const allyFaction of ['ixians', 'tleilaxu'] as const) for (const lead of ['ecaz', 'ally'] as const) {
const source = stoneHarassOccupyFixture({allyFaction, lead, harass: false, stone: 'opponent', mirror: true,
variableDial: 0, support: 0, allyForces: 4, ...(allyFaction === 'ixians' ? {allyElite: 2} : {}), opponentForces: 5});
takeStoneHarassCard(source.game, source.lead, 'ecaz-reinforcements');
source.plans.find(step => step.actor === source.lead)!.action.defense = 'ecaz-reinforcements';
const originalReserves = source.game.players.find(player => player.id === source.lead)!.reserves;
// Natural Storm changes public tie priority. Make the original Stone
// finishable for every mixed allocation, never just the sealed zero dial.
const opponentPlan = source.plans.find(step => step.actor === source.opponent)!.action;
const fullCommitment = viewGame(source.game, source.opponent).battle!.tieWinner !== source.opponent;
opponentPlan.dial = fullCommitment ? 5 : 0;
opponentPlan.support = fullCommitment ? 5 : 0;
let game = sealStoneHarassPlans(source);
assert.equal(game.decision?.player, source.lead, 'the selected lead physically owns Mirror and chooses the copy first');
game = finishStoneHarassBattle(game, {modes: {[source.lead]: 'ignore', [source.opponent]: 'ignore'}});
assert.equal(game.lastBattleContext?.winner, source.lead, 'the public-safe original plan leaves fewer undialed counters than the coalition');
assert.equal(game.players.find(player => player.id === source.lead)!.hand.some(card => card.id === MIRROR_CARD), true);
assert.equal(game.players.find(player => player.id === source.ecaz)!.tanks, lead === 'ecaz' ? 6 : 3);
assert.equal(game.players.find(player => player.id === source.ally)!.tanks, lead === 'ally' ? 3 : 0);
assert.equal(game.players.find(player => player.id === source.lead)!.reserves, originalReserves - 3);
assert.equal(game.discard.filter(card => card.id === 'ecaz-reinforcements').length, 1);
assert.equal(viewGame(game, source.lead).battle, null);
}
});
+1 -6
View File
@@ -128,7 +128,7 @@ const inventory = (g: Game) =>
.map((c) => c.id)
.sort();
void test('forged Stone role and public Ix timing reject before any hidden plan commitment', () => {
void test('forged Stone roles reject before any hidden plan commitment', () => {
for (const patch of [
{ id: 'forged-stone' },
{ name: 'Other Stone' },
@@ -145,11 +145,6 @@ void test('forged Stone role and public Ix timing reject before any hidden plan
);
assert.deepEqual(g, before);
}
let ix = fixture();
ix.expansions.push('ix');
ix = prepare(start(ix));
assert.throws(() => plan(ix, 'r'), /Ix expansion/);
assert.deepEqual(ix.battle!.plans, {});
});
void test('Advanced Emperor outcome-changing allocation is rejected identically before or after an unrelated hidden opposing plan', () => {