diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md index 37c4fc45..0edee8fc 100644 --- a/docs/CURRENT_STATUS.md +++ b/docs/CURRENT_STATUS.md @@ -17,6 +17,15 @@ open mode gates. ## Current checkpoint and work +**Basic Fremen and Ecaz final victory — 29 October. Prototyped / Partial.** +The documented provisional interpretation permits allied Fremen and Ecaz to +co-occupy Habbanya, as well as Sietch Tabr, for Fremen's final-turn victory. +Solitary or nonallied Ecaz and third-party fighters remain blockers. The real +victory path and public quote are exercised using staged final-turn forces; +24 related cases, typecheck, lint and the production build pass. Full rules +and deployed acceptance remain open. +[Source and scope](ECAZ_VICTORY_RULES.md#basic-two-sietch-co-occupation--29-october-2026). + **Basic independent cards, Kull and Residual Poison — 29 October. Prototyped / Partial.** Supported fresh native Basic rosters can enable printed Kull with independent Ecaz Treachery Cards. Focused native Ecaz/Ixian journeys diff --git a/docs/ECAZ_VICTORY_RULES.md b/docs/ECAZ_VICTORY_RULES.md index 1ee9e2c5..0313588d 100644 --- a/docs/ECAZ_VICTORY_RULES.md +++ b/docs/ECAZ_VICTORY_RULES.md @@ -1,8 +1,26 @@ # Ecaz victory implementation contract -Basic retains the [14 September Tabr-only runtime](#fremen-final-turn-runtime-follow-up). -Advanced now follows the authorized supplied revision's physical p32 allowance -in both protected sietches. The publisher audit below remains historical context. +Basic provisionally permits reciprocal Fremen/Ecaz co-occupation in either +protected sietch, adopting the recommended symmetry described below. The +publisher's worked FAQ mentions only Tabr; Habbanya is an inference, not a +publisher ruling. Advanced follows the authorized supplied revision's physical +p32 allowance in both. The older runtime and source audits remain historical. + +## Basic two-sietch co-occupation — 29 October 2026 + +The common Fremen special-victory quote now permits an allied Ecaz presence +alongside Fremen in Habbanya as well as Sietch Tabr. This chooses the existing +recommended Basic interpretation under the user's direction to implement +recommended options, rather than treating the publisher FAQ's Tabr example as +a prohibition elsewhere. Each territory still requires fighters of both current +allies; solitary or nonallied Ecaz and third factions remain blockers. Ordinary +victory precedence, Tuek restrictions and Guild fallback are unchanged. + +The existing [quote tests](../tests/fremen-victory.test.ts) cover Basic Habbanya +and absence of Fremen; the [final-turn engine cases](../tests/fremen-ecaz-victory-engine.test.ts) +stage conserved forces after setup and resolve the actual readiness/victory +path, including both modes and the existing negative cases. No mode-wide +acceptance is implied. ## Authorized Advanced two-sietch allowance — 8 October 2026 @@ -11,20 +29,21 @@ Fremen I**, permits “Fremen forces (± coexisting Ecaz forces) or no one” in **Sietch Tabr and Habbanya Sietch**. This is an explicit Advanced source cutover, not a newly located publisher symmetry ruling. -The shared `fremenSpecialVictory` quote now ignores reciprocal allied +The shared `fremenSpecialVictory` quote ignores reciprocal allied Ecaz cooccupation in either protected sietch when both factions actually -have fighters there. Basic stays Tabr-only. Solitary/nonallied Ecaz and +have fighters there. Basic's later provisional extension is described above. Solitary/nonallied Ecaz and third-faction fighters still block; other Tuek/final-turn/ordinary-winner/ prediction/fallback conditions do not change. Public progress and real victory evaluation consume the same quote without another decision. One native Collection-to-Mentat smoke reproduces the old Guild fallback, -then awards Fremen/Ecaz on the same Advanced board while preserving -Basic and solitary-Ecaz exclusions. Three Habbanya contract cases pass. +then awarded Fremen/Ecaz on the same Advanced board while preserving the +then-current Basic boundary and solitary-Ecaz exclusions. Three Habbanya +contract cases passed at that checkpoint. No broad suite or deployed acceptance was run. -**Superseded for Advanced:** the Habbanya rows below predate the authorized -supplied p32 cutover above. Basic still follows the Tabr-only audit. +**Superseded:** the historical implementation boundary below predates the +Advanced source cutover and the later provisional Basic extension above. Primary-source review, 2026-09-07. This separates the explicit three-stronghold rule from its ordinary control and prediction consequences. No combat-rounding decision is needed for a victory predicate operating on an already-settled board. The unrelated rounding conflict remains as corrected in [Ecaz Occupy rules](ECAZ_OCCUPY_RULES.md). diff --git a/docs/RULE_DECISIONS.md b/docs/RULE_DECISIONS.md index fe43730a..a62a7ccd 100644 --- a/docs/RULE_DECISIONS.md +++ b/docs/RULE_DECISIONS.md @@ -3228,7 +3228,7 @@ dedicated retrieved worked FAQs. [Contract and remaining limits](HOMEWORLD_OCCUP | Moritani Advanced assassination preview | After an eligible battle loss, a different opposing-faction Traitor Card may be revealed even if its leader is already dead (zero bounty). Face-up set-aside and one private replacement occur during Mentat; one use per faction. Normal-call duration, uniform hidden-eligibility UX and exceptional combinations remain explicit. | [Runtime/source contract](MORITANI_ASSASSINATE_LEADERS.md) | | Moritani Extortion at Mentat | The user selected normal Terror placement/relocation first, including any Karama response; only then collect the reserved five bank spice and approach other players in storm order for a possible three-spice payment. Payment removes the token; all declines recover it with rotated hidden supply IDs. The relative placement timing is a user-selected interpretation, not a publisher ruling. | [Publisher sequence and selected timing](MORITANI_EXTORTION_RULES.md#mentat-ordering-selected-interpretation-not-a-publisher-ruling) | | Ecaz Ambassador / Moritani Terror shared entry | Bounded classic plus Ecaz/Moritani Basic/Advanced, native paired Skills with optional independent cards and the original paired Nexus envelopes retain both originally eligible owners in current storm order; committed entry/payment/counters do not replay. Bare card-only factions need no Skills. Later alliances do not erase the trigger; physical effect prerequisites are rechecked. This remains provisional, not publisher priority. Genuine BG fighters qualify independently; advisors are exempt. Entry waits for its own income, Intrusion/advisor or storm controls; actual in-flight and unsupported effect/module/child guards remain. | [Source gap and selected policy](MORITANI_ENTRY_TIMING.md#selected-bounded-overlap-order--29-september-2026), [paired native Skills/cards](MORITANI_ENTRY_TIMING.md#paired-skills-and-independent-card-entries--17-october-2026), [original Nexus](MORITANI_ENTRY_TIMING.md#paired-native-nexus-composition--7-october-2026), [deferred entry](MORITANI_ENTRY_TIMING.md#deferred-arrival-entry-behind-the-arrivals-own-interactions--7-october-2026) | -| Fremen/Ecaz final-turn victory | Basic keeps reciprocal cooccupation at Tabr only. Authorized supplied Advanced physical p32 Fremen I permits both Tabr and Habbanya when Fremen/Ecaz actually coexist. Solitary/nonallied Ecaz and third parties still block; ordinary winner/prediction/fallback order is preserved. | [Current source cutover](ECAZ_VICTORY_RULES.md) | +| Fremen/Ecaz final-turn victory | Basic provisionally extends the publisher's Tabr cooccupation example to Habbanya under the documented recommendation. Authorized supplied Advanced physical p32 Fremen I permits both Tabr and Habbanya when Fremen/Ecaz actually coexist. Solitary/nonallied Ecaz and third parties still block; ordinary winner/prediction/fallback order is preserved. | [Current source cutover](ECAZ_VICTORY_RULES.md) | | Truthtrance recorded knowledge | Questions compare an already recorded BG prediction, active submitted storm dial or actually known native Fremen forecast. Missing knowledge is unknown; only the aggregate answer is published. No future promise or unknown deck peek is created. | [Connected predicates and evidence](TRUTHTRANCE_KNOWLEDGE.md) | | Nexus lifecycle | Twelve physical cards, phase draw/replacement and alliance forfeiture; native Cunning, absent-faction Secret Ally and Betrayal have separate effects. No blanket Karama immunity. | [Common source contract](NEXUS_CARD_RULES.md), [lifecycle runtime](NEXUS_CARD_RUNTIME.md) | | Nexus faction effects | Preserve physical custody, original faction identity and durable effect receipts; use each printed family instead of borrowing another faction's rules by analogy. | [Atreides](NEXUS_ATREIDES_RULES.md), [Harkonnen](NEXUS_HARKONNEN_RULES.md), [BG](NEXUS_BENE_GESSERIT_RULES.md), [Emperor](NEXUS_EMPEROR_RULES.md), [Fremen](NEXUS_FREMEN_RULES.md), [Guild](NEXUS_GUILD_RULES.md) | diff --git a/game/engine.ts b/game/engine.ts index 59324f72..22708662 100644 --- a/game/engine.ts +++ b/game/engine.ts @@ -30630,7 +30630,7 @@ function victory(g: Game, quote?: VictoryQuote, resume: VictoryContinuationResum const fremenWon = fremen?.qualifies && fremen.members.length === g.winner.length && fremen.members.every((id) => g.winner.includes(id)); const specialReason = fremenWon - ? ` Fremen special victory: both protected sietches meet the occupation condition and no prohibited faction occupies Tuek’s Sietch.${fremen.sietches.some((site) => site.ecazCooccupation) ? ' Allied Ecaz and Fremen may co-occupy Sietch Tabr without blocking this victory.' : ''}` + ? ` Fremen special victory: both protected sietches meet the occupation condition and no prohibited faction occupies Tuek’s Sietch.${fremen.sietches.some((site) => site.ecazCooccupation) ? ` Allied Ecaz and Fremen co-occupy ${fremen.sietches.filter(site => site.ecazCooccupation).map(site => territory(site.territory).name).join(' and ')} without blocking this victory.` : ''}` : ''; log( g, diff --git a/game/faction-reference.ts b/game/faction-reference.ts index f6e02894..d0f2a5e8 100644 --- a/game/faction-reference.ts +++ b/game/faction-reference.ts @@ -14,7 +14,7 @@ export const FACTION_RULES: Record< 'Use Kulon on your movement turn to add one territory of range. Use La La La during Revival to prevent a player’s free force returns for the phase; a reactive decision lets you stop a pending free revival before it resolves. Both cards have Karama responses before discard.', 'Trip to Gamont returns one other player’s force from a chosen sector to its reserves during Mentat. Elite types are preserved. A Karama response precedes the return, and CHOAM gets a final opportunity after the closing market before victory is checked.', 'Baliset prevents a player from moving into a territory occupied by CHOAM for the turn, while permitting shipment. Jubba Cloak protects CHOAM forces in one territory from a moving storm. Both powers have Karama responses before the card is discarded.', - 'New supported Basic lobbies with CHOAM and the CHOAM and Ix decks enable the Kull preview by default; the host may turn it off. Each selected faction needs its deck, and other optional modules remain outside this profile. React to a legal Karama attempt with held Kull Wahad before costs. The original card stays reserved; only a different eligible Karama can counter before the phase ban applies. Success spends Kull, retains the original unplayed card and prevents the activating player’s Karama uses for the phase. Decline or prevention resumes the original once; a prevented Kull stays held but its physical card’s effect is blocked that phase. With Richese, supported Kull and counter discards offer Semuta, including when a paid ordinary or cache sale is waiting for Harkonnen’s bonus. Ordinary bribe, Revival and alliance, simple and mixed current-lot bid, and phase-wide bid questions are supported. Combined turn-action questions are available in the supported Basic profile, with some Karama uses still guarded. Unfunded non-CHOAM Karama-dependent overbids remain deferred; see the Kull guide. Complete CHOAM rules remain unfinished.', + 'New supported Basic lobbies with CHOAM and the CHOAM and Ix decks enable the Kull preview by default; the host may turn it off. Each selected faction needs its deck. Independent Ecaz Treachery Cards are supported; other optional modules remain outside this profile. React to a legal Karama attempt with held Kull Wahad before costs. The original card stays reserved; only a different eligible Karama can counter before the phase ban applies. Success spends Kull, retains the original unplayed card and prevents the activating player’s Karama uses for the phase. Decline or prevention resumes the original once; a prevented Kull stays held but its physical card’s effect is blocked that phase. With Richese, supported Kull and counter discards offer Semuta, including when a paid ordinary or cache sale is waiting for Harkonnen’s bonus. Ordinary bribe, Revival and alliance, simple and mixed current-lot bid, and phase-wide bid questions are supported. Combined turn-action questions are available in the supported Basic profile, with some Karama uses still guarded. Unfunded non-CHOAM Karama-dependent overbids remain deferred; see the Kull guide. Complete CHOAM rules remain unfinished.', ], advanced: [ 'Once per game, spend a real Karama and discard selected other cards for three spice each. The cash-in can occur during pending phase or power decisions, while committed battle cards remain protected. See the CHOAM special Karama topic for funding and timing details.', @@ -103,7 +103,7 @@ export const FACTION_RULES: Record< 'Bring reserves to the Great Flat or one territory within two territories of it for no spice, subject to storm and occupancy restrictions. Ordinary movement has range two, or three when eligible for ornithopters.', 'Worms do not devour Fremen. After the Nexus, move some or all forces from the worm territory to a legal destination. The destination’s existing forces survive.', 'You choose whether a worm spares your ally and whether to grant your ally three free force revivals. These benefits are optional.', - 'Basic final-turn victory requires protected sietches empty or occupied by Fremen and no Atreides/Harkonnen/Emperor in Tuek. Reciprocal allied Ecaz cooccupation is permitted at Sietch Tabr only; other conditions remain. Advanced follows the separate supplied two-sietch allowance below.', + 'Basic final-turn victory requires protected sietches empty or occupied by Fremen and no Atreides/Harkonnen/Emperor in Tuek. Reciprocal allied Ecaz may co-occupy either protected sietch with Fremen; allowing Habbanya is a provisional extension of the printed Sietch Tabr example. Other conditions remain.', ], advanced: [ 'Once per game during Spice Blow and Nexus, spend Karama to call Shai-Hulud in sand. Resolve destruction and optional protections immediately; Nexus follows at the end of the blow, with eligible Fremen rides afterward. The called worm consumes no spice-deck card.', @@ -170,7 +170,7 @@ export const FACTION_RULES: Record< 'After Revival, place Ambassadors in eligible strongholds, paying one spice for the first, two for the second and so on that turn. Eligible enemy entry lets you trigger an effect for yourself or your ally. The Ambassador guide explains each identity and its choices.', 'Your Ecaz Ambassador can acquire an available Duke Vidal or offer an alliance when both participants are unallied. Duke availability, loans and custody use his separate rules.', 'You and your ally may share territories. Divide shared desert spice by agreement; without agreement, divide as equally as possible and give the odd spice to your ally. Both factions occupying each of three qualifying strongholds provides an allied victory route.', - 'When you are allied with Fremen, sharing Sietch Tabr does not itself prevent the Fremen special victory. Its other conditions still apply.', + 'When you are allied with Fremen, sharing Sietch Tabr or Habbanya Sietch does not itself prevent the Fremen special victory. The Basic Habbanya allowance is provisional; the other victory conditions still apply.', 'Basic combined Occupy, the Tleilaxu Ambassador first-death leader alternative and the new-ally Duke loan now have connected first versions, with explicit provisional policies where the source is unresolved. Exceptional custody and competing arrival interactions remain for refinement; this is not complete-mode certification.', ], advanced: [ diff --git a/game/fremen-victory.ts b/game/fremen-victory.ts index 0919ff18..c12563ea 100644 --- a/game/fremen-victory.ts +++ b/game/fremen-victory.ts @@ -18,10 +18,11 @@ export type FremenVictoryProgress = { tueksBlockers: string[]; }; -/** Public settled-board facts. Basic keeps the publisher's Tabr-only Ecaz - * exception; supplied Advanced PDF p32 permits coexisting allies in both - * protected sietches. Timing, earlier winners and Guild fallback remain - * the victory quote's responsibility. */ +/** Public settled-board facts. Both protected sietches admit coexisting + * Fremen and allied Ecaz. Basic's Habbanya extension is a provisional + * symmetric reading; supplied Advanced PDF p32 explicitly permits both. + * Timing, earlier winners and Guild fallback remain the victory quote's + * responsibility. */ export function fremenSpecialVictory( g: BoardContext, ): FremenVictoryProgress | null { @@ -57,7 +58,6 @@ export function fremenSpecialVictory( if (player.id === fremen.id || fighterCount(player, territory) === 0) return false; return !( - (g.advanced || territory === 'sietch_tabr') && ecazCooccupation && player.id === ecaz!.id ); diff --git a/game/reference.ts b/game/reference.ts index c983bba0..4c9e9f66 100644 --- a/game/reference.ts +++ b/game/reference.ts @@ -149,7 +149,7 @@ const phaseDetails = [ 'A Fremen move using the two-territory advantage has a Karama response. Cancellation leaves the forces in place and limits this move to one territory; city ornithopters and a later extra move keep their normal rules.', 'Guild transport moves one exact physical group across the planet or returns it to reserves. Permission and price are separate counter opportunities; common world-to-foreign-world shipment does not require Guild cross/return privilege. Purchased half price grants no new route. Ordinary movement still checks range, storm and stronghold occupancy.', 'You may pass through an ally’s territory on a legal route that ends elsewhere. Ordinary physical reserve shipments in both modes may enter allied territory before movement, then depart during that turn. Storm and stronghold capacity remain binding. Basic visibly labels its provisional no-exit policy: excess new visitors still present go to Tanks. Advanced follows supplied p15: all the ending player’s shared forces go to Tanks, including old forces, subject to advisor/Polar/Ecaz exemptions. Neither grants an extra move. Concealed No-Fields and optional arrivals remain separate.', - 'Ecaz and its reciprocal ally may share territories and count as one faction toward stronghold capacity, without extra movement or storm/Homeworld exceptions. Their three-joint-stronghold victory and desert allocation are connected. Fremen/Ecaz cooccupation permits the Fremen special win in Sietch Tabr in Basic; supplied Advanced p32 permits both Tabr and Habbanya Sietch. Other victory conditions and exceptional combined interactions remain separate.', + 'Ecaz and its reciprocal ally may share territories and count as one faction toward stronghold capacity, without extra movement or storm/Homeworld exceptions. Their three-joint-stronghold victory and desert allocation are connected. Fremen/Ecaz cooccupation permits the Fremen special win in both Sietch Tabr and Habbanya Sietch; the Basic Habbanya allowance is a provisional interpretation. Other victory conditions and exceptional combined interactions remain separate.', 'A restored pending physical shipment is checked again before forces or spice are committed. If its saved turn, force allocation, destination, price or authorized contribution no longer matches the table, it is rejected without changing the saved declaration.', 'Finish shipment and movement explicitly. Any Bene Gesserit free-shipment decision and its response must finish first.', 'In Advanced, finishing your turn sends your own fighters in territories shared with your ally to Tanks, even if your ally acts later or the alliance formed this turn. Polar Sink, Bene Gesserit advisor coexistence in either direction and native Ecaz peaceful coexistence are exempt. The finish warning names affected territories without revealing concealed forces. Loss is mandatory, not a separate confirmation; finishing remains legal if you cannot escape.', @@ -4545,18 +4545,18 @@ export const RULE_TOPICS: RuleTopic[] = [ category: 'Advanced & expansions', coverage: 'Partial', developmentStage: 'Integrated', - summary: 'Allied Ecaz and Fremen may share Sietch Tabr, and in Advanced Habbanya Sietch, while qualifying for the Fremen special victory.', + summary: 'Allied Ecaz and Fremen may share Sietch Tabr or Habbanya Sietch while qualifying for the Fremen special victory.', steps: [ 'At the final-turn victory check, ordinary stronghold victories come first. If none qualifies, Fremen’s special occupation conditions are checked before the Guild fallback. A qualifying Fremen victory includes its ally and is not replaced by a Bene Gesserit prediction.', - 'Ecaz and Fremen must be current reciprocal allies and both have fighters in Sietch Tabr. Their co-occupation does not block the Fremen special condition. Ecaz alone, non-allied Ecaz or another faction’s fighters still block it. Accompanying Bene Gesserit advisors do not count as fighters.', - 'In Basic, Habbanya Sietch must be empty or occupied only by Fremen. In Advanced, reciprocal allied Ecaz and Fremen may also share Habbanya Sietch when both have fighters there. Tuek’s Sietch must contain no Atreides, Harkonnen or Emperor presence, and Advanced play also excludes Richese presence. A concealed No-Field counts without revealing its hidden value.', + 'Ecaz and Fremen must be current reciprocal allies and both have fighters in the same protected sietch. Their co-occupation does not block the Fremen special condition. Ecaz alone, non-allied Ecaz or another faction’s fighters still block it. Accompanying Bene Gesserit advisors do not count as fighters. The Basic Habbanya allowance extends the printed Sietch Tabr example as a provisional interpretation.', + 'Tuek’s Sietch must contain no Atreides, Harkonnen or Emperor presence, and Advanced play also excludes Richese presence. A concealed No-Field counts without revealing its hidden value.', 'The public victory guide lists the three territories and any blocking factions. It uses the same settled-board facts as automatic victory resolution. No extra confirmation is required; the table chronicle explains a successful special victory.', ], checklist: [ - { area: 'Implementation', status: 'Partial', detail: 'Basic Tabr-only and supplied Advanced p32 two-sietch cooccupation allowances share public progress and the real final-turn quote. Broader expansion acceptance remains separate.', evidence: ['game/fremen-victory.ts', 'game/victory-quote.ts', 'game/engine.ts'] }, + { area: 'Implementation', status: 'Partial', detail: 'Basic and supplied Advanced p32 two-sietch cooccupation allowances share public progress and the real final-turn quote. The Basic Habbanya extension is provisional; broader expansion acceptance remains separate.', evidence: ['game/fremen-victory.ts', 'game/victory-quote.ts', 'game/engine.ts'] }, { area: 'Player controls', status: 'Implemented', detail: 'Read-only public territory conditions and blockers; victory resolves at the existing checkpoint without a new decision.', evidence: ['components/victory-progress.tsx', 'components/game-table.tsx'] }, { area: 'AI', status: 'Partial', detail: 'All four profiles reach the automatic outcome through legal final-turn actions. Strategic defense of this allied exception is not separately calibrated.', evidence: ['tests/fremen-ecaz-victory-engine.test.ts'] }, - { area: 'Documentation', status: 'Implemented', detail: 'Publisher Basic Tabr exception and authorized Advanced two-sietch cutover are distinguished.', evidence: ['docs/ECAZ_VICTORY_RULES.md', 'game/reference.ts'] }, + { area: 'Documentation', status: 'Implemented', detail: 'Publisher Basic Tabr exception, provisional Basic Habbanya interpretation and authorized Advanced two-sietch allowance are distinguished.', evidence: ['docs/ECAZ_VICTORY_RULES.md', 'game/reference.ts'] }, { area: 'Verification', status: 'Partial', detail: 'Public presence, precedence, all-profile continuation, JSON and authenticated SQLite recovery are covered. Final-turn positions are explicitly staged after genuine setup.', evidence: ['tests/fremen-victory.test.ts', 'tests/fremen-victory-controls.test.tsx', 'tests/fremen-ecaz-victory-engine.test.ts', 'tests/fremen-ecaz-victory-recovery.test.ts'] }, ], related: ['victory', 'faction-fremen', 'faction-ecaz'], diff --git a/tests/fremen-ecaz-victory-engine.test.ts b/tests/fremen-ecaz-victory-engine.test.ts index bb004fd0..c645fa0e 100644 --- a/tests/fremen-ecaz-victory-engine.test.ts +++ b/tests/fremen-ecaz-victory-engine.test.ts @@ -43,25 +43,24 @@ void test('prospective guide does not award an early victory and a normal winner assert.deepEqual(finishFremenEcazTurn(g).winner, ['gu']); }); -void test('Advanced reciprocal Fremen/Ecaz cooccupation qualifies in both sietches while Basic stays Tabr-only', () => { +void test('reciprocal Fremen/Ecaz cooccupation qualifies in both sietches in Basic and Advanced', () => { for (const advanced of [false, true]) for (const territory of ['sietch_tabr', 'habbanya_ridge_sietch']) { const g = fremenEcazFinalTurn(advanced); const key = territory === 'sietch_tabr' ? 'sietch_tabr:14' : 'habbanya_ridge_sietch:17'; for (const p of g.players.slice(0, 2)) p.forces = { [key]: 3 }; const before = structuredClone(g); - const qualifies = advanced || territory === 'sietch_tabr'; const guide = viewGame(g, 'ec').fremenVictory!; - assert.equal(guide.qualifies, qualifies); + assert.equal(guide.qualifies, true); const sietch = guide.sietches.find((s) => s.territory === territory)!; assert.equal(sietch.ecazCooccupation, true); - assert.deepEqual(sietch.blockers, qualifies ? [] : ['ec']); + assert.deepEqual(sietch.blockers, []); const done = finishFremenEcazTurn(g); - assert.deepEqual(done.winner, qualifies ? ['fr', 'ec'] : ['gu']); + assert.deepEqual(done.winner, ['fr', 'ec']); assert.deepEqual(g, before); assert.deepEqual(fremenEcazCustody(done), fremenEcazCustody(g)); - if (qualifies) assert.match(done.log.at(-1)!.text, /Fremen special victory/); - else assert.doesNotMatch(done.log.at(-1)!.text, /Fremen special victory/); + assert.match(done.log.at(-1)!.text, /Fremen special victory/); + assert.match(done.log.at(-1)!.text, territory === 'sietch_tabr' ? /Sietch Tabr/ : /Habbanya/); } }); diff --git a/tests/fremen-victory.test.ts b/tests/fremen-victory.test.ts index 3ef894d1..4333e001 100644 --- a/tests/fremen-victory.test.ts +++ b/tests/fremen-victory.test.ts @@ -80,23 +80,44 @@ void test('solitary, nonallied and third-party Ecaz presence do not receive the assert.equal(fremenSpecialVictory(third)!.qualifies, false); }); -void test('Basic Ecaz co-occupation at Habbanya remains a blocker', () => { +void test('Basic allied Ecaz co-occupation at Habbanya permits Fremen victory', () => { const g = fixture(); place(g, 'f', 'habbanya_ridge_sietch:17'); place(g, 'e', 'habbanya_ridge_sietch:17'); const result = fremenSpecialVictory(g)!; assert.equal(result.sietches[1].ecazCooccupation, true); - assert.deepEqual(result.sietches[1].blockers, ['e']); - assert.equal(result.qualifies, false); + assert.deepEqual(result.sietches[1].blockers, []); + assert.equal(result.qualifies, true); }); -void test('supplied Advanced Ecaz cooccupation permits Habbanya while preserving Basic and solitary Ecaz blockers', () => { - const g = fixture(true); - place(g, 'f', 'habbanya_ridge_sietch:17'); - place(g, 'e', 'habbanya_ridge_sietch:17'); - assert.equal(fremenSpecialVictory(g)!.qualifies, true); - seat(g, 'f').forces = {}; - assert.deepEqual(fremenSpecialVictory(g)!.sietches[1].blockers, ['e']); +void test('both rulesets keep solitary Ecaz as a Habbanya blocker', () => { + for (const advanced of [false, true]) { + const g = fixture(advanced); + place(g, 'f', 'habbanya_ridge_sietch:17'); + place(g, 'e', 'habbanya_ridge_sietch:17'); + assert.equal(fremenSpecialVictory(g)!.qualifies, true); + seat(g, 'f').forces = {}; + assert.deepEqual(fremenSpecialVictory(g)!.sietches[1].blockers, ['e']); + } +}); + +void test('nonallied Ecaz and third-party fighters still block Habbanya in both rulesets', () => { + for (const advanced of [false, true]) { + const nonallied = fixture(advanced); + seat(nonallied, 'f').ally = null; + seat(nonallied, 'e').ally = null; + place(nonallied, 'f', 'habbanya_ridge_sietch:17'); + place(nonallied, 'e', 'habbanya_ridge_sietch:17'); + assert.deepEqual(fremenSpecialVictory(nonallied)!.sietches[1].blockers, ['e']); + assert.equal(fremenSpecialVictory(nonallied)!.qualifies, false); + + const third = fixture(advanced); + place(third, 'f', 'habbanya_ridge_sietch:17'); + place(third, 'e', 'habbanya_ridge_sietch:17'); + place(third, 'r', 'habbanya_ridge_sietch:17'); + assert.deepEqual(fremenSpecialVictory(third)!.sietches[1].blockers, ['r']); + assert.equal(fremenSpecialVictory(third)!.qualifies, false); + } }); void test('accompanied BG advisors do not block, while mandatory lone-advisor release restores fighter presence', () => {