Connect mixed No-Field armies to native Occupy battles

This commit is contained in:
Quintine committed 2026-10-16 09:12:57 +11:00
1 parent a58b8bc92c
commit 0dfdeb9578
14 files changed
+778 -43

No files matched your search

+11 -9
View File
@@ -8305,16 +8305,18 @@ function occupyArmiesConnected(g: Game, ecaz: Player, ally: Player, t: string):
}
return false;
}
/** Active Occupy's plan actor controls its ally's variable physical pool. */
function battlePhysicalForceOwner(g: Game, id: string): string {
const occupy = g.battle?.ecazOccupy;
return occupy?.lead === id && !occupy.canceled ? occupy.ally : id;
}
/** Inspection follows the selected plan actor; reveal still belongs to the physical marker owner. */
function battleNoFieldPlan(g: Game, id: string): boolean {
const b = g.battle, occupy = b?.ecazOccupy;
const owner = occupy?.lead === id ? occupy.canceled ? id : occupy.ally : id;
return !!b?.noFieldPlayers?.includes(owner);
return !!g.battle?.noFieldPlayers?.includes(battlePhysicalForceOwner(g, id));
}
function battleConcealedForces(g: Game, id: string): boolean {
const b = g.battle, occupy = b?.ecazOccupy;
const owner = occupy?.lead === id ? occupy.canceled ? id : occupy.ally : id;
return !!b && getPlayer(g, owner).noField?.deployed?.location.territory === b.territory;
const b = g.battle;
return !!b && getPlayer(g, battlePhysicalForceOwner(g, id)).noField?.deployed?.location.territory === b.territory;
}
function ecazBattleProfile(g: Game): EcazOccupyBattleProfile | null {
const b = g.battle, occupy = b?.ecazOccupy;
@@ -33663,8 +33665,8 @@ function applyActionInner(
([key, n]) => n > 0 && splitLocation(key).territory === choice.territory,
);
if (!mixed) continue;
requireRule(!combined,
'Combined Occupy No-Field battle dialing awaits a ruling. Ordinary mixed battles use the visible provisional pool.');
requireRule(!combined || g.advanced,
'Combined mixed No-Field dialing requires the selected Advanced Occupy composition.');
(mixedNoFieldPlayers ??= []).push(playerId);
}
if (combined) {
@@ -35185,7 +35187,7 @@ export function viewGame(state: Game, id: string) {
) as Record<string, StrongholdId | null>,
tieWinner: battleTieWinner(g),
noFieldPlayers: [b.attacker, b.defender].filter(player => battleNoFieldPlan(g, player)),
...(b.mixedNoFieldPlayers?.includes(id) ? { mixedNoField: true as const } : {}),
...(b.mixedNoFieldPlayers?.includes(battlePhysicalForceOwner(g, id)) ? { mixedNoField: true as const } : {}),
ownForces: [b.attacker, b.defender].includes(id)
? planCombatForces(g, me, getPlayer(g, b.attacker === id ? b.defender : b.attacker))
: null,
+10 -7
View File
@@ -1037,7 +1037,7 @@ export const RULE_TOPICS: RuleTopic[] = [
'In supported card profiles, provisional materialization-first order forecasts the own marker privately without moving forces early. Both plans seal, the original token reveals and materializes its reserve-limited ordinary counters at its sector, then Harass selects/returns actual undialed counters. Reinforcements must leave three original reserves after that reveal and before any future Harass return; an unfunded card cannot seal a plan. Marker0, last-used identity and original reveal-event history remain native.',
'Concealed collection, occupancy and control use one-force presence. Exposed storm sectors and unprotected worm destruction reveal and remove the resulting actual forces. Storm shelter and other sectors remain separate. Trip to Gamont at a location with a marker reveals it, even if ordinary forces also occupy that sector, then returns one actual force if present. It is still used if zero forces remain. Canceling the CHOAM power leaves the marker concealed.',
'All four AI profiles choose from their own private token inventory, pay only the one-force shipment price, move the marker explicitly and reveal positive tokens when useful. They retain zero-token presence and use only public information to assess opponents. They also offer safe positive tokens from their own inventory during the allied opportunity, fund them with their own spice and accept or decline private offers using their own force and payment information. A human Richese player’s offer window holds an AI ally’s shipment. Pending responses, decisions and Truthtrance retain priority.',
'Marker-only No-Fields compose with the existing Advanced Occupy profile, including card-user-specific Harass and reserve costs. Same-owner mixed ordinary/marker Occupy and entire-plan special Karama inspection remain guarded. Homeworld custody, wider Truthtrance, other modules and complete Richese starts remain unfinished; mixed-pool, price and card-effect sequencing are explicit prototype interpretations.',
'Marker-only and ordinary-plus-marker No-Fields compose with the selected Advanced Occupy profile under the same provisional additive pool. Either selected lead gets its actual variable-owner quote; original fixed Ecaz strength stays separate. Prevention restores only the chosen lead’s own pool. Basic mixed co-side, entire-plan special Karama, Homeworld custody, wider Truthtrance and other profile/module boundaries remain. Complete Richese starts and final acceptance stay gated.',
],
related: [
'richese-cards',
@@ -1065,7 +1065,7 @@ export const RULE_TOPICS: RuleTopic[] = [
area: 'AI',
status: 'Partial',
detail:
'Own/allied pricing participation remains. All four profiles use private own materialization, original reserve funding and physical return quotes; unfunded Reinforcements is excluded while Harass remains legal. Sixteen native programs exercise ordinary, funded and fallback paths without strategic tuning or calibration.',
'Own/allied pricing participation remains. All four profiles use private own/selected variable materialization, original reserve funding and physical return quotes. Sixteen current native programs accept80 policy actions across active/prevented allied/opposing mixed battles; no strategic tuning or calibration is claimed.',
evidence: ['game/bots.ts'],
},
{
@@ -1096,6 +1096,9 @@ export const RULE_TOPICS: RuleTopic[] = [
'tests/fixture-harass-no-field.ts',
'tests/no-field-reinforcements.test.ts',
'tests/fixture-no-field-reinforcements.ts',
'tests/ecaz-occupy-mixed-no-field.test.ts',
'tests/fixture-ecaz-occupy-mixed-no-field.ts',
'tests/ecaz-occupy-mixed-no-field-opponent.test.ts',
],
},
],
@@ -1388,18 +1391,18 @@ export const RULE_TOPICS: RuleTopic[] = [
'Eligible Karama can prevent Occupy after the lead choice and before powers/plans. The chosen lead remains lead and dials its own army; the other ally contributes zero. The plan editor then uses the new force pool and no fixed Ecaz strength.',
'An Ixian variable army takes its own typed losses and chooses native surviving-Suboid substitution, including when Ecaz formed the plan. Actual Ixians own the exchange/counter; the selected lead still owns winner cards and continuation. CHOAM support income uses original eligible payer/donor shares, not the free fixed contribution.',
'A Tleilaxu winning co-side cannot Face Dance itself. External Face Dance returns both actual winning allied armies with a combined survivor maximum, while the selected winner keeps original rewards and card cleanup. Reserve/board zero/partial/full replacements remain native choices, not a copied player.',
'A native Richese ally may supply ordinary counters or its concealed No-Field alone. A zero or reserve-limited marker remains battle presence; the original Richese owner materializes it only after both plans seal. The selected lead receives the private variable pool needed for its plan; rivals do not. Dial prescience and whole-plan restrictions follow the selected plan’s actual force owner, while cancellation to Ecaz’s own pool removes that concealed-dial restriction. Richese’s own ordinary-plus-marker dialing remains guarded.',
'A native Richese ally may supply ordinary counters, a concealed No-Field, or both under the selected Advanced provisional additive pool. The selected plan actor gets that private variable pool and mixed-pool notice; rivals do not. The original Richese owner materializes once after both seals, including zero/caps. Prescience/whole-plan restrictions follow the physical force owner. Preventing Occupy restores only the selected lead’s own army; selected Ecaz neither borrows Richese counters nor exposes its hidden maximum.',
'Stone Burner compares physical undialed counters, not total dial or paid strength: active Occupy contributes floor(Ecaz forces / 2) plus the ally’s native undialed tokens. Canceled Occupy compares only the selected lead’s own pool. Physical Mirror copies use the same comparison and retain their original cleanup. Hidden opposing No-Field preflight uses public possible pools, not its actual denomination; genuinely winner-changing typed allocations remain blocked.',
'With the independent variant, Harass returns only the actual card user’s undialed forces. An Ecaz user keeps the original mandatory ceil contribution committed at free full strength while its floor count returns; ally support is separate. An allied user subtracts fixed strength before choosing its native typed commitment. Canceled Occupy uses its selected lead’s own pool. Returned reserves are outside later casualties/explosion/Face Dance.',
'Reinforcements consumes three card-user reserves normal-first and adds +2 score without extra support or on-board arrivals. Its existing provisional cost/disposal on every revealed outcome is unchanged, not a new publisher ruling. Own same-plan Harass/Reinforcements and Stone remain guarded; Basic/unprofiled co-side cards remain unsupported.',
'Advanced numbers come from the user-authorized revised PDF, physical pages 32 and 19, not a newly located publisher erratum. Basic follows the main printed paragraph provisionally. Existing faction/deck prerequisites remain:33 Ecaz alone,47 Ix,35 CHOAM,47 both; Richese retains its separate ten-card cache. Explicit --ecaz-treachery independently adds all three cards. Whole-plan disclosure, mixed No-Field and optional-overlay interactions remain for refinement; public starts remain gated.',
'Reinforcements consumes three card-user reserves normal-first and adds +2 normal score, never support or physical arrivals. Under the provisional native order, an own No-Field materializes before that original reserve fee and successful Harass returns; three reserves must remain after reveal before this plan may seal. Original/copied Stone ignores +2 score but not the fee. Existing all-revealed-outcome cost/disposal remains provisional. Basic/unprofiled co-side cards stay guarded.',
'Advanced numbers come from the user-authorized revised PDF, physical pages 32 and 19, not a new publisher erratum. Basic follows the main printed paragraph provisionally. Original faction/deck prerequisites, separate ten-card Richese cache and independent three-card variant selection stay. Ordinary-plus-marker Advanced Occupy extends the provisional additive interpretation, not the unresolved FAQ cap. Basic mixed-co-side, whole-plan/optional overlays and public starts remain separate gates.',
],
checklist: [
{ area: 'Implementation', status: 'Partial', detail: 'Combined-side enumeration, lead choice, native Occupy response and owner-labelled physical outcomes are connected. Basic odd counts now follow the main printed paragraph provisionally; Advanced retains its authorized arithmetic. Optional/exotic composition refinement remains.', evidence: ['game/ecaz-occupy-battle.ts', 'game/board-resolution-quote.ts', 'game/battle-resolution-quote.ts', 'game/engine.ts'] },
{ area: 'Player controls', status: 'Partial', detail: 'Original 390px controls cover mandatory/free dials, native Karama redial, Ix substitutions and whole-side Face Dance. Richese human T78EFNZK chose Ecaz lead, sealed total5/support2 with its real leader, revealed the ally’s original No-Field5 into only two available reserves, and reached Collection with Ecaz2 and two Richese losses. Basic human FEJ6SBFB shows the correct even-force labels, a total-dial slider bounded2..6 with no support control, and real Ecaz/guild casualties after an8–6 win. Other combinations remain open.', evidence: ['components/ecaz-occupy.tsx', 'components/face-dance-decision.tsx', 'components/game-table.tsx', 'game/ecaz-occupy-options.ts'] },
{ area: 'Player controls', status: 'Partial', detail: 'Original total-wheel, lead and Karama controls now support native mixed Richese as ally or opponent. Selected Ecaz sees the private mixed variable notice, not its own maximum; prevention returns to selected-only forces. Four new390px journeys include capped mixed Ecaz lead, funded H/R Richese lead, opposing zero with ordinary counters and actual Karama redial. Earlier native/basic controls remain historical evidence.', evidence: ['components/ecaz-occupy.tsx', 'components/face-dance-decision.tsx', 'components/game-table.tsx', 'game/ecaz-occupy-options.ts'] },
{ area: 'AI', status: 'Partial', detail: 'All four native policies actually seal legal total plans for either lead, active or canceled, using native leaders/cards and physical support. Strategy remains deferred.', evidence: ['game/ecaz-occupy-options.ts', 'game/bots.ts', 'tests/ecaz-occupy-options.test.ts'] },
{ area: 'Documentation', status: 'Implemented', detail: 'Authorized Advanced arithmetic and provisional main-paragraph Basic arithmetic are distinguished from the conflicting FAQ, specific Duke ruling and deferred interaction refinement.', evidence: ['docs/ECAZ_OCCUPY_RULES.md', 'docs/RULE_DECISIONS.md', 'game/reference.ts'] },
{ area: 'Verification', status: 'Partial', detail: 'Development Verified: affected363 rules cases,26 actual native programs including all four legal policies, and original390px Richese/No-Field controls. Source-band Basic adds focused even/odd/canceled quote cases and native runtime cases with all four policies and original Collection/victory. Five genuine2..6-seat Basic games: four complete2090 accepted actions/54 JSON continuations/no rejection; four-seat seed20261299 is captured at the preserved odd-count conflict. Earlier46 ordinary/variant games and source-specific card evidence remain historical; complete factions/optional combinations and comprehensive assurance follow all rules.', evidence: ['tests/ecaz-occupy-battle.test.ts', 'tests/basic-ecaz-occupy-runtime.test.ts', 'tests/ecaz-occupy-richese.test.ts', 'tests/ecaz-occupy-stone.test.ts', 'tests/ecaz-occupy-cards-runtime.test.ts', 'tools/faction-games.ts', 'docs/ECAZ_OCCUPY_RULES.md'] },
{ area: 'Verification', status: 'Partial', detail: 'Current mixed regressions cover both physical roles/leads, zero/caps, prevention, original support/losses, real Traitors and H/R funding/disposal. Four actual mobile journeys and16 native programs/80 policy actions reach original Collection. Earlier363/26-program and Basic full-game evidence remains historical, not a fresh broad/recovery/deployed certification. Complete modes, overlays and final assurance remain open.', evidence: ['tests/ecaz-occupy-mixed-no-field.test.ts', 'tests/fixture-ecaz-occupy-mixed-no-field.ts', 'tests/ecaz-occupy-mixed-no-field-opponent.test.ts', 'tests/ecaz-occupy-battle.test.ts', 'tests/basic-ecaz-occupy-runtime.test.ts', 'tests/ecaz-occupy-richese.test.ts', 'tests/ecaz-occupy-stone.test.ts', 'tests/ecaz-occupy-cards-runtime.test.ts', 'tools/faction-games.ts', 'docs/ECAZ_OCCUPY_RULES.md'] },
],
related: ['faction-ecaz', 'advanced-ecaz', 'ecaz-ambassadors', 'battle', 'victory', 'implementation-checklist'],
},