Enable Reinforcements in clean Ecaz-Moritani battles
This commit is contained in:
1 parent
1083c6ccbf
commit
6ac5dabdee
11 files changed
+180
-28
No files matched your search
+25
-9
@@ -9477,14 +9477,30 @@ function recruitsModeSupported(g: Game) {
|
||||
!g.discoveryEnabled
|
||||
);
|
||||
}
|
||||
function ecazAllyAtBattle(g: Game, side: Player, territoryId: string) {
|
||||
const ally = g.players.find(player => player.id === side.ally && player.ally === side.id);
|
||||
return !!ally && (side.faction === 'ecaz' || ally.faction === 'ecaz') &&
|
||||
Object.entries(ally.forces).some(([key, amount]) => amount > 0 && splitLocation(key).territory === territoryId);
|
||||
}
|
||||
|
||||
function reinforcementsModeSupported(g: Game) {
|
||||
const pairedEcaz = g.expansions.length === 1 && g.expansions[0] === 'ecaz';
|
||||
return !!g.ecazTreachery && recruitsModeSupported(g) && !g.sandtrout &&
|
||||
g.expansions.length === 0 &&
|
||||
g.players.every(player => faction(player.faction).expansion === 'base');
|
||||
(g.expansions.length === 0 || pairedEcaz) &&
|
||||
g.players.every(player => {
|
||||
const expansion = faction(player.faction).expansion;
|
||||
return expansion === 'base' || (pairedEcaz && expansion === 'ecaz');
|
||||
});
|
||||
}
|
||||
function currentReinforcementsCost(g: Game, p: Player) {
|
||||
requireRule(reinforcementsModeSupported(g),
|
||||
'Reinforcements currently requires the standalone Ecaz card variant with classic factions.');
|
||||
'Reinforcements requires the standalone Ecaz card variant with classic or Ecaz/Moritani factions.');
|
||||
if (g.battle) {
|
||||
const other = getPlayer(g, g.battle.attacker === p.id ? g.battle.defender : g.battle.attacker);
|
||||
requireRule(!ecazAllyAtBattle(g, p, g.battle.territory) &&
|
||||
!ecazAllyAtBattle(g, other, g.battle.territory),
|
||||
'Reinforcements with co-present Ecaz allies awaits combined-army battle integration.');
|
||||
}
|
||||
try {
|
||||
return quoteReinforcements(p.reserves - (p.elites?.reserves ?? 0), p.elites?.reserves ?? 0);
|
||||
} catch (error) {
|
||||
@@ -9500,9 +9516,12 @@ function reinforcementsPreview(g: Game, p: Player) {
|
||||
const elite = Math.min(3 - normal, Math.max(0, p.elites?.reserves ?? 0));
|
||||
let blocked: string | null = null;
|
||||
if (!reinforcementsModeSupported(g))
|
||||
blocked = 'Reinforcements currently requires the standalone Ecaz card variant with classic factions.';
|
||||
blocked = 'Reinforcements requires the standalone Ecaz card variant with classic or Ecaz/Moritani factions.';
|
||||
else if (g.status !== 'playing' || g.phase !== 6 || battle.revealed || battle.plans[p.id])
|
||||
blocked = 'Choose Reinforcements before sealing your Battle Plan.';
|
||||
else if (ecazAllyAtBattle(g, p, battle.territory) ||
|
||||
ecazAllyAtBattle(g, getPlayer(g, battle.attacker === p.id ? battle.defender : battle.attacker), battle.territory))
|
||||
blocked = 'Reinforcements with co-present Ecaz allies awaits combined-army battle integration.';
|
||||
else if (normal + elite < 3)
|
||||
blocked = 'Reinforcements needs three physical forces in your reserves.';
|
||||
return { blocked, normal, elite };
|
||||
@@ -15186,11 +15205,8 @@ function harassWithdrawContext(g: Game, p: Player): HarassWithdrawContext {
|
||||
blocked = 'Harass & Withdraw with other optional modules, including Homeworlds, is still being implemented.';
|
||||
else if (g.players.some(player => player.faction === 'richese') || g.richeseCache !== undefined || g.richeseRemoved !== undefined)
|
||||
blocked = 'Harass & Withdraw with the Richese card family awaits the Stone Burner timing ruling.';
|
||||
else if ([p, other].some(side => {
|
||||
const ally = g.players.find(player => player.id === side.ally && player.ally === side.id);
|
||||
return ally && (side.faction === 'ecaz' || ally.faction === 'ecaz') &&
|
||||
Object.entries(ally.forces).some(([key, amount]) => amount > 0 && splitLocation(key).territory === b.territory);
|
||||
})) blocked = 'Harass & Withdraw with co-present Ecaz allies awaits combined-army battle integration.';
|
||||
else if (ecazAllyAtBattle(g, p, b.territory) || ecazAllyAtBattle(g, other, b.territory))
|
||||
blocked = 'Harass & Withdraw with co-present Ecaz allies awaits combined-army battle integration.';
|
||||
else if (p.noField?.deployed?.location.territory === b.territory)
|
||||
blocked = 'Reveal your No-Field before using Harass & Withdraw.';
|
||||
const locations = Object.fromEntries(Object.entries(p.forces)
|
||||
|
||||
+5
-5
@@ -4225,17 +4225,17 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
summary: 'Add two to battle strength while paying three own reserve counters into the Tanks when the physical card is revealed.',
|
||||
steps: [
|
||||
'In the independent Ecaz Treachery Cards variant, place your held Reinforcements card in either battle-card slot; it is neither a weapon nor a defense. A category Prescience answer is None, not the name of this card. One physical card cannot fill both slots.',
|
||||
'Have three own forces in reserves when sealing. The current Basic/Advanced classic-faction prototype spends ordinary reserves first and then elite reserves, moving exactly three to their corresponding Tanks subpools when the battle resolves. No extra forces arrive in the battle territory.',
|
||||
'Have three own forces in reserves when sealing. The current Basic/Advanced classic-faction and paired Ecaz/Moritani prototype spends ordinary reserves first and then elite reserves, moving exactly three to their corresponding Tanks subpools when the battle resolves. No extra forces arrive in the battle territory.',
|
||||
'The normal-outcome score gains 2 without adding to the physical dial, spice support or on-board casualties. The card and its reserve cost are consumed even on a Traitor victory, a mutual Traitor result or a Lasgun/Shield explosion; successful Traitor calls still decide the outcome directly.',
|
||||
'Payment on every outcome, deterministic normal-first selection and counting the reserve transfer as battle losses are documented provisional interpretations, not publisher rulings. Harass/Stone Burner combinations, other optional modules and expansion factions remain unavailable in this bounded prototype.',
|
||||
'Payment on every outcome, deterministic normal-first selection and counting the reserve transfer as battle losses are documented provisional interpretations, not publisher rulings. Harass/Stone Burner, co-present Ecaz allied armies, other optional modules and other expansion pairings remain unavailable in this bounded prototype.',
|
||||
],
|
||||
related: ['battle-cards', 'battle', 'card-recruits', 'card-harass-withdraw', 'implementation-checklist'],
|
||||
checklist: [
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Physical card custody, either battle slot, three typed reserve counters, normal score modifier and mandatory discard connect in classic-faction Basic/Advanced games only.', evidence: ['game/reinforcements.ts', 'game/battle-resolution-quote.ts', 'game/engine.ts'] },
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Physical card custody, either battle slot, three typed reserve counters, normal score modifier and mandatory discard connect in classic or paired Ecaz/Moritani Basic/Advanced games.', evidence: ['game/reinforcements.ts', 'game/battle-resolution-quote.ts', 'game/engine.ts'] },
|
||||
{ area: 'Player controls', status: 'Partial', detail: 'Owner-only availability, exact cost guidance and both battle selectors support the card; private category answers remain None.', evidence: ['components/battle-preparation.tsx', 'components/game-table.tsx'] },
|
||||
{ area: 'AI', status: 'Partial', detail: 'Four profiles consider legal physical slot candidates and the normal score bonus; strategy and calibration remain unfinished.', evidence: ['game/bots.ts'] },
|
||||
{ area: 'Documentation', status: 'Partial', detail: 'The printed effect and unresolved Traitor, explosion, type-selection and accounting interpretations are distinguished from this temporary prototype.', evidence: ['docs/ECAZ_TREACHERY_RULES.md', 'docs/REINFORCEMENTS_RUNTIME.md'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Rule, engine, controls and saved concurrent recovery cases cover this bounded behavior; full variant and combined modes are not certified.', evidence: ['tests/reinforcements.test.ts', 'tests/reinforcements-engine.test.ts', 'tests/reinforcements-controls.test.tsx', 'tests/reinforcements-recovery.test.ts'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Rule, paired-faction engine/controls and saved concurrent recovery cases cover bounded behavior; co-present Ecaz allied armies, the full card variant and combined optional modules remain uncertified.', evidence: ['tests/reinforcements.test.ts', 'tests/reinforcements-engine.test.ts', 'tests/reinforcements-controls.test.tsx', 'tests/reinforcements-recovery.test.ts'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -4273,7 +4273,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'Development setup supports two through six distinct seats from the base factions and selected expansion sets. Expansion selection and optional modules are separate; public expansion starts remain disabled.',
|
||||
'Ecaz places six starting forces in Imperial Basin, allocated among sectors 9, 10 and 11, and keeps fourteen in reserve. Finish Fremen placement first; Bene Gesserit then places its Advanced starting advisor. Moritani places its six forces in an unoccupied territory after all other setup is complete.',
|
||||
'Ixians keep their private starting-card selection and physical cyborg/suboid forces. Tleilaxu receives three private Face Dancers. Advanced CHOAM receives its Auditor. Richese has a separate ten-card cache and three private No-Fields. Ecaz and Moritani receive their Ambassador and Terror inventories and share one Duke Vidal disc.',
|
||||
'Without Ix, CHOAM adds Poison Tooth and Artillery Strike to the 33-card ordinary deck. With Ix, its 47-card deck contains those physical cards once. Ecaz faction selection adds no Treachery Cards: the separate three-card variant has Recruits, bounded Harass & Withdraw and bounded Reinforcements development controls for classic-faction games.',
|
||||
'Without Ix, CHOAM adds Poison Tooth and Artillery Strike to the 33-card ordinary deck. With Ix, its 47-card deck contains those physical cards once. Ecaz faction selection adds no Treachery Cards: the separate three-card variant has Recruits, bounded Harass & Withdraw and bounded Reinforcements development controls for classic or Ecaz/Moritani games.',
|
||||
'Advanced Ixians may explicitly decline Technology for one Richese special lot. Their once-per-round use remains available later. The special-lot exchange itself is unfinished pending replacement-card custody interpretation.',
|
||||
'Fresh unsupported movement arrivals still reject before opening prevention choices or reserving a new movement card. A saved CHOAM allow choice can return an uncommitted unsupported move for another choice, preserving its existing reserved Ornithopter. The bounded Ecaz/Moritani same-entry Ambassador/Terror overlap instead snapshots both eligible owners in storm order without replaying the original move.',
|
||||
'Ordinary shipment checks unfinished arrival combinations before saving the Guild interception choice. An already saved ordinary shipment may return unspent when its still-gated arrival cannot commit; older No-Field, Smuggler, Nexus and Ambassador shipments retain their separate recovery boundaries. Classic plus Ecaz/Moritani Ambassador/Terror overlap has a provisional saved storm-order sequence, while other simultaneous priorities remain unfinished.',
|
||||
|
||||
Reference in new issue
Block a user