Compose classic independent cards with World Nexus rules
Retain independent36, original Homeworlds and Nexus12 through classic fresh setup with optional Skills and phase modules. Resolve own-World Reinforcements fees before computing temporary Sardaukar roles, preserving original Cunning declarations, printed traits and physical counters. Record the provisional ordinary-nonrole-first policy and align human/AI quotes and captions. Validation: types/lint/build, 128 affected tests across 15 files, bounded rule-source review and finite original-action smoke for Recruits, Harass, local-fee Cunning, all four legal policies and rendered source controls. Verified runtime c219ec587a54b44079539168d93835a5af904c8afb4de58bcec0a3d60c809f0c; documentation-only proof update 8ee21598e7be0c89a08fbad50786c346ebc8ae705492d8b31ea00fae0ffefa3c. Full modes, combinations, assurance and deployed acceptance remain open. No saved-room/provider changes.
This commit is contained in:
1 parent
178251ad35
commit
97c2b67650
18 files changed
+737
-30
No files matched your search
+27
-6
@@ -2001,7 +2001,8 @@ export type Game = {
|
||||
nexusAdvisorHistory?: { receipt: NexusAdvisorReceipt; stage: 'pending' | 'completed' | 'canceled'; frame: string; signature: string }[];
|
||||
nexusAdvisorLast?: { event: string; stage: 'pending' | 'completed' | 'canceled' };
|
||||
nexusSardaukarHistory?: {receipt: NexusSardaukarReceipt; stage: 'pending' | 'active' | 'canceled'; parent: string; signature: string;
|
||||
casualties?: {forces: CombatForces; dial: number; support: number; options: Casualties[]; outcome: 'pending' | 'complete'}}[];
|
||||
casualties?: {forces: CombatForces; dial: number; support: number; options: Casualties[]; outcome: 'pending' | 'complete';
|
||||
reserveFee?: {normal: number; elite: number}}}[];
|
||||
nexusSardaukarLast?: {event: string; stage: 'pending' | 'active' | 'canceled'};
|
||||
nexusChoamHistory?: {receipt: NexusChoamReceipt; stage: 'pending' | 'canceled' | 'complete' | 'fizzled'; frame: string; parent: string; signature: string}[];
|
||||
nexusMoritaniHistory?: {receipt:NexusMoritaniReceipt;stage:'pending'|'complete'|'canceled';before:string;frame:string;signature:string}[];
|
||||
@@ -3795,9 +3796,21 @@ function nexusSardaukarIntegrity(g: Game) {
|
||||
nexusRule(() => validateNexusSardaukar(g, record.receipt));
|
||||
const losses = record.casualties;
|
||||
if (losses) {
|
||||
const fee = losses.reserveFee;
|
||||
if (fee) {
|
||||
const home = g.homeworlds && homeworldBattleLocation(g,record.receipt.territory);
|
||||
const original = {...losses.forces,normal:record.receipt.normal,temporaryElite:record.receipt.count};
|
||||
requireRule(g.ecazTreachery && home && home.native === record.receipt.owner &&
|
||||
Object.keys(fee).sort().join(',') === 'elite,normal' && fee.elite === 0 &&
|
||||
Number.isSafeInteger(fee.normal) && fee.normal >= 1 && fee.normal <= 3 &&
|
||||
losses.forces.normal === record.receipt.normal-fee.normal &&
|
||||
validCombatForces(original) && Number(losses.forces.temporaryElite) < record.receipt.count &&
|
||||
JSON.stringify(reinforcementsBattleForces(original,fee)) === JSON.stringify(losses.forces),
|
||||
'The reduced temporary Sardaukar need their original own-World reserve fee.');
|
||||
}
|
||||
requireRule(record.stage === 'active' && ['pending','complete'].includes(losses.outcome) &&
|
||||
Object.keys(losses).sort().join(',') === 'dial,forces,options,outcome,support' &&
|
||||
validCombatForces(losses.forces) && losses.forces.temporaryElite === 5 &&
|
||||
Object.keys(losses).sort().join(',') === (fee ? 'dial,forces,options,outcome,reserveFee,support' : 'dial,forces,options,outcome,support') &&
|
||||
validCombatForces(losses.forces) && (fee ? Number(losses.forces.temporaryElite) >= 2 : losses.forces.temporaryElite === 5) &&
|
||||
Array.isArray(losses.options) && losses.options.length > 0 &&
|
||||
JSON.stringify(losses.options) === JSON.stringify(casualtyOptions(losses.forces,losses.dial,losses.support)),
|
||||
'The Nexus Sardaukar casualties have lost their committed force allocation.');
|
||||
@@ -10742,7 +10755,7 @@ export function initializeLeaderSkillsGameForAudit(state: Game): Game {
|
||||
const homeworlds = homeworldLeaderSkillsProfile(state);
|
||||
const independent = independentTreacheryLeaderSkillsProfile(state);
|
||||
if (state.ecazTreachery) {
|
||||
requireRule(independent, 'Independent Skills cards require supported selected decks with original World/phase modules, or classic Nexus with original phase modules without Homeworlds; independent World/Nexus and unrelated preview contexts remain gated.');
|
||||
requireRule(independent, 'Independent Skills cards require supported selected decks with original World/phase modules, or classic Nexus with original World/phase modules; unrelated preview contexts remain gated.');
|
||||
requireFreshBaseRuntime(state);
|
||||
requireFreshFactionInventory(state);
|
||||
}
|
||||
@@ -10789,6 +10802,7 @@ function independentCardModuleRoster(g: Game): boolean {
|
||||
(g.expansions.length === 1 && g.expansions[0] === 'ecaz'));
|
||||
}
|
||||
function ecazHomeworldProfile(g: Game): boolean {
|
||||
if (g.homeworlds && classicIndependentNexusProfile(g)) return true;
|
||||
if (g.homeworlds && g.leaderSkills && !g.sandtrout && independentTreacheryLeaderSkillsProfile(g)) return true;
|
||||
return !!g.homeworlds && g.ecazTreachery === true && independentCardModuleRoster(g) &&
|
||||
g.players.length >= 2 && g.players.length <= 6 &&
|
||||
@@ -10828,7 +10842,7 @@ export function initializeEcazTreacheryGameForAudit(state: Game): Game {
|
||||
g.ecazTreachery = true;
|
||||
const homeworlds = !!g.homeworlds;
|
||||
requireRule(!homeworlds || ecazHomeworldProfile(g),
|
||||
'Independent World cards need a supported selected-deck roster and original phase modules without Nexus; independent World/Nexus remains a separate pending interaction.');
|
||||
'Independent World cards need a supported selected-deck roster and original phase modules, or classic Nexus with original phase modules.');
|
||||
const initialized = initializeSetupGameForAudit(g, homeworlds, classicIndependentNexusProfile(g), false, false, false, false, true, true);
|
||||
if (byFaction(initialized, 'ecaz')) initialized.ecazOccupyPreview = true;
|
||||
if (moritaniAssassinateModeSupported(initialized)) initializeMoritaniAssassinateState(initialized);
|
||||
@@ -23481,7 +23495,14 @@ function resolveBattle(g: Game, retreat?: DiplomatRetreatSelection) {
|
||||
? quote.fixedLosses?.find(loss => loss.owner === winner.id) : undefined;
|
||||
const sardaukar = currentNexusSardaukar(g);
|
||||
if (sardaukar?.stage === 'active' && casualtyCommitment && winner?.id === sardaukar.receipt.owner) {
|
||||
sardaukar.casualties = {...structuredClone(casualtyCommitment),outcome:'pending'};
|
||||
const reduced = casualtyCommitment.forces.temporaryElite !== sardaukar.receipt.count;
|
||||
const plan = b.plans[winner.id];
|
||||
const fee = reduced && usesNativeBattleSpecial(plan,REINFORCEMENTS_CARD)
|
||||
? currentReinforcementsCost(g,winner,plan.reinforcementsSources).sources?.[b.territory] : undefined;
|
||||
requireRule(!reduced || (fee && g.homeworlds && homeworldBattleLocation(g,b.territory)?.native === winner.id),
|
||||
'Reduced temporary Sardaukar require the original native Reinforcements fee.');
|
||||
sardaukar.casualties = {...structuredClone(casualtyCommitment),
|
||||
...(fee ? {reserveFee:structuredClone(fee)} : {}),outcome:'pending'};
|
||||
sardaukar.signature = nexusSardaukarSignature(sardaukar);
|
||||
}
|
||||
for (const payment of quote.payments) {
|
||||
|
||||
@@ -30,13 +30,13 @@ type ClassicNexusProfile = Partial<FactionModuleProfile> & Pick<FactionModulePro
|
||||
sandtrout?: unknown; moritaniAssassinatePreview?: unknown;
|
||||
};
|
||||
|
||||
/** Classic Nexus12 with selected base33/independent36, optional all14 and original phase modules.
|
||||
* Original Homeworld core uses base33; native effect stamps are not selected overlays. */
|
||||
/** Classic Nexus12 with selected base33/independent36, optional all14, original
|
||||
* Homeworlds and phase modules. Native effect stamps are not selected overlays. */
|
||||
export function classicNexusCoreProfile(game: ClassicNexusProfile): boolean {
|
||||
const players = game.players;
|
||||
if (typeof game.advanced !== 'boolean' || !game.nexusCards ||
|
||||
game.expansions.length !== 0 || !players || players.length < (game.techTokens ? 3 : 2) || players.length > 6 ||
|
||||
(game.homeworlds && game.ecazTreachery) || (game.strongholdCards && !game.advanced) || game.sandtrout ||
|
||||
(game.strongholdCards && !game.advanced) || game.sandtrout ||
|
||||
game.discoveryEnabled || game.discoveries || game.discoveryStash || game.greatMaker ||
|
||||
game.semutaPreview || game.moritaniAssassinatePreview || game.advancedPreview || game.kullPreview || game.nexusKullPreview ||
|
||||
game.richeseBetrayalPreview ||
|
||||
|
||||
+9
-4
@@ -4105,7 +4105,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'The separately marked fresh Tupile entry grants one hand slot to the proved occupier and reciprocal ally. Every server-derived hand limit includes it, so Harkonnen can hold nine without receiving a tenth bonus. After a proved loss, each old holder privately selects exactly its excess original eligible cards to return to the normal limit; committed cards remain protected. Original phase opening and remaining movement resume without another shipment or payment. Basic unknown lifecycle blocks rather than guesses cleanup. Advanced departure can restore CHOAM’s low intelligence without clearing its previous answers or used factions.',
|
||||
'The fresh native Ecaz high-card preview shows a separate prospective conjunction: native seven-plus population, at least one actual jointly held stronghold and proved current alliance holdings of two other native factions’ Homeworlds. This does not change the ordinary four-stronghold or three-joint Ecaz targets. Original Mentat decides the win before prediction/fallback; source uncertainty remains blocked. Duke authority and the Ecaz-only revival ruling are separate.',
|
||||
'The card collection below is available for reference. Homeworld starts are enabled in supported fresh preview envelopes; the remaining occupation variants and combinations stay under verification.',
|
||||
'Original base33 World/Nexus now composes optional all14, Tech3+ and Advanced Strongholds2+ with real first-Storm token owners and end-Mentat card claims. Guild World shipments and Cunning visitor returns pass native permission/rate before the funded fee; borrowed Caladan fee1/fee5 settles to the distinct holder while typed custody waits. Temporary-Sardaukar own-Kaitain battles keep ordinary-only casualties and true Salusa custody; Giedi native-call cancellation follows original capture/execution/cleanup and one private replacement. Normal Smuggler companions may also supply the fee in core. Independent36 World/Nexus remains a separate pending own-World fee/conversion gate with public messaging; other envelopes stay separate.',
|
||||
'Original classic World/Nexus uses base33 or selected independent36 with optional all14, Tech3+ and Advanced Strongholds2+ with real first-Storm token owners and end-Mentat card claims. Guild World shipments and Cunning visitor returns pass native permission/rate before the funded fee; borrowed Caladan fee1/fee5 settles to the distinct holder while typed custody waits. Temporary-Sardaukar own-Kaitain battles keep ordinary-only casualties and true Salusa custody; Giedi native-call cancellation follows original capture/execution/cleanup and one private replacement. Normal Smuggler companions may also supply the fee in core. On an own World, Reinforcements provisionally spends ordinary non-role counters first, reducing temporary Sardaukar roles only when converted counters pay. Kaitain7 paying local3 leaves four temporary roles on four normals; the original five-counter declaration and pre-fee traits remain. Other envelopes and complete-mode acceptance stay separate.',
|
||||
],
|
||||
example:
|
||||
'Atreides with six reserves on Caladan uses the high face. Dropping to five uses the low face and removes the next-Spice-Blow inspection advantage. Either face adds two to native battle strength on Caladan.',
|
||||
@@ -4124,6 +4124,11 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
detail:
|
||||
"All 13 cards, saved typed custody, supported-deck setup, Emperor movement, native Arrakis shipment sources, revival destinations, low charity/free-revival benefits, Tleilax’s phase-start income penalty and high-Salusa free Sardaukar support are connected. World-to-world invasion, Guild transport from Arrakis and Guild native returns use exact source pools, funding and interception. Homeworld battles connect native bonuses, limited explosion casualties, native-only Traitors and Face Dancers, and Ixian substitution. Junction offers connect sponsored board and Homeworld routes, including own returns, foreign departures and their distinct payments. Caladan foresight, Wallach advisor counts, Ix stronghold movement and Richese token movement now respect native population. High Tupile blocks Worthless sales and Ambassador payouts, including after an interrupted Ghola revival. Kaitain paid discards share the closing Bidding opportunity with CHOAM; Ecaz poison income uses actual physical disposal and current native population. Mandatory winning-card disposal follows winner casualties. Supported low Kaitain and Junction payment receipts preserve the payer’s cost and use current native population; divergent allied rounding remains guarded. Giedi grants its once-per-phase bonus on actual positive desert receipts, including settled Ecaz shares. Low Grumman uses original public entry counts across Terror and competing-reaction checks. Southern and Tleilax preserve the exact eligible revival group through one optional placement, with held income and explicit unresolved destination rules. Fresh occupation additionally connects bank and percentage receipts, completed Southern Collection, immediate sharing, Caladan inspection, displaced Ix pool, Richese card-only choice with native terms, real Giedi receiver choice and native protections. Fresh native CHOAM entries additionally connect original Tupile slots, actual normal-limit cleanup and restored low-query authority without resetting usage. Advanced retains sole-qualified ownership until last departure; Basic lifecycle, exceptional Duke, concealed transport and full/public acceptance remain unfinished. Already purchased ordinary Karama now composes with immune Junction through explicit unused-price acknowledgment; original tariff/payees stay unchanged and only delivered transport consumes its grant.",
|
||||
evidence: [
|
||||
'game/faction-module-profile.ts',
|
||||
'game/reinforcements-homeworld.ts',
|
||||
'components/reinforcements-sources.tsx',
|
||||
'tests/nexus-world-independent-cards.test.ts',
|
||||
'tests/nexus-world-independent-battles.test.ts',
|
||||
'game/homeworld-cards.ts',
|
||||
'game/homeworld-game.ts',
|
||||
'game/homeworld-native-reserves.ts',
|
||||
@@ -4542,7 +4547,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
developmentStage: 'Verified',
|
||||
summary: 'An unallied rival holding Guild Nexus may take one full original positive shipment payment, including their own pre-funded payment, instead of Guild, bank or occupied Junction income in fresh classic core or supported original standalone entries.',
|
||||
steps: [
|
||||
'Fresh classic2–6 Basic/Advanced Nexus core uses base33 or selected independent36, optionally all14 Skills, original Tech3+ and Advanced Strongholds2+. Actual Guild receives original fee state at fresh setup alongside supported Harkonnen/Ixian reactions; played games are not reflagged. Original explicit native Guild/Homeworld entry retains its seeded boundary. Other selected families, Worlds, Discovery/Semuta and reaction envelopes stay separate.',
|
||||
'Fresh classic2–6 Basic/Advanced Nexus core uses base33 or selected independent36, optionally all14 Skills, original Homeworlds, Tech3+ and Advanced Strongholds2+. Actual Guild receives original fee state at fresh setup alongside supported Harkonnen/Ixian reactions; played games are not reflagged. Original explicit native Guild/Homeworld entry retains its seeded boundary. Other selected families, Discovery/Semuta and reaction envelopes stay separate.',
|
||||
'Remain unallied and hold the actual singleton Guild Nexus while another seated player is native Guild. Use spends that physical Nexus once, with no Treachery Card, hand slot or extra activation spice. Cunning and absent-Guild Secret Ally are separate modes. An empty Guild board or temporary leader control does not change the mode.',
|
||||
'Within classic core, original Guild Cunning second shipments and Richese Secret Ally discounted shipments retain their actual signed grant, physical group, paid tariff and native continuation through the same funded fee gate. One Guild singleton cannot simultaneously fund native Cunning and rival Betrayal Use; other held faces retain neutral Pass. Outside core, original producer envelopes remain separate. Guild Secret Ally still requires Guild to be absent and is not another native-Guild producer.',
|
||||
'Wait for a legal positive shipment fee after any applicable native stop and rate responses. The original source, tariff and physical forces must already be legal and fully funded. Stopped, declined, invalid or zero-cost shipments cannot award spice or spend this Nexus; ordinary free Fremen reinforcement and free Guild Ambassador transport stay distinct.',
|
||||
@@ -4677,7 +4682,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
summary: 'A separate twelve-card deck rewards remaining unallied. Private draws and selected Atreides, Harkonnen, Tleilaxu, Ixian, Bene Gesserit, Emperor, Fremen, CHOAM, Moritani, Richese and Guild effects are connected. Other effects and complete module play remain in development.',
|
||||
steps: [
|
||||
'This optional module can be combined with Basic or Advanced rules independently of the factions and other expansion modules selected. Include one Nexus Card for each of the twelve factions.',
|
||||
'Fresh classic2–6 Basic/Advanced Nexus core admits base33 or selected independent36, optionally all14, original Tech3+ and Advanced Strongholds2+. Actual Treachery, twelve Nexus, fourteen Skills, three Tech and six held-card stocks remain distinct; no selection is silently added. Original fresh Guild/Harkonnen/Ixian states use actual role/card authority. Starting hands/training, real closing draws, first-Storm tokens and end-Mentat claims stay native. Other families, Worlds, Discovery/Semuta and reaction envelopes remain separate.',
|
||||
'Fresh classic2–6 Basic/Advanced Nexus core admits base33 or selected independent36, optionally all14, original Homeworlds, Tech3+ and Advanced Strongholds2+. Actual Treachery, twelve Nexus, fourteen Skills, three Tech and six held-card stocks remain distinct; no selection is silently added. Original fresh Guild/Harkonnen/Ixian states use actual role/card authority. Starting hands/training, real closing draws, first-Storm tokens and end-Mentat claims stay native. Other families, Discovery/Semuta and reaction envelopes remain separate.',
|
||||
'Recruits changes native Revival rates and the ordinary seven ceiling, not the fixed Tleilaxu Secret Ally group or its one-spice price. Five paid card counters may leave two ordinary free returns, using the one leader slot once. Emperor Cunning still makes five ordinary board counters temporarily Sardaukar: independent reserve fees use separate physical reserves, Harass returns actual undialed counters, and native Karama changes only the original Nexus effect. Native winner/inspection and Collection continue without extra cleanup prompts.',
|
||||
'At the end of the entire Spice Blow and Nexus phase, if a Nexus occurred and at least one alliance exists, each unallied player may draw one card or discard their held card and draw a replacement. You may decline. The two Advanced spice piles do not grant two closing draws.',
|
||||
'Keep your card secret until it is used. You may hold at most one Nexus Card. It is separate from your Treachery hand and does not occupy a Treachery hand slot.',
|
||||
@@ -4751,7 +4756,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
{area:"Player controls",status:'Partial',detail:"Four current original-action390px journeys prove native Junction5/full2, poor1/full2 release plus ordinary one-force replacement, still-held source sand2 disabled then five-force stronghold1, and independent purchased1. The response explains the spent-card consequence. No overflow, browser error or application exception; two tool-only stale-node errors were resolved by fresh evidence without replay. Earlier Junction/loan/refund surfaces remain scoped evidence.",evidence:['components/nexus-cards.tsx', 'components/nexus-traitors.tsx', 'components/nexus-tleilaxu.tsx', 'components/nexus-suboids.tsx', 'components/nexus-advisors.tsx', 'components/nexus-sardaukar.tsx', 'components/choam-power-cost.tsx', 'components/moritani-terror.tsx', 'components/nexus-moritani-betrayal.tsx', 'components/nexus-ecaz-betrayal.tsx', 'components/nexus-ecaz-inquiry.tsx', 'components/nexus-ecaz-duke.tsx', 'components/nexus-richese-betrayal.tsx', 'components/game-table.tsx', 'components/nexus-tleilaxu-revival.tsx', 'components/battle-preparation.tsx', 'components/nexus-fremen-revival.tsx','components/nexus-emperor-payment.tsx', 'components/nexus-emperor-secret-ally.tsx', "components/nexus-emperor-seller-purchase.tsx", "components/nexus-emperor-ambassador-purchase.tsx", "components/richese-special-karama.tsx", "components/junction-transport.tsx"]},
|
||||
{area:"AI",status:'Partial',detail:"All four original policies execute a legal still-held Richese full-rate one-counter alternative after native cancellation, with actual physical delivery and card cost. Shared human/AI quotes no longer promise a canceled discount. Native permission alternatives, recipient-specific lender prices and immune Junction source choice remain. Optional non-reoffer is not forced. No strategic first-candidate pin, tuning or calibration.",evidence:['game/nexus-card-options.ts', 'game/nexus-traitor-options.ts', 'game/nexus-tleilaxu-options.ts', 'game/nexus-suboid-options.ts', 'game/nexus-advisor-options.ts', 'game/nexus-sardaukar-options.ts', 'game/choam-power-options.ts', 'game/nexus-moritani-options.ts', 'game/nexus-moritani-betrayal-options.ts', 'game/nexus-ecaz-betrayal-options.ts', 'game/nexus-ecaz-inquiry-options.ts', 'game/nexus-ecaz-duke-options.ts', 'game/nexus-richese-options.ts', 'game/bot-nexus-richese-betrayal.ts', 'game/nexus-guild-cunning-options.ts', 'game/bots.ts', 'game/nexus-fremen-revival-options.ts','game/nexus-emperor-payment.ts', 'game/nexus-emperor-secret-ally-options.ts']},
|
||||
{area:"Documentation",status:'Partial',detail:"Richese printed one-counter pricing and native Guild eligibility stay distinct from absent-faction cancellation, physical force count and independent purchased price. Unfunded spent-card/no-refund release is selected existing no-debt composition, not a new publisher worked example. Immune Junction and earlier permission/refund/loan interpretations remain explicit. Full rules and public gates stay open.",evidence:['docs/NEXUS_CARD_RULES.md', 'docs/NEXUS_ATREIDES_RULES.md', 'docs/NEXUS_HARKONNEN_RULES.md', 'docs/NEXUS_TLEILAXU_RULES.md', 'docs/NEXUS_IXIAN_CUNNING_RULES.md', 'docs/NEXUS_CUNNING_RUNTIME.md', 'docs/NEXUS_BENE_GESSERIT_RULES.md', 'docs/NEXUS_ADVISOR_RUNTIME.md', 'docs/NEXUS_EMPEROR_RULES.md', 'docs/NEXUS_SARDAUKAR_RUNTIME.md', 'docs/NEXUS_FREMEN_RULES.md', 'docs/NEXUS_ECAZ_RULES.md', 'docs/NEXUS_CHOAM_RULES.md', 'docs/NEXUS_CHOAM_RUNTIME.md', 'docs/NEXUS_MORITANI_RULES.md', 'docs/NEXUS_MORITANI_RUNTIME.md', 'docs/NEXUS_RICHESE_RUNTIME.md', 'docs/NEXUS_RICHESE_RULES.md', 'docs/NEXUS_GUILD_CUNNING_RUNTIME.md', 'docs/GUILD_RATE_KARAMA_RULES.md', 'docs/RICHESE_ALLIED_SHIPMENT_AUDIT.md', 'docs/GUILD_HOMEWORLD_TRANSPORT_RULES.md', 'docs/NEXUS_GUILD_RULES.md', "docs/NEXUS_EMPEROR_SECRET_ALLY_RUNTIME.md", "docs/RULE_DECISIONS.md", "docs/RICHESE_AMBASSADOR_RULES.md", "docs/RICHESE_AMBASSADOR_IMPLEMENTATION.md", "docs/RICHESE_ACQUISITION_RULES.md", "docs/DUKE_VIDAL_RULES.md", "docs/ECAZ_DUKE_ACQUISITION.md", "docs/ECAZ_REVIVAL_RULES.md", "docs/JUNCTION_TRANSPORT_RULES.md", "docs/JUNCTION_TRANSPORT_RUNTIME.md"]},
|
||||
{area:"Verification",status:'Partial',detail:"Classic core now composes original Tech3+ and Advanced Strongholds2+ with base33/selected36 and optional14. Real first-Storm owners, phase-end income and first-Mentat held cards precede actual card grants, funded receipts, support and casualties. Four390px paths and four legal policies complete earned income/combat; new regressions cover fixed/free returns, spendability, one-pile accrual, native winner transfer and held custody. Other native/World/Discovery/Semuta envelopes and full assurance remain open.",evidence:['tests/nexus-core-tech-runtime.test.ts','tests/fixture-nexus-core-tech.ts','tests/nexus-core-strongholds-runtime.test.ts','tests/fixture-nexus-core-strongholds.ts','tests/classic-nexus-core.test.ts','tests/fixture-classic-nexus-core.ts','game/faction-module-profile.ts','tests/classic-independent-nexus-utility.test.ts', 'tests/classic-independent-nexus-battle.test.ts', 'tests/fixture-classic-independent-nexus.ts', 'tests/mixed-nexus-modules-runtime.test.ts', 'tests/paired-e3-nexus-modules-runtime.test.ts', 'tests/sample-custody.test.ts', 'tests/ambassador-terror-overlap-engine.test.ts', 'tests/single-nexus-e1-runtime.test.ts', 'tests/single-nexus-e2-runtime.test.ts', 'tests/e3-nexus-ecaz-runtime.test.ts', 'tests/e3-nexus-moritani-runtime.test.ts', 'tests/homeworld-classic-nexus-skills-runtime.test.ts', 'tests/homeworld-classic-discovery-nexus-runtime.test.ts', 'tests/nexus-cards.test.ts', 'tests/nexus-card-engine.test.ts', 'tests/nexus-fremen-cunning.test.ts', 'tests/nexus-fremen-cunning-recovery.test.ts', 'tests/nexus-fremen-cunning-summon.test.ts', 'tests/nexus-fremen-cunning-summon-recovery.test.ts', 'tests/nexus-ecaz-betrayal-quotes.test.ts', 'tests/nexus-ecaz-betrayal.test.ts', 'tests/nexus-ecaz-betrayal-recovery.test.ts', 'tests/nexus-ecaz-inquiry.test.ts', 'tests/nexus-ecaz-duke-quote.test.ts', 'tests/nexus-ecaz-duke-engine.test.ts', 'tests/nexus-ecaz-duke-recovery.test.ts', 'tests/nexus-traitor-recovery.test.ts', 'tests/nexus-face-dancer-engine.test.ts', 'tests/nexus-suboids-engine.test.ts', 'tests/normal-free-support.test.ts', 'tests/nexus-suboid-controls.test.ts', 'tests/nexus-advisor-controls.test.ts', 'tests/nexus-advisor-bots.test.ts', 'tests/nexus-sardaukar-controls.test.ts', 'tests/nexus-sardaukar-bots.test.ts', 'tests/nexus-choam-controls.test.ts', 'tests/nexus-choam-bots.test.ts', 'tests/nexus-moritani-controls.test.ts', 'tests/nexus-moritani-bots.test.ts', 'tests/nexus-moritani-betrayal.test.ts', 'tests/nexus-richese-controls.test.ts', 'tests/nexus-richese-bots.test.ts', 'tests/nexus-richese-betrayal-engine.test.ts', 'tests/nexus-richese-betrayal-recovery.test.ts', 'tests/nexus-guild-cunning-controls.test.ts', 'tests/native-guild-rate-karama.test.ts', 'tests/native-guild-rate-modules.test.ts', 'tests/allied-marker-guild-rate.test.ts', 'tests/purchased-homeworld-rate.test.ts', 'tests/native-guild-transport-permission.test.ts', 'tests/native-guild-homeworld-rate.test.ts', 'tests/native-guild-rate-betrayal.test.ts', 'tests/nexus-guild-cunning-engine.test.ts', 'tests/nexus-guild-cunning-bots.test.ts', 'tests/nexus-module-payments-runtime.test.ts', 'tests/nexus-module-battles-runtime.test.ts', 'tests/paired-ix-nexus-modules-runtime.test.ts', 'tests/paired-choam-nexus-modules-runtime.test.ts', 'tests/classic-nexus-skills-payments-runtime.test.ts', 'tests/classic-nexus-skills-battles-runtime.test.ts', 'tests/nexus-skills-modules-payments-runtime.test.ts', 'tests/nexus-skills-modules-battles-runtime.test.ts', 'tests/paired-ix-nexus-skills-modules-runtime.test.ts', 'tests/paired-choam-nexus-skills-modules-runtime.test.ts', 'tests/ecaz-nexus-skills-modules-runtime.test.ts', 'tests/moritani-nexus-skills-modules-runtime.test.ts', 'docs/NEXUS_CARD_RULES.md', 'tests/nexus-tleilaxu-revival.test.ts', 'tests/fixture-nexus-tleilaxu-secret-ally.ts', 'tests/nexus-tleilaxu-secret-ally-runtime.test.ts', 'tests/nexus-borrowed-voice.test.ts', 'tests/fixture-nexus-borrowed-voice.ts', 'tests/nexus-borrowed-voice-runtime.test.ts', 'tests/nexus-fremen-protection.test.ts', 'tests/fixture-nexus-fremen-protection.ts', 'tests/nexus-fremen-protection-runtime.test.ts', 'tests/fixture-stronghold-factions.ts', 'tests/fixture-nexus-advisor-fresh.ts', 'tests/nexus-advisor-fresh-runtime.test.ts', 'tests/fixture-nexus-advisor-storm.ts', 'tests/nexus-advisor-storm-runtime.test.ts', 'docs/NEXUS_ADVISOR_RUNTIME.md', 'tests/nexus-fremen-free-quota.test.ts', 'tests/fixture-nexus-fremen-free-quota.ts', 'tests/nexus-fremen-free-quota-runtime.test.ts', 'tests/nexus-fremen-revival.test.ts','tests/nexus-emperor-payment.test.ts','tests/fixture-nexus-emperor-payment.ts','tests/nexus-emperor-payment-runtime.test.ts','docs/NEXUS_EMPEROR_RULES.md', 'tests/nexus-emperor-extra-source.test.ts', 'tests/nexus-emperor-extra-continuation.test.ts', 'tests/fixture-nexus-emperor-extra.ts', 'tests/nexus-emperor-extra-runtime.test.ts', 'tests/fixture-single-nexus-e1.ts', 'docs/NEXUS_EMPEROR_SLine truncated
|
||||
{area:"Verification",status:'Partial',detail:"Classic core retains original World/Tech/Stronghold modules with base33/selected36 and optional14. Independent World cases use native setup, population-sensitive Recruits, typed foreign Harass and actual own-Kaitain Reinforcements/Cunning. The local fee reduces only its paid temporary roles; original count5 and real stars remain. Earlier phase/card proofs are dated in the canonical contract. Native-family/Discovery/Semuta envelopes, full-mode and comprehensive assurance remain open.",evidence:['tests/nexus-world-independent-cards.test.ts','tests/nexus-world-independent-battles.test.ts','tests/reinforcements-temporary-role.test.ts','tests/nexus-core-tech-runtime.test.ts','tests/fixture-nexus-core-tech.ts','tests/nexus-core-strongholds-runtime.test.ts','tests/fixture-nexus-core-strongholds.ts','tests/classic-nexus-core.test.ts','tests/fixture-classic-nexus-core.ts','game/faction-module-profile.ts','tests/classic-independent-nexus-utility.test.ts', 'tests/classic-independent-nexus-battle.test.ts', 'tests/fixture-classic-independent-nexus.ts', 'tests/mixed-nexus-modules-runtime.test.ts', 'tests/paired-e3-nexus-modules-runtime.test.ts', 'tests/sample-custody.test.ts', 'tests/ambassador-terror-overlap-engine.test.ts', 'tests/single-nexus-e1-runtime.test.ts', 'tests/single-nexus-e2-runtime.test.ts', 'tests/e3-nexus-ecaz-runtime.test.ts', 'tests/e3-nexus-moritani-runtime.test.ts', 'tests/homeworld-classic-nexus-skills-runtime.test.ts', 'tests/homeworld-classic-discovery-nexus-runtime.test.ts', 'tests/nexus-cards.test.ts', 'tests/nexus-card-engine.test.ts', 'tests/nexus-fremen-cunning.test.ts', 'tests/nexus-fremen-cunning-recovery.test.ts', 'tests/nexus-fremen-cunning-summon.test.ts', 'tests/nexus-fremen-cunning-summon-recovery.test.ts', 'tests/nexus-ecaz-betrayal-quotes.test.ts', 'tests/nexus-ecaz-betrayal.test.ts', 'tests/nexus-ecaz-betrayal-recovery.test.ts', 'tests/nexus-ecaz-inquiry.test.ts', 'tests/nexus-ecaz-duke-quote.test.ts', 'tests/nexus-ecaz-duke-engine.test.ts', 'tests/nexus-ecaz-duke-recovery.test.ts', 'tests/nexus-traitor-recovery.test.ts', 'tests/nexus-face-dancer-engine.test.ts', 'tests/nexus-suboids-engine.test.ts', 'tests/normal-free-support.test.ts', 'tests/nexus-suboid-controls.test.ts', 'tests/nexus-advisor-controls.test.ts', 'tests/nexus-advisor-bots.test.ts', 'tests/nexus-sardaukar-controls.test.ts', 'tests/nexus-sardaukar-bots.test.ts', 'tests/nexus-choam-controls.test.ts', 'tests/nexus-choam-bots.test.ts', 'tests/nexus-moritani-controls.test.ts', 'tests/nexus-moritani-bots.test.ts', 'tests/nexus-moritani-betrayal.test.ts', 'tests/nexus-richese-controls.test.ts', 'tests/nexus-richese-bots.test.ts', 'tests/nexus-richese-betrayal-engine.test.ts', 'tests/nexus-richese-betrayal-recovery.test.ts', 'tests/nexus-guild-cunning-controls.test.ts', 'tests/native-guild-rate-karama.test.ts', 'tests/native-guild-rate-modules.test.ts', 'tests/allied-marker-guild-rate.test.ts', 'tests/purchased-homeworld-rate.test.ts', 'tests/native-guild-transport-permission.test.ts', 'tests/native-guild-homeworld-rate.test.ts', 'tests/native-guild-rate-betrayal.test.ts', 'tests/nexus-guild-cunning-engine.test.ts', 'tests/nexus-guild-cunning-bots.test.ts', 'tests/nexus-module-payments-runtime.test.ts', 'tests/nexus-module-battles-runtime.test.ts', 'tests/paired-ix-nexus-modules-runtime.test.ts', 'tests/paired-choam-nexus-modules-runtime.test.ts', 'tests/classic-nexus-skills-payments-runtime.test.ts', 'tests/classic-nexus-skills-battles-runtime.test.ts', 'tests/nexus-skills-modules-payments-runtime.test.ts', 'tests/nexus-skills-modules-battles-runtime.test.ts', 'tests/paired-ix-nexus-skills-modules-runtime.test.ts', 'tests/paired-choam-nexus-skills-modules-runtime.test.ts', 'tests/ecaz-nexus-skills-modules-runtime.test.ts', 'tests/moritani-nexus-skills-modules-runtime.test.ts', 'docs/NEXUS_CARD_RULES.md', 'tests/nexus-tleilaxu-revival.test.ts', 'tests/fixture-nexus-tleilaxu-secret-ally.ts', 'tests/nexus-tleilaxu-secret-ally-runtime.test.ts', 'tests/nexus-borrowed-voice.test.ts', 'tests/fixture-nexus-borrowed-voice.ts', 'tests/nexus-borrowed-voice-runtime.test.ts', 'tests/nexus-fremen-protection.test.ts', 'tests/fixture-nexus-fremen-protection.ts', 'tests/nexus-fremen-protection-runtime.test.ts', 'tests/fixture-stronghold-factions.ts', 'tests/fixture-nexus-advisor-fresh.ts', 'tests/nexus-advisor-fresh-runtime.test.ts', 'tests/fixture-nexus-advisor-storm.ts', 'tests/nexus-advisor-storm-runtime.test.ts', 'docs/NEXUS_ADVISOR_RUNTIME.md', 'tests/nexus-fremen-free-quota.test.ts', 'tests/fixture-nexus-fremen-free-quota.ts', 'tests/nexus-fremen-free-quota-runtime.test.ts', 'tests/nexus-fremen-revival.test.ts','tests/nexus-emperor-payment.test.ts','tests/fixture-nexus-emperor-payment.ts','tests/nexus-emperor-payment-runtime.test.ts','docs/NEXUS_EMPEROR_RULES.md', 'tests/nexus-emperor-extra-source.test.ts', 'tests/nexus-emperor-extra-continuation.test.ts', 'tests/fixture-nexus-empeLine truncated
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CombatForces } from './combat';
|
||||
import { validCombatForces, type CombatForces } from './combat';
|
||||
import {
|
||||
homeworldForceGroups, type HomeworldCustody, type HomeworldCustodyContext,
|
||||
type HomeworldForces,
|
||||
@@ -63,14 +63,18 @@ export function quoteReinforcementsHomeworldCost(context: HomeworldCustodyContex
|
||||
|
||||
/** On an own World the reserve fee and combat army share physical counters.
|
||||
* Reserve the fee first, but retain the original sealed support/strength traits.
|
||||
* Provisionally spend ordinary counters without a temporary role first, then
|
||||
* remove that role only from converted counters actually used to pay the fee.
|
||||
* Foreign-World and Arrakis callers pass no on-location cost. */
|
||||
export function reinforcementsBattleForces(forces: CombatForces,
|
||||
onLocationCost?: HomeworldForces): CombatForces {
|
||||
if (!onLocationCost) return forces;
|
||||
const { normal, elite } = onLocationCost;
|
||||
if (!Number.isSafeInteger(normal) || normal < 0 || !Number.isSafeInteger(elite) || elite < 0 ||
|
||||
if (!validCombatForces(forces) || !Number.isSafeInteger(normal) || normal < 0 || !Number.isSafeInteger(elite) || elite < 0 ||
|
||||
normal > forces.normal || elite > forces.elite)
|
||||
throw new ReinforcementsError('The reserve fee cannot also fund the physical Homeworld dial.');
|
||||
if (normal + elite === 0) return forces;
|
||||
return { ...forces, normal: forces.normal - normal, elite: forces.elite - elite };
|
||||
const remainingNormal = forces.normal - normal;
|
||||
return { ...forces, normal: remainingNormal, elite: forces.elite - elite,
|
||||
...(forces.temporaryElite === undefined ? {} : {temporaryElite: Math.min(forces.temporaryElite, remainingNormal)}) };
|
||||
}
|
||||
Reference in new issue
Block a user