Prototype Fremen Nexus free Revival for base factions

This commit is contained in:
Quintine committed 2026-09-27 14:48:31 +10:00
1 parent 5c1c944a4f
commit 1e690b7062
10 files changed
+350 -7

No files matched your search

+49
View File
@@ -33,6 +33,7 @@ import { quoteJacurutuBattleIncome } from './discovery-battle';
import { greatMakerSignature, greatMakerMajority, validateGreatMaker, type GreatMaker } from './great-maker';
import { quoteNexusChoamTrade, validateNexusChoamTrade, nexusChoamTradeSignature, type NexusChoamTrade } from './nexus-choam-trade';
import { EMPEROR_NEXUS_REVIVALS, emperorNexusEvent, emperorNexusSignature, emperorNexusModeSupported, emperorNexusPools, emperorNexusRevivalElites, validateEmperorNexusRevival, type NexusEmperorRevival } from './nexus-emperor-secret-ally';
import { FREMEN_NEXUS_FREE_FORCES, fremenNexusRevivalOffer, fremenNexusRevivalPools, fremenNexusRevivalSignature, validateFremenNexusRevival, type FremenNexusRevival } from './nexus-fremen-revival';
import { truthKnowledgeOf } from './truthtrance-knowledge';
import { createMoritaniAssassinateOpportunity, moritaniAssassinateTrigger, moritaniAssassinateChoices, quoteMoritaniAssassinate, moritaniAssassinateSignature, validateMoritaniAssassinate, type MoritaniAssassinateState, type MoritaniAssassinateReceipt } from './moritani-assassinate';
import { createNexusGuildSecretAlly, validateNexusGuildSecretAlly, quoteNexusGuildSecretShipment, type NexusGuildSecretAllyReceipt } from './nexus-guild-secret-ally';
@@ -1023,6 +1024,8 @@ export type Game = {
nexusChoamTradeLast?: { event: string; stage: NexusChoamTrade['stage'] };
nexusEmperorSecretHistory?: NexusEmperorRevival[];
nexusEmperorSecretEvents?: string[];
nexusFremenRevivalHistory?: FremenNexusRevival[];
nexusFremenRevivalEvents?: string[];
treacheryDiscardSequence?: number;
resolvedTreacheryDiscardSequence?: number;
/** Committed discard receipts are evidence, not additional card custody. */
@@ -1788,6 +1791,49 @@ function playNexusEmperorRevive(g:Game,p:Player,action:Action) {
log(g,`${p.name} spent Emperor Nexus Secret Ally to return three forces${elite ? `, including ${elite} Fedaykin` : ''} from Tanks to reserves for free. Ordinary force and free-revival allowances are unchanged.`,
{faction:p.faction,name:'Emperor Nexus revival'});
}
function nexusFremenRevivalIntegrity(g: Game) {
const history = g.nexusFremenRevivalHistory, events = g.nexusFremenRevivalEvents;
if (history === undefined) {
requireRule(events === undefined, 'Fremen Nexus has lost its saved revival history.');
return;
}
requireRule(g.nexusCards?.cards && Array.isArray(history) && history.length > 0 &&
Array.isArray(events) && history.length === events.length &&
new Set(events).size === events.length,
'Fremen Nexus has lost its physical card or independent use markers.');
for (const [index, record] of history.entries()) {
nexusRule(() => validateFremenNexusRevival(g, record));
requireRule(record.event === events[index] &&
(index === 0 || record.turn >= history[index - 1].turn),
'Fremen Nexus has duplicated or reordered a completed return.');
}
const last = history.at(-1)!;
if (last.turn === g.turn)
requireRule(g.nexusCards.cards.discard.includes('fremen'),
'Fremen Nexus has reopened its already spent physical card.');
}
function playNexusFremenRevive(g: Game, p: Player, action: Action) {
const offer = fremenNexusRevivalOffer(g, p.id);
requireRule(offer && !offer.blocked && action.event === offer.event &&
Object.keys(action).sort().join(',') === 'elite,event,type' &&
offer.eliteOptions.includes(action.elite as number),
offer?.blocked ?? 'Choose a current eligible three-force Fremen Nexus return.');
const elite = action.elite as number, before = fremenNexusRevivalPools(p);
g.nexusCards!.cards = nexusRule(() => discardNexusCard(g.nexusCards!.cards!, p.id, g.players));
addRevivedReserves(g, p, FREMEN_NEXUS_FREE_FORCES, elite);
p.tanks -= FREMEN_NEXUS_FREE_FORCES;
p.revived += FREMEN_NEXUS_FREE_FORCES;
p.freeForcesRevived = (p.freeForcesRevived ?? 0) + FREMEN_NEXUS_FREE_FORCES;
if (p.elites) { p.elites.tanks -= elite; p.elites.revived += elite; }
const record: FremenNexusRevival = { event: offer.event, owner: p.id, turn: g.turn,
phase: 4, faction: p.faction, advanced: g.advanced, elite, before,
after: fremenNexusRevivalPools(p), signature: '' };
record.signature = fremenNexusRevivalSignature(record);
(g.nexusFremenRevivalHistory ??= []).push(record);
(g.nexusFremenRevivalEvents ??= []).push(record.event);
log(g, `${p.name} spent Fremen Nexus Secret Ally to return three forces${elite ? `, including ${elite} elite` : ''} from Tanks to reserves for free. This uses the ordinary three-force revival allowance.`,
{ faction: p.faction, name: 'Fremen Nexus revival' });
}
function playNexusChoamTrade(g: Game, p: Player, action: Action) {
const offer = currentNexusChoamTrade(g, p.id);
requireRule(Object.keys(action).every(key => ['type', 'event', 'card'].includes(key)) &&
@@ -14515,6 +14561,7 @@ function marketGholaIntegrity(g: Game) {
saphoAggressorIntegrity(g);
nexusCardsIntegrity(g);
nexusEmperorSecretIntegrity(g);
nexusFremenRevivalIntegrity(g);
nexusTraitorIntegrity(g);
nexusFaceDancerIntegrity(g);
nexusSuboidIntegrity(g);
@@ -21160,6 +21207,7 @@ function applyActionInner(
if (t === 'discovery') { playDiscovery(g, p, action); return g; }
if (t === 'nexusChoamTrade') { playNexusChoamTrade(g, p, action); return g; }
if (t === 'nexusEmperorRevive') { playNexusEmperorRevive(g,p,action); return g; }
if (t === 'nexusFremenRevive') { playNexusFremenRevive(g,p,action); return g; }
if (t === 'nexusMoritaniBetrayal') { playMoritaniBetrayal(g, p, action); return g; }
if (t === 'nexusAtreides') { playNexusAtreides(g, p, action); return g; }
requireRule(
@@ -25007,6 +25055,7 @@ export function viewGame(state: Game, id: string) {
nexusCards: projectedNexusCards(g, id),
nexusChoamTrade: currentNexusChoamTrade(g, id),
nexusEmperorSecretAlly: nexusEmperorSecretAllyOffer(g,id),
nexusFremenRevival: fremenNexusRevivalOffer(g,id),
nexusMoritani: nexusMoritaniOffer(g,id),
nexusMoritaniBetrayal: moritaniBetrayalOffer(g,id,
g.grummanCollection?.stage === 'waiting' && grummanCollectionAutomatic(g)),