Connect Emperor Nexus retention to original Ambassador purchases
This commit is contained in:
1 parent
d3e1c2f242
commit
3ce2e1d32a
19 files changed
+1228
-107
No files matched your search
@@ -111,6 +111,7 @@ const decisions = {
|
||||
nexusFremenProtection: true,
|
||||
nexusEmperorPayment: true,
|
||||
nexusEmperorSellerPurchase: true,
|
||||
nexusEmperorAmbassadorPurchase: true,
|
||||
nexusFremenCunningRide: true,
|
||||
moritaniRetention: true,
|
||||
moritaniSpecialKarama: true,
|
||||
|
||||
@@ -2696,6 +2696,9 @@ function policyActions(g: GameView): Action[] {
|
||||
if (d.kind === 'nexusEmperorSellerPurchase')
|
||||
return [{ type: 'nexusEmperorSellerPurchase', event: d.event,
|
||||
use: !!g.nexusEmperorSellerPurchase?.canUse && !g.nexusEmperorSellerPurchase.blocked }];
|
||||
if (d.kind === 'nexusEmperorAmbassadorPurchase')
|
||||
return [{ type: 'nexusEmperorAmbassadorPurchase', event: d.event,
|
||||
use: !!g.nexusEmperorAmbassadorPurchase?.canUse && !g.nexusEmperorAmbassadorPurchase.blocked }];
|
||||
if (d.kind === 'nexusFremenCunningOffer')
|
||||
return [{ type: 'decision', event: d.event,
|
||||
accept: g.nexusCards?.card === 'fremen' && me.faction === 'fremen' &&
|
||||
|
||||
+142
-52
@@ -45,7 +45,7 @@ import { quoteNexusChoamBetrayal, validateNexusChoamBetrayal, nexusChoamBetrayal
|
||||
import { quoteEcazInquiry, ecazInquiryAnswer, ecazInquiryHistory, ecazInquirySignature, validateEcazInquiry, type EcazInquiryReceipt } from './nexus-ecaz-inquiry';
|
||||
import { nexusCleanPlayBlocked } from './nexus-play-boundary';
|
||||
import { EMPEROR_NEXUS_REVIVALS, emperorNexusModeSupported, quoteEmperorNexusRevival, createEmperorNexusRevival, settleEmperorNexusRevival, validateEmperorNexusRevival, type NexusEmperorRevival } from './nexus-emperor-secret-ally';
|
||||
import { emperorNexusPurchaseEvent, quoteEmperorNexusPurchase, signEmperorNexusPurchase, validateEmperorNexusPurchase, emperorNexusSellerPurchaseModeSupported, quoteEmperorNexusSellerPurchase, createEmperorNexusSellerPurchase, type EmperorNexusPurchase } from './nexus-emperor-purchase';
|
||||
import { emperorNexusPurchaseEvent, quoteEmperorNexusPurchase, signEmperorNexusPurchase, validateEmperorNexusPurchase, emperorNexusSellerPurchaseModeSupported, quoteEmperorNexusSellerPurchase, createEmperorNexusSellerPurchase, emperorNexusAmbassadorPurchaseModeSupported, quoteEmperorNexusAmbassadorPurchase, createEmperorNexusAmbassadorPurchase, type EmperorNexusPurchase } from './nexus-emperor-purchase';
|
||||
import { createEmperorPaymentReceipt, validateEmperorPaymentReceipt, emperorPaymentUseBlock, emperorPaymentSourceQuote, requiredEmperorPaymentResponders, emperorPaymentReceiptSignature, type EmperorPaymentReceipt } from './nexus-emperor-payment';
|
||||
import { FREMEN_NEXUS_FREE_FORCES, fremenNexusRevivalOffer, createFremenNexusRevival, upgradeFremenNexusRevival, validateFremenNexusRevival, type FremenNexusRevival } from './nexus-fremen-revival';
|
||||
import { captureFremenCunningOccurrence, quoteFremenCunning, quoteFremenCunningRide, validateFremenCunningSelection, type FremenCunningOccurrence, type FremenCunningRideAuthorization, type FremenCunningSelection } from './nexus-fremen-cunning';
|
||||
@@ -935,6 +935,7 @@ export type Decision =
|
||||
| { kind: 'auctionPayment'; player: string }
|
||||
| { kind: 'nexusEmperorPayment'; player: string; event: string }
|
||||
| { kind: 'nexusEmperorSellerPurchase'; player: string; event: string }
|
||||
| { kind: 'nexusEmperorAmbassadorPurchase'; player: string; event: string }
|
||||
| {
|
||||
kind: 'advisor';
|
||||
player: string;
|
||||
@@ -1636,12 +1637,14 @@ export type Game = {
|
||||
| 'loan'
|
||||
| 'copy'
|
||||
| 'cards'
|
||||
| 'purchase'
|
||||
| 'income'
|
||||
| 'bonus'
|
||||
| 'move'
|
||||
| 'ship'
|
||||
| 'revival'
|
||||
| 'arrival';
|
||||
nexusPurchase?: { event: string; signature: string };
|
||||
relocation?: {
|
||||
order: FremenAmbassadorMove;
|
||||
next: 'flip' | 'intrusion' | 'terror' | 'finish';
|
||||
@@ -1663,6 +1666,7 @@ export type Game = {
|
||||
amount: 3;
|
||||
emperor: string | null;
|
||||
stage: 'income' | 'bonus';
|
||||
nexusEvent?: string;
|
||||
};
|
||||
beneficiary?: string;
|
||||
effect?: AmbassadorEffect;
|
||||
@@ -2805,6 +2809,9 @@ function nexusEmperorSecretIntegrity(g:Game) {
|
||||
if (!('version' in record))
|
||||
requireRule(emperorNexusModeSupported(g) && !g.ecazTreachery && !g.sandtrout,
|
||||
'A normal-bank Emperor purchase lost its original supported profile.');
|
||||
else if (record.version === 3)
|
||||
requireRule(emperorNexusAmbassadorPurchaseModeSupported(g),
|
||||
'An Emperor Ambassador purchase lost its original supported native profile.');
|
||||
requireRule(record.sequence === index && purchaseEvents[index] === record.event,
|
||||
'Emperor Nexus purchase has duplicated or reordered a completed use.');
|
||||
}
|
||||
@@ -16839,6 +16846,100 @@ function continueAmbassadorRelocation(g: Game, event: string) {
|
||||
}
|
||||
finishAmbassador(g);
|
||||
}
|
||||
const EMPEROR_AMBASSADOR_POLICY = 'Provisional purchase composition: prove three own spice and retain it. No bank payment or reimbursement is invented; original card acquisition and entry continuation still resolve.';
|
||||
function emperorAmbassadorSourceSignature(g: Game): string {
|
||||
const entry = g.pendingAmbassador!;
|
||||
const buyer = getPlayer(g, entry.beneficiary!);
|
||||
return JSON.stringify([entry.event, entry.owner, entry.entrant, entry.token,
|
||||
entry.territory, entry.sector, entry.turn, entry.phase, entry.effect,
|
||||
entry.beneficiary, entry.copyChoices, entry.resume, entry.wormRider,
|
||||
entry.guildAdvisorOrigin, entry.revivalEvent, entry.victoryEvent,
|
||||
g.active, buyer.spice, buyer.ally, g.nexusEmperorPurchaseHistory?.length ?? 0]);
|
||||
}
|
||||
function nexusEmperorAmbassadorIntegrity(g: Game): void {
|
||||
const entry = g.pendingAmbassador;
|
||||
const continuation = g.pendingTreacheryDiscard?.continuation;
|
||||
const decision = g.decision?.kind === 'nexusEmperorAmbassadorPurchase' ? g.decision :
|
||||
continuation?.kind === 'truthtranceDiscard' ? continuation.resume.decision : null;
|
||||
if (!entry?.nexusPurchase) {
|
||||
requireRule(entry?.stage !== 'purchase' && decision?.kind !== 'nexusEmperorAmbassadorPurchase',
|
||||
'The original Ambassador purchase lost its buyer confirmation.');
|
||||
return;
|
||||
}
|
||||
const source = entry.nexusPurchase;
|
||||
requireRule(Object.keys(source).sort().join(',') === 'event,signature' &&
|
||||
entry.stage === 'purchase' && !entry.purchaseReceipt &&
|
||||
emperorNexusAmbassadorPurchaseModeSupported(g) &&
|
||||
decision?.kind === 'nexusEmperorAmbassadorPurchase' && decision.player === entry.beneficiary &&
|
||||
decision.event === source.event && source.signature === emperorAmbassadorSourceSignature(g),
|
||||
'The original Ambassador purchase lost its committed entry, price or beneficiary.');
|
||||
const quote = nexusRule(() => quoteEmperorNexusAmbassadorPurchase(g, entry.beneficiary!));
|
||||
requireRule(quote.event === source.event, 'The original Ambassador purchase event changed.');
|
||||
}
|
||||
function nexusEmperorAmbassadorOffer(g: Game, id: string) {
|
||||
const entry = g.pendingAmbassador;
|
||||
if (!entry?.nexusPurchase || g.decision?.kind !== 'nexusEmperorAmbassadorPurchase' ||
|
||||
g.decision.player !== id || entry.beneficiary !== id) return null;
|
||||
const quote = nexusRule(() => quoteEmperorNexusAmbassadorPurchase(g, id));
|
||||
const blocked = g.truthtrance || pendingNexusTraitors(g) || g.pendingTreacheryDiscard
|
||||
? 'Finish the original private question or card return before confirming this purchase.' : quote.blocked;
|
||||
return { ...quote, blocked, canUse: !blocked, policy: EMPEROR_AMBASSADOR_POLICY };
|
||||
}
|
||||
function decideNexusEmperorAmbassadorPurchase(g: Game, p: Player, action: Action): void {
|
||||
const entry = g.pendingAmbassador;
|
||||
requireRule(entry?.nexusPurchase && action.type === 'nexusEmperorAmbassadorPurchase' &&
|
||||
action.event === entry.nexusPurchase.event && typeof action.use === 'boolean' &&
|
||||
Object.keys(action).sort().join(',') === 'event,type,use' &&
|
||||
p.id === entry.beneficiary && g.decision?.kind === 'nexusEmperorAmbassadorPurchase' &&
|
||||
g.decision.player === p.id && !g.truthtrance && !pendingNexusTraitors(g),
|
||||
'Confirm only your current original Ambassador purchase with Pay or Use.');
|
||||
nexusEmperorAmbassadorIntegrity(g);
|
||||
if (action.use) {
|
||||
const quote = nexusRule(() => quoteEmperorNexusAmbassadorPurchase(g, p.id));
|
||||
requireRule(!quote.blocked, quote.blocked ?? 'This Emperor Nexus purchase is unavailable.');
|
||||
}
|
||||
g.decision = null;
|
||||
finishRicheseAmbassadorPurchase(g, action.use);
|
||||
}
|
||||
function finishRicheseAmbassadorPurchase(g: Game, use = false): void {
|
||||
const entry = g.pendingAmbassador!;
|
||||
const buyer = getPlayer(g, entry.beneficiary!);
|
||||
if (buyer.spice < 3 || buyer.hand.length >= handLimit(buyer) ||
|
||||
(!g.deck.length && !g.discard.length)) {
|
||||
requireRule(!use, 'This Emperor Nexus purchase is no longer available.');
|
||||
delete entry.nexusPurchase;
|
||||
log(g, `${buyer.name} received no card through the Richese Ambassador. No spice was charged; the trigger remains committed.`);
|
||||
finishAmbassador(g);
|
||||
return;
|
||||
}
|
||||
const card = draw(g);
|
||||
requireRule(card, 'No Treachery Card is available for the Ambassador purchase.');
|
||||
let nexusEvent: string | undefined;
|
||||
if (use) {
|
||||
const receipt = nexusRule(() => createEmperorNexusAmbassadorPurchase(g, buyer.id, card.id));
|
||||
nexusEvent = receipt.event;
|
||||
g.nexusCards!.cards = nexusRule(() => discardNexusCard(g.nexusCards!.cards!, buyer.id, g.players));
|
||||
(g.nexusEmperorPurchaseHistory ??= []).push(receipt);
|
||||
(g.nexusEmperorPurchaseEvents ??= []).push(receipt.event);
|
||||
} else buyer.spice -= 3;
|
||||
delete entry.nexusPurchase;
|
||||
buyer.hand.push(card);
|
||||
const emperor = byFaction(g, 'emperor');
|
||||
entry.purchaseReceipt = {
|
||||
buyer: buyer.id, card: card.id, amount: 3,
|
||||
emperor: emperor && emperor.id !== buyer.id ? emperor.id : null, stage: 'income',
|
||||
...(nexusEvent ? { nexusEvent } : {}),
|
||||
};
|
||||
log(g, use
|
||||
? `${buyer.name} spent Emperor Nexus after proving three own spice, retained it and drew the original Richese Ambassador purchase card. No bank payment or reimbursement is created. Provisional purchase composition.`
|
||||
: `${buyer.name} paid 3 spice and drew the top Treachery Card through the Richese Ambassador. Its identity remains private.`,
|
||||
{ faction: buyer.faction, name: 'Richese Ambassador purchase' });
|
||||
if (entry.purchaseReceipt.emperor) {
|
||||
entry.stage = 'income';
|
||||
g.response = { kind: 'emperorIncome', source: 'ambassador', intent: entry.event,
|
||||
owner: entry.purchaseReceipt.emperor, amount: 3, passed: [] };
|
||||
} else ambassadorPurchaseBonus(g);
|
||||
}
|
||||
/** These are consequences of a completed purchase, never an auction lot. */
|
||||
function validateAmbassadorPurchaseResponse(g: Game, response: ResponseWindow) {
|
||||
const entry = g.pendingAmbassador;
|
||||
@@ -16877,6 +16978,16 @@ function validateAmbassadorPurchaseResponse(g: Game, response: ResponseWindow) {
|
||||
(emperor && emperor.id !== buyer.id ? emperor.id : null),
|
||||
'This Ambassador purchase has an invalid income recipient.',
|
||||
);
|
||||
if (receipt.nexusEvent) {
|
||||
const original = g.nexusEmperorPurchaseHistory?.find(record => record.event === receipt.nexusEvent);
|
||||
requireRule(original && 'version' in original && original.version === 3 &&
|
||||
original.entry === entry.event && original.token === entry.token &&
|
||||
original.entrant === entry.entrant && original.owner === receipt.buyer &&
|
||||
original.card === receipt.card && original.turn === entry.turn &&
|
||||
original.phase === entry.phase && !receipt.emperor && response.kind !== 'emperorIncome',
|
||||
'The retained-price Ambassador purchase lost its original consumed Nexus source.');
|
||||
nexusRule(() => validateEmperorNexusPurchase(g, original));
|
||||
}
|
||||
requireRule(
|
||||
response.kind === 'emperorIncome'
|
||||
? entry.stage === 'income' &&
|
||||
@@ -17262,50 +17373,13 @@ function resolveAmbassadorEffect(g: Game) {
|
||||
return;
|
||||
}
|
||||
if (effect === 'richese') {
|
||||
// Resolve after commitment so Ecaz cannot probe its ally's private funds.
|
||||
if (
|
||||
p.spice < 3 ||
|
||||
p.hand.length >= handLimit(p) ||
|
||||
(!g.deck.length && !g.discard.length)
|
||||
) {
|
||||
log(
|
||||
g,
|
||||
`${p.name} received no card through the Richese Ambassador. No spice was charged; the trigger remains committed.`,
|
||||
);
|
||||
finishAmbassador(g);
|
||||
return;
|
||||
}
|
||||
const card = draw(g);
|
||||
requireRule(
|
||||
card,
|
||||
'No Treachery Card is available for the Ambassador purchase.',
|
||||
);
|
||||
p.spice -= 3;
|
||||
p.hand.push(card);
|
||||
const emperor = byFaction(g, 'emperor');
|
||||
entry.purchaseReceipt = {
|
||||
buyer: p.id,
|
||||
card: card.id,
|
||||
amount: 3,
|
||||
emperor: emperor && emperor.id !== p.id ? emperor.id : null,
|
||||
stage: 'income',
|
||||
};
|
||||
log(
|
||||
g,
|
||||
`${p.name} paid 3 spice and drew the top Treachery Card through the Richese Ambassador. Its identity remains private.`,
|
||||
{ faction: p.faction, name: 'Richese Ambassador purchase' },
|
||||
);
|
||||
if (entry.purchaseReceipt.emperor) {
|
||||
entry.stage = 'income';
|
||||
g.response = {
|
||||
kind: 'emperorIncome',
|
||||
source: 'ambassador',
|
||||
intent: entry.event,
|
||||
owner: entry.purchaseReceipt.emperor,
|
||||
amount: 3,
|
||||
passed: [],
|
||||
};
|
||||
} else ambassadorPurchaseBonus(g);
|
||||
// Uniform timing must not expose a private Nexus face or private ability to pay.
|
||||
if (emperorNexusAmbassadorPurchaseModeSupported(g)) {
|
||||
const quote = nexusRule(() => quoteEmperorNexusAmbassadorPurchase(g, p.id));
|
||||
entry.stage = 'purchase';
|
||||
entry.nexusPurchase = { event: quote.event, signature: emperorAmbassadorSourceSignature(g) };
|
||||
g.decision = { kind: 'nexusEmperorAmbassadorPurchase', player: p.id, event: quote.event };
|
||||
} else finishRicheseAmbassadorPurchase(g);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
@@ -28710,6 +28784,7 @@ export function applyAction(state: Game, id: string, action: Action): Game {
|
||||
richeseBetrayalIntegrity(state);
|
||||
nexusEmperorPaymentIntegrity(state);
|
||||
nexusEmperorSellerIntegrity(state);
|
||||
nexusEmperorAmbassadorIntegrity(state);
|
||||
kullIntegrity(state);
|
||||
advancedPreviewIntegrity(state);
|
||||
harkonnenExchangeIntegrity(state);
|
||||
@@ -28800,10 +28875,11 @@ export function applyAction(state: Game, id: string, action: Action): Game {
|
||||
if (g.pendingNexusIxianReplacement) return g;
|
||||
if (g.pendingGuildBetrayal) {guildBetrayalIntegrity(g); return g;}
|
||||
if (g.pendingRicheseBetrayal) {richeseBetrayalIntegrity(g);return g;}
|
||||
if (g.pendingNexusEmperorPayment || g.pendingNexusEmperorSellerPurchase) {
|
||||
if (g.pendingNexusEmperorPayment || g.pendingNexusEmperorSellerPurchase || g.pendingAmbassador?.nexusPurchase) {
|
||||
finishActionContinuations(g);
|
||||
nexusEmperorPaymentIntegrity(g);
|
||||
nexusEmperorSellerIntegrity(g);
|
||||
nexusEmperorAmbassadorIntegrity(g);
|
||||
return g;
|
||||
}
|
||||
if (g.pendingKull) {
|
||||
@@ -28924,6 +29000,7 @@ export function applyAction(state: Game, id: string, action: Action): Game {
|
||||
richeseBetrayalIntegrity(g);
|
||||
nexusEmperorPaymentIntegrity(g);
|
||||
nexusEmperorSellerIntegrity(g);
|
||||
nexusEmperorAmbassadorIntegrity(g);
|
||||
marketGholaIntegrity(g);
|
||||
homeworldRule(() => homeworldGameIntegrity(g));
|
||||
homeworldBattleLossIntegrity(g);
|
||||
@@ -28954,7 +29031,7 @@ function finishActionContinuations(g: Game) {
|
||||
if (g.pendingTreacheryDiscard?.reaction) return;
|
||||
requireRule(!g.pendingTreacheryDiscard, 'The automatic discard chain did not finish.');
|
||||
if (g.pendingNexusEmperorPayment) return;
|
||||
if (g.pendingNexusEmperorSellerPurchase) return;
|
||||
if (g.pendingNexusEmperorSellerPurchase || g.pendingAmbassador?.nexusPurchase) return;
|
||||
if (pendingNexusTraitors(g)) return;
|
||||
observeOccupation(g);
|
||||
resumeHomeworldRevivalReturn(g);
|
||||
@@ -29015,7 +29092,7 @@ function settleAutomaticContinuations(g: Game) {
|
||||
if (g.pendingGuildBetrayal) return;
|
||||
if (g.pendingRicheseBetrayal) return;
|
||||
if (g.pendingNexusEmperorPayment) return;
|
||||
if (g.pendingNexusEmperorSellerPurchase) return;
|
||||
if (g.pendingNexusEmperorSellerPurchase || g.pendingAmbassador?.nexusPurchase) return;
|
||||
if (g.pendingKull?.stage === 'offer') return;
|
||||
if (pendingNexusTraitors(g)) return;
|
||||
if (g.pendingNullentropy) return;
|
||||
@@ -29026,7 +29103,7 @@ function settleAutomaticContinuations(g: Game) {
|
||||
if (g.pendingNexusIxianReplacement) return;
|
||||
if (g.pendingRicheseBetrayal) return;
|
||||
if (g.pendingNexusEmperorPayment) return;
|
||||
if (g.pendingNexusEmperorSellerPurchase) return;
|
||||
if (g.pendingNexusEmperorSellerPurchase || g.pendingAmbassador?.nexusPurchase) return;
|
||||
syncTupileSlots(g);
|
||||
prepareTupileCleanup(g);
|
||||
if (g.pendingHomeworldTupileCleanup || tupileSourceBlock(g)) return;
|
||||
@@ -29086,7 +29163,7 @@ function settleAutomaticContinuations(g: Game) {
|
||||
if (g.pendingGuildBetrayal) return;
|
||||
if (g.pendingRicheseBetrayal) return;
|
||||
if (g.pendingNexusEmperorPayment) return;
|
||||
if (g.pendingNexusEmperorSellerPurchase) return;
|
||||
if (g.pendingNexusEmperorSellerPurchase || g.pendingAmbassador?.nexusPurchase) return;
|
||||
// Opposing/automatic consequences can release a now-impossible promise;
|
||||
// they are not the original actor voluntarily spending a promised resource.
|
||||
reconcileBattlePromises(g);
|
||||
@@ -29112,6 +29189,7 @@ export function normalizeAutomaticGame(state: Game): Game {
|
||||
richeseBetrayalIntegrity(state);
|
||||
nexusEmperorPaymentIntegrity(state);
|
||||
nexusEmperorSellerIntegrity(state);
|
||||
nexusEmperorAmbassadorIntegrity(state);
|
||||
kullIntegrity(state);
|
||||
advancedPreviewIntegrity(state);
|
||||
harkonnenExchangeIntegrity(state);
|
||||
@@ -29152,10 +29230,11 @@ export function normalizeAutomaticGame(state: Game): Game {
|
||||
if (g.pendingNexusIxianReplacement) return g;
|
||||
if (g.pendingGuildBetrayal) return g;
|
||||
if (g.pendingRicheseBetrayal) return g;
|
||||
if (g.pendingNexusEmperorPayment || g.pendingNexusEmperorSellerPurchase) {
|
||||
if (g.pendingNexusEmperorPayment || g.pendingNexusEmperorSellerPurchase || g.pendingAmbassador?.nexusPurchase) {
|
||||
finishActionContinuations(g);
|
||||
nexusEmperorPaymentIntegrity(g);
|
||||
nexusEmperorSellerIntegrity(g);
|
||||
nexusEmperorAmbassadorIntegrity(g);
|
||||
return g;
|
||||
}
|
||||
if (g.pendingKull?.stage === 'offer') return g;
|
||||
@@ -29190,6 +29269,7 @@ export function normalizeAutomaticGame(state: Game): Game {
|
||||
richeseBetrayalIntegrity(g);
|
||||
nexusEmperorPaymentIntegrity(g);
|
||||
nexusEmperorSellerIntegrity(g);
|
||||
nexusEmperorAmbassadorIntegrity(g);
|
||||
marketGholaIntegrity(g);
|
||||
prepareTupileCleanup(g);
|
||||
homeworldRule(() => homeworldGameIntegrity(g));
|
||||
@@ -29225,7 +29305,7 @@ function applyActionInner(
|
||||
'Finish the original occupied Tupile hand cleanup before another action.');
|
||||
ecazTreacheryIntegrity(state);
|
||||
const g = structuredClone(state);
|
||||
if ((g.pendingNexusEmperorPayment || g.pendingNexusEmperorSellerPurchase) &&
|
||||
if ((g.pendingNexusEmperorPayment || g.pendingNexusEmperorSellerPurchase || g.pendingAmbassador?.nexusPurchase) &&
|
||||
!g.truthtrance && !pendingNexusTraitors(g)) {
|
||||
const p = getPlayer(g, id);
|
||||
const opensTruth = action?.type === 'card' && !action.mode &&
|
||||
@@ -29234,6 +29314,7 @@ function applyActionInner(
|
||||
if (!opensTruth) {
|
||||
if (action?.type === 'advanceBots') return g;
|
||||
if (g.pendingNexusEmperorSellerPurchase) decideNexusEmperorSellerPurchase(g, p, action);
|
||||
else if (g.pendingAmbassador?.nexusPurchase) decideNexusEmperorAmbassadorPurchase(g, p, action);
|
||||
else decideNexusEmperorPayment(g, p, action);
|
||||
return g;
|
||||
}
|
||||
@@ -29422,6 +29503,11 @@ function applyActionInner(
|
||||
decideNexusEmperorSellerPurchase(g, p, action);
|
||||
return g;
|
||||
}
|
||||
if (g.pendingAmbassador?.nexusPurchase) {
|
||||
if (action.type === 'advanceBots') return g;
|
||||
decideNexusEmperorAmbassadorPurchase(g, p, action);
|
||||
return g;
|
||||
}
|
||||
if (t === 'card' && action.card === RECRUITS_CARD_ID) {
|
||||
requireRule(
|
||||
Object.keys(action).sort().join(',') === 'card,type',
|
||||
@@ -29767,6 +29853,8 @@ function applyActionInner(
|
||||
'Use only the original allied-purchase event with Use or Pass.');
|
||||
requireRule(decision.kind !== 'nexusEmperorSellerPurchase',
|
||||
'Use only the original seller-lot event with Pay or Use.');
|
||||
requireRule(decision.kind !== 'nexusEmperorAmbassadorPurchase',
|
||||
'Use only the original Ambassador-purchase event with Pay or Use.');
|
||||
if (decision.kind === 'nexusIxianReplacement') {
|
||||
requireRule(false, 'Use only the current purchased-card event with Use or Pass.');
|
||||
}
|
||||
@@ -33205,6 +33293,7 @@ export function viewGame(state: Game, id: string) {
|
||||
richeseBetrayalIntegrity(state);
|
||||
nexusEmperorPaymentIntegrity(state);
|
||||
nexusEmperorSellerIntegrity(state);
|
||||
nexusEmperorAmbassadorIntegrity(state);
|
||||
kullIntegrity(state);
|
||||
advancedPreviewIntegrity(state);
|
||||
harkonnenExchangeIntegrity(state);
|
||||
@@ -33649,6 +33738,7 @@ export function viewGame(state: Game, id: string) {
|
||||
nexusEmperorBetrayal: nexusEmperorBetrayalOffer(g, id),
|
||||
nexusEmperorPayment: nexusEmperorPaymentOffer(g, id),
|
||||
nexusEmperorSellerPurchase: nexusEmperorSellerOffer(g, id),
|
||||
nexusEmperorAmbassadorPurchase: nexusEmperorAmbassadorOffer(g, id),
|
||||
nexusBgBetrayal: nexusBgBetrayalOffer(g, id),
|
||||
nexusBorrowedVoice: nexusBorrowedVoiceOffer(g, id),
|
||||
nexusFremenRevival: fremenNexusRevivalOffer(g,id),
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { FACTIONS, type FactionId } from './catalog';
|
||||
import { handLimit, type Game } from './engine';
|
||||
import { handLimit, type Game, type Player } from './engine';
|
||||
import { emperorNexusEvent } from './nexus-emperor-secret-ally';
|
||||
import { nexusCardMode, validateNexusCards } from './nexus-cards';
|
||||
import { validAmbassadorResume } from './ambassador-resume';
|
||||
import { ambassadorPhaseAllowed } from './ambassador-phase';
|
||||
import {
|
||||
pairedNexusModulesProfile, singleE1E2NexusProfile, mixedE1E2NexusProfile, mixedE3NexusProfile,
|
||||
standaloneE3NexusProfile, pairedE3NexusProfile,
|
||||
} from './nexus-module-profile';
|
||||
import {
|
||||
pairedNexusLeaderSkillsProfile, singleE1E2NexusLeaderSkillsProfile,
|
||||
mixedE1E2NexusLeaderSkillsProfile, mixedE3NexusLeaderSkillsProfile,
|
||||
standaloneE3NexusLeaderSkillsProfile, pairedE3NexusLeaderSkillsProfile,
|
||||
} from './leader-skill-profile';
|
||||
import type { RicheseAuction } from './richese-auction';
|
||||
import { quoteRicheseSettlement, type RicheseSettlementQuote } from './richese-settlement';
|
||||
@@ -37,7 +41,31 @@ export type EmperorNexusSellerPurchase = Omit<EmperorNexusBankPurchase, 'auction
|
||||
round: string;
|
||||
lot: string;
|
||||
};
|
||||
export type EmperorNexusPurchase = EmperorNexusBankPurchase | EmperorNexusSellerPurchase;
|
||||
export type EmperorNexusAmbassadorPurchase = {
|
||||
version: 3;
|
||||
kind: 'purchase';
|
||||
event: string;
|
||||
owner: string;
|
||||
turn: number;
|
||||
/** Actual original entry phase, including source-bound Homeworld arrivals. */
|
||||
phase: number;
|
||||
sequence: number;
|
||||
faction: FactionId;
|
||||
advanced: boolean;
|
||||
origin: 'ambassador';
|
||||
price: 3;
|
||||
card: string;
|
||||
beforeSpice: number;
|
||||
afterSpice: number;
|
||||
signature: string;
|
||||
entry: string;
|
||||
token: string;
|
||||
entrant: string;
|
||||
};
|
||||
export type EmperorNexusPurchase =
|
||||
| EmperorNexusBankPurchase
|
||||
| EmperorNexusSellerPurchase
|
||||
| EmperorNexusAmbassadorPurchase;
|
||||
export type EmperorNexusSellerPurchaseInput = {
|
||||
round: string;
|
||||
lot: RicheseAuction;
|
||||
@@ -85,8 +113,23 @@ export function quoteEmperorNexusPurchase(
|
||||
);
|
||||
}
|
||||
|
||||
/** Bind a retained price to the original consumed Ambassador entry and its
|
||||
* purchase sequence, never to the privately drawn card face. */
|
||||
export function emperorNexusAmbassadorPurchaseEvent(
|
||||
turn: number, phase: number, owner: string, sequence: number, entry: string,
|
||||
): string {
|
||||
return JSON.stringify(['nexusEmperorAmbassadorPurchase', turn, phase, owner, sequence, entry]);
|
||||
}
|
||||
|
||||
/** Consistency signature, not cryptographic authentication; field order is fixed. */
|
||||
export function signEmperorNexusPurchase(record: EmperorNexusPurchase): string {
|
||||
if ('version' in record && record.version === 3)
|
||||
return JSON.stringify([
|
||||
'emperorNexusAmbassadorPurchase', record.version, record.kind, record.event,
|
||||
record.owner, record.turn, record.phase, record.sequence, record.faction,
|
||||
record.advanced, record.origin, record.price, record.card, record.beforeSpice,
|
||||
record.afterSpice, record.entry, record.token, record.entrant,
|
||||
]);
|
||||
if ('version' in record)
|
||||
return JSON.stringify([
|
||||
'emperorNexusSellerPurchase', record.version, record.kind, record.event,
|
||||
@@ -148,12 +191,39 @@ export function validateEmperorNexusPurchase(
|
||||
context.players.length &&
|
||||
new Set(context.players.map((player) => player.faction)).size ===
|
||||
context.players.length;
|
||||
if (plain(record) && 'version' in record) {
|
||||
if (plain(record) && 'version' in record && record.version === 3) {
|
||||
if (
|
||||
!validContext ||
|
||||
Object.keys(record).sort().join(',') !==
|
||||
'advanced,afterSpice,beforeSpice,card,entrant,entry,event,faction,kind,origin,owner,phase,price,sequence,signature,token,turn,version' ||
|
||||
record.kind !== 'purchase' || record.origin !== 'ambassador' ||
|
||||
record.price !== 3 || !whole(record.phase) || record.phase < 1 || record.phase > 8 ||
|
||||
!whole(record.turn) || record.turn < 1 || record.turn > context.turn ||
|
||||
!whole(record.sequence) ||
|
||||
!text(record.owner) || !text(record.card) || !text(record.entry) ||
|
||||
!text(record.token) || !text(record.entrant) ||
|
||||
record.entrant === record.owner ||
|
||||
!context.players.some(player => player.id === record.entrant) ||
|
||||
context.players.find(player => player.id === record.owner)?.faction !== record.faction ||
|
||||
record.advanced !== context.advanced ||
|
||||
!whole(record.beforeSpice) || record.beforeSpice < record.price ||
|
||||
!whole(record.afterSpice) || record.afterSpice !== record.beforeSpice ||
|
||||
record.event !== emperorNexusAmbassadorPurchaseEvent(
|
||||
record.turn, record.phase, record.owner, record.sequence, record.entry,
|
||||
) ||
|
||||
record.signature !== signEmperorNexusPurchase(record)
|
||||
)
|
||||
throw new EmperorNexusPurchaseError(
|
||||
'Emperor Nexus Ambassador purchase has lost its original trigger, buyer, price or retained spice.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (plain(record) && 'version' in record && record.version === 2) {
|
||||
if (
|
||||
!validContext ||
|
||||
Object.keys(record).sort().join(',') !==
|
||||
'advanced,afterSpice,beforeSpice,card,event,faction,kind,lot,origin,owner,phase,price,round,seller,sequence,signature,turn,version' ||
|
||||
record.version !== 2 || record.kind !== 'purchase' || record.phase !== 3 ||
|
||||
record.kind !== 'purchase' || record.phase !== 3 ||
|
||||
!whole(record.turn) || record.turn < 1 || record.turn > context.turn ||
|
||||
!whole(record.sequence) || !text(record.owner) || !text(record.card) ||
|
||||
!text(record.round) || !text(record.lot) || !text(record.seller) ||
|
||||
@@ -179,6 +249,7 @@ export function validateEmperorNexusPurchase(
|
||||
if (
|
||||
!validContext ||
|
||||
!plain(record) ||
|
||||
'version' in record ||
|
||||
!('auctionIndex' in record) ||
|
||||
Object.keys(record).sort().join(',') !==
|
||||
'advanced,afterSpice,auctionIndex,beforeSpice,card,event,faction,kind,owner,phase,price,sequence,signature,turn' ||
|
||||
@@ -347,3 +418,125 @@ export function createEmperorNexusSellerPurchase(
|
||||
validateEmperorNexusPurchase(g, receipt);
|
||||
return receipt;
|
||||
}
|
||||
|
||||
/** Public Emperor-absent Ecaz envelope only, reusing the original E3 Nexus and
|
||||
* Skills profiles; it never inspects the concealed Nexus face or a hand. */
|
||||
export function emperorNexusAmbassadorPurchaseModeSupported(g: Game): boolean {
|
||||
if (!g.nexusCards || g.ecazTreachery || g.sandtrout ||
|
||||
g.players.some(p => p.faction === 'emperor') ||
|
||||
!g.players.some(p => p.faction === 'ecaz')) return false;
|
||||
if (standaloneE3NexusProfile(g) || pairedE3NexusProfile(g) || mixedE3NexusProfile(g))
|
||||
return true;
|
||||
return !!g.leaderSkills && (standaloneE3NexusLeaderSkillsProfile(g) ||
|
||||
pairedE3NexusLeaderSkillsProfile(g) || mixedE3NexusLeaderSkillsProfile(g));
|
||||
}
|
||||
|
||||
type AmbassadorPurchaseSource = {
|
||||
buyer: Player | null;
|
||||
entry: NonNullable<Game['pendingAmbassador']> | null;
|
||||
event: string;
|
||||
blocked: string | null;
|
||||
};
|
||||
|
||||
/** Original consumed-token evidence plus the buyer's private eligibility. The
|
||||
* physical supply is skipped only for the card an already committed draw bound. */
|
||||
function ambassadorPurchaseSource(g: Game, owner: string, supply: boolean): AmbassadorPurchaseSource {
|
||||
const entry = g.pendingAmbassador ?? null;
|
||||
const buyer = text(owner) ? g.players.find(p => p.id === owner) ?? null : null;
|
||||
const token = entry && g.ecazAmbassadors?.tokens.find(t => t.id === entry.token);
|
||||
const event = entry && text(entry.event) && text(owner)
|
||||
? emperorNexusAmbassadorPurchaseEvent(g.turn, g.phase, owner,
|
||||
g.nexusEmperorPurchaseHistory?.length ?? 0, entry.event)
|
||||
: '';
|
||||
let blocked: string | null = null;
|
||||
if (!emperorNexusAmbassadorPurchaseModeSupported(g))
|
||||
blocked = 'Emperor Nexus Ambassador purchase requires a supported native Ecaz Nexus profile with Emperor absent.';
|
||||
else if (g.status !== 'playing')
|
||||
blocked = 'Emperor Nexus Ambassador purchase needs the original game still in progress.';
|
||||
else if (!entry || !text(entry.event) || !text(entry.token))
|
||||
blocked = 'The original Richese Ambassador trigger is no longer current.';
|
||||
else if (!buyer)
|
||||
blocked = 'Emperor Nexus Ambassador purchase lost its original purchaser.';
|
||||
else if (entry.effect !== 'richese')
|
||||
blocked = 'The original Richese Ambassador effect is no longer active.';
|
||||
else if (entry.beneficiary !== owner ||
|
||||
entry.owner !== g.players.find(p => p.faction === 'ecaz')?.id)
|
||||
blocked = 'The original Richese Ambassador purchase belongs to another Ecaz beneficiary.';
|
||||
else if (entry.turn !== g.turn || entry.phase !== g.phase || !ambassadorPhaseAllowed(g))
|
||||
blocked = 'The original Richese Ambassador trigger belongs to another turn or phase.';
|
||||
else if (!validAmbassadorResume(g, entry))
|
||||
blocked = 'The original Richese Ambassador trigger lost its recorded entry source.';
|
||||
else if (entry.entrant === owner || !g.players.some(p => p.id === entry.entrant))
|
||||
blocked = 'The original Richese Ambassador trigger lost its entering player.';
|
||||
else if (!token || !((token.effect === 'richese' && token.zone === 'used') ||
|
||||
(token.effect === 'beneGesserit' && token.zone === 'removed' &&
|
||||
entry.copyChoices.includes('richese'))))
|
||||
blocked = 'The original Richese Ambassador token is no longer consumed for this effect.';
|
||||
if (blocked) throw new EmperorNexusPurchaseError(blocked);
|
||||
if (buyer!.ally)
|
||||
blocked = 'Emperor Nexus Secret Ally requires an unallied buyer.';
|
||||
else if (!g.nexusCards?.cards)
|
||||
blocked = 'Hold the real Emperor Nexus Secret Ally card to retain your purchase price.';
|
||||
else {
|
||||
try {
|
||||
validateNexusCards(g.nexusCards.cards, g.players);
|
||||
if (g.nexusCards.cards.hands[owner] !== 'emperor' ||
|
||||
nexusCardMode('emperor', buyer!.faction, g.players.map(p => p.faction)) !== 'secretAlly')
|
||||
blocked = 'Hold the real Emperor Nexus Secret Ally card to retain your purchase price.';
|
||||
else if (buyer!.hand.length >= handLimit(buyer!))
|
||||
blocked = 'The Richese Ambassador purchase needs room in the buyer’s hand.';
|
||||
else if (!whole(buyer!.spice) || buyer!.spice < 3)
|
||||
blocked = 'The Richese Ambassador purchase needs the three-spice price held in the buyer’s own spice.';
|
||||
else if (supply && !g.deck.length && !g.discard.length)
|
||||
blocked = 'No Treachery Card is available for the Ambassador purchase.';
|
||||
} catch (error) {
|
||||
blocked = error instanceof Error ? error.message
|
||||
: 'Hold the real Emperor Nexus Secret Ally card to retain your purchase price.';
|
||||
}
|
||||
}
|
||||
return { buyer, entry, event, blocked };
|
||||
}
|
||||
|
||||
/** Pre-commit proof only. Private room, spice, supply and Nexus face disable
|
||||
* Use without gating Pay; malformed original sources remain hard errors. */
|
||||
export function quoteEmperorNexusAmbassadorPurchase(
|
||||
g: Game, owner: string,
|
||||
): { event: string; price: 3; blocked: string | null } {
|
||||
const source = ambassadorPurchaseSource(g, owner, true);
|
||||
return { event: source.event, price: 3, blocked: source.blocked };
|
||||
}
|
||||
|
||||
/** Bind the original purchase to the card the engine actually drew before the
|
||||
* buyer's hand changes. The draw already removed it from the deck, so absence
|
||||
* from the physical stock is valid; a duplicate or an undrawn card is not. */
|
||||
export function createEmperorNexusAmbassadorPurchase(
|
||||
g: Game, owner: string, card: string,
|
||||
): EmperorNexusAmbassadorPurchase {
|
||||
if (!text(owner) || !text(card))
|
||||
throw new EmperorNexusPurchaseError(
|
||||
'Emperor Nexus Ambassador purchase needs its original buyer and drawn Treachery Card.',
|
||||
);
|
||||
const source = ambassadorPurchaseSource(g, owner, false);
|
||||
if (source.blocked) throw new EmperorNexusPurchaseError(source.blocked);
|
||||
const buyer = source.buyer!, entry = source.entry!;
|
||||
const physical = [
|
||||
...g.players.flatMap(p => p.hand), ...g.deck, ...g.discard,
|
||||
...(g.richeseCache ?? []), ...(g.richeseRemoved ?? []),
|
||||
...(g.ixSetupCards ?? []), ...(g.ixAuction?.cards ?? []),
|
||||
...(g.ornithopter ? [g.ornithopter.card] : []),
|
||||
].filter(c => c.id === card);
|
||||
if (physical.length !== 0)
|
||||
throw new EmperorNexusPurchaseError(
|
||||
'Emperor Nexus Ambassador purchase lost its single physically drawn Treachery Card.',
|
||||
);
|
||||
const receipt: EmperorNexusAmbassadorPurchase = {
|
||||
version: 3, kind: 'purchase', event: source.event, owner: buyer.id,
|
||||
turn: g.turn, phase: g.phase, sequence: g.nexusEmperorPurchaseHistory?.length ?? 0,
|
||||
faction: buyer.faction, advanced: g.advanced, origin: 'ambassador', price: 3,
|
||||
card, beforeSpice: buyer.spice, afterSpice: buyer.spice, signature: '',
|
||||
entry: entry.event, token: entry.token, entrant: entry.entrant,
|
||||
};
|
||||
receipt.signature = signEmperorNexusPurchase(receipt);
|
||||
validateEmperorNexusPurchase(g, receipt);
|
||||
return receipt;
|
||||
}
|
||||
+14
-12
@@ -1387,7 +1387,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'When another eligible faction enters a marked stronghold, Ecaz may trigger its Ambassador for itself or its current ally, or leave the token in place. Ecaz itself, its ally, advisors and the faction matching the physical Ambassador do not trigger it. Entries with competing arrival reactions remain blocked until their ordering is implemented; a rejected entry does not spend or move its pieces.',
|
||||
'Emperor grants five bank spice. Atreides records the entrant’s current Treachery hand privately for the beneficiary; Harkonnen records one randomly selected held Traitor identity, or Face Dancer identity for a Tleilaxu entrant. These snapshots remain in the recipient’s private inspection history, identify when they were taken, and do not become a live view of later changes. Opening, enlarging or closing them needs no game acknowledgement.',
|
||||
'CHOAM lets the beneficiary choose any available hand cards, including none, for three spice each. Ixians require one available card to discard before drawing its replacement. Card controls respect projected reservations. After committing an Ixian exchange, a voluntary interruption must leave a card available for its required discard; a queued Truthtrance counts as already committed. A Bene Gesserit trigger permanently removes its token, then the beneficiary chooses an available effect outside the original supply for this group; unsupported copies remain visibly blocked.',
|
||||
'The Richese Ambassador automatically spends three of the chosen beneficiary’s spice for the top Treachery Card if the purchase can be completed. There is no extra purchase confirmation. An unavailable purchase consumes the triggered token without charging spice or granting a card; the public outcome does not disclose whether the beneficiary lacked spice or hand space. Emperor receives another faction’s payment unless that income is canceled. A Harkonnen buyer may receive its normal bonus card, subject to its hand limit and Karama. These benefits finish before movement or a worm ride resumes. Ixian allied replacement requires Bidding, so it does not apply to these entries.',
|
||||
'The Richese Ambassador purchases the top Treachery Card for three of the chosen beneficiary’s own spice when executable. Normally this is automatic; in supported Emperor-absent Nexus tables, the committed trigger instead offers uniform beneficiary Pay or eligible Emperor Nexus Use, with no new decline. Under the provisional purchase composition, an actual unallied holder may prove/retain that price, with no bank payment or reimbursement. Original private inability still consumes the token without payment/draw and does not disclose the reason. Native Emperor income and Harkonnen bonus remain applicable to their original sources, with hand limits and Karama; original movement/worm continuation runs once. Ixian replacement still requires Bidding.',
|
||||
'The Fremen Ambassador lets the beneficiary relocate one group already on the board directly to any available territory and sector, subject to storm and occupancy. Select physical forces from one territory, preserve elite quantities, and optionally include your concealed No-Field without revealing it. This independent move costs no shipment spice and does not use ordinary movement allowances. It can change sectors within one territory, but pieces already in the destination sector stay in place. Homeworlds and reserves are not board territories.',
|
||||
'The mobile stronghold can receive a non-Ixian group only from the territory it points to. Ecaz and its ally may co-occupy under Occupy. Advisors keep their applicable stance and accompaniment locks; an eligible arrival can request fighters. CHOAM may use Baliset during Shipment and Movement; if allowed, choose another legal destination. Intrusion or Terror resolves before the original entrant continues. A destination that causes both settles the Intrusion first and then offers the committed Terror entry; no payment or force transfer replays. There is no extra decline after triggering; if no legal move remains, the effect finishes without moving pieces and its token stays consumed.',
|
||||
'The Guild Ambassador grants an immediate free shipment of zero through four physical reserve forces for Ecaz or its ally, including typed elites. Choose a clear territory and sector; this does not spend ordinary shipment or movement, ally aid or a retained rate card. Fremen may use destinations outside their ordinary reinforcement radius, but their southern reserves stay on-planet and this effect never permits storm entry. Only Ixians may ship directly into their placed mobile stronghold.',
|
||||
@@ -4346,7 +4346,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
category: 'Advanced & expansions',
|
||||
coverage: 'Partial',
|
||||
developmentStage: 'Prototyped',
|
||||
summary: 'With Emperor absent, an unallied holder can choose exactly three additional free returns or retain a personally affordable original purchase price. Classic bank auctions and original Richese cache/Advanced Black Market now have distinct connected paths. Allied funding, direct CHOAM/Ambassador purchases and complete combinations remain unfinished.',
|
||||
summary: 'With Emperor absent, an unallied holder can choose exactly three extra free returns or retain a personally affordable original purchase price. Classic bank auctions, Richese cache/Advanced Black Market and the Richese Ambassador have distinct connected paths. Other real purchase sources and full combinations retain separate contracts.',
|
||||
steps: [
|
||||
'Use the private Emperor Secret Ally controls during Revival. Select the physical elite count when more than one legal group is available. The card returns exactly three eligible forces from Tanks to reserves and is discarded once.',
|
||||
'These returns cost no spice and do not consume ordinary force-revival or free-revival allowances. You may use the card before or after ordinary revivals, while Revival remains open.',
|
||||
@@ -4354,14 +4354,15 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'The independent revival grant enters original pending revival. Under the visible provisional policy, native Tleilaxu prevention/income, typed reserves, Tech and eligible Homeworld handling remain; ordinary free-rate blocking does not reprice it. Keeping the card creates no extra public confirmation.',
|
||||
'At an eligible classic normal bank-auction winner payment, prove the positive final bid from your own spice and spend Emperor Nexus to receive the lot without losing spice. Every winner sees the same payment confirmation; the provisional recommendation is selected under the decision priority. Ordinary spice or legal Karama remains available.',
|
||||
'Original Richese cache and Advanced Black Market offer Pay or eligible Emperor Nexus Use after original Richese Betrayal. The provisional seller-unpaid policy retains your full personally affordable price without bank reimbursement. One original lot, native Harkonnen bonus and source successor continue; legal self-cache is included but self-Black remains prohibited. Original self-purchase forced discard cannot consume this Nexus or acquire a card.',
|
||||
'A committed Richese Ambassador trigger now offers its original beneficiary Pay or eligible Emperor Nexus Use, with no new decline. Under the visible provisional purchase composition, prove and keep three own spice while drawing one original deck card; no bank payment, reimbursement or fake auction is made. Original private inability, native bonus and entrant movement/worm continuation remain. Allies cannot Use; actual forming-alliance Nexus disposal stays original.',
|
||||
],
|
||||
related: ['nexus-cards', 'revival'],
|
||||
checklist: [
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Independent extra-three enters original pending revival. Distinct classic bank and original Richese seller paths retain personally affordable price and one actual lot with signed source receipts. Original seller/stock/bonus/successor run with no invented reimbursement. Allied and other acquisition producers remain open.', evidence: ['game/nexus-emperor-secret-ally.ts', 'game/nexus-emperor-purchase.ts', 'game/auction-continuation-quote.ts', 'game/engine.ts'] },
|
||||
{ area: 'Player controls', status: 'Partial', detail: 'Private typed revival and public uniform buyer confirmations with eligible Use, retained-price proof and visible provisional seller-unpaid policy; original Pay and native reactions remain.', evidence: ['components/nexus-emperor-secret-ally.tsx', 'components/nexus-emperor-seller-purchase.tsx', 'components/nexus-cards.tsx', 'components/game-table.tsx'] },
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Independent extra-three remains. Distinct original bank/Richese seller/Ambassador paths retain personally affordable price and actual acquisition with signed source receipts. Native draw/recycling, hand limits, bonus and source successor continue; no invented reimbursement or fake auction. Other real sources and full combinations remain open.', evidence: ['game/nexus-emperor-secret-ally.ts', 'game/nexus-emperor-purchase.ts', 'game/auction-continuation-quote.ts', 'game/engine.ts', 'game/ambassador-phase.ts', 'game/ambassador-resume.ts'] },
|
||||
{ area: 'Player controls', status: 'Partial', detail: 'Private typed revival and uniform original winning-buyer/committed-beneficiary confirmations with eligible Use, price proof and visible provisional source policy. Actual Pay, Truthtrance and original entrant controls remain; no new beneficiary veto.', evidence: ['components/nexus-emperor-secret-ally.tsx', 'components/nexus-emperor-seller-purchase.tsx', 'components/nexus-emperor-ambassador-purchase.tsx', 'components/nexus-cards.tsx', 'components/game-table.tsx'] },
|
||||
{ area: 'AI', status: 'Partial', detail: 'Minimal profiles use projected legal revival and original personally funded purchase choices, or Pay, without inspecting rival private custody. Complete strategy/calibration remain unfinished.', evidence: ['game/nexus-emperor-secret-ally-options.ts', 'game/bots.ts'] },
|
||||
{ area: 'Documentation', status: 'Partial', detail: 'Printed alternatives, independent revival and original bank/Richese purchases distinguish provisional native prevention, uniform timing and seller-unpaid recommendation from publisher rulings. Other acquisition sources and complete combinations remain open.', evidence: ['docs/NEXUS_EMPEROR_RULES.md', 'docs/NEXUS_EMPEROR_SECRET_ALLY_RUNTIME.md', 'docs/RULE_DECISIONS.md'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Current seller10-file union108/108, types/lint/build and twenty original API profiles pass. Actual390px cache/Black/self-cache/diverted-cache Use proves unchanged wallets, one lot and native bonus/successor without overflow/errors. Prior bank/revival evidence remains; broader acceptance and assurance remain open.', evidence: ['tests/nexus-emperor-secret-ally.test.ts', 'tests/nexus-emperor-secret-ally-recovery.test.ts', 'tests/nexus-emperor-purchase-engine.test.ts', 'tests/nexus-emperor-purchase-recovery.test.ts', 'tests/nexus-emperor-secret-ally-purchase-controls.test.ts', 'tests/fixture-nexus-emperor-seller.ts', 'tests/nexus-emperor-seller-purchase.test.ts', 'tests/nexus-emperor-seller-runtime.test.ts'] },
|
||||
{ area: 'Documentation', status: 'Partial', detail: 'Printed alternatives and original purchase classification distinguish independent revival, bank/Richese seller/Ambassador source compositions from publisher rulings. Other real acquisition sources and full combinations retain separate contracts; CHOAM closing sales/trades are not direct purchase producers.', evidence: ['docs/NEXUS_EMPEROR_RULES.md', 'docs/NEXUS_EMPEROR_SECRET_ALLY_RUNTIME.md', 'docs/RULE_DECISIONS.md', 'docs/RICHESE_AMBASSADOR_RULES.md', 'docs/RICHESE_AMBASSADOR_IMPLEMENTATION.md'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Current Ambassador13-file union111/111, types/lint/build and eight original API profiles pass. Four actual390px surfaces prove Pay3/Use0, one card and native movement/spice continuation without overflow/errors. Native allied Hark bonus, original alliance disposal and Truthtrance remain. Prior seller108/108/twenty API profiles and bank/revival evidence remain; broad assurance, combinations and deployed acceptance are open.', evidence: ['tests/nexus-emperor-secret-ally.test.ts', 'tests/nexus-emperor-secret-ally-recovery.test.ts', 'tests/nexus-emperor-purchase-engine.test.ts', 'tests/nexus-emperor-purchase-recovery.test.ts', 'tests/nexus-emperor-secret-ally-purchase-controls.test.ts', 'tests/fixture-nexus-emperor-seller.ts', 'tests/nexus-emperor-seller-purchase.test.ts', 'tests/nexus-emperor-seller-runtime.test.ts', 'tests/fixture-nexus-emperor-ambassador.ts', 'tests/nexus-emperor-ambassador-purchase.test.ts', 'tests/nexus-emperor-ambassador-runtime.test.ts'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -4571,7 +4572,8 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'Emperor Cunning requires an Advanced battle with at least five ordinary Emperor counters and no actual Sardaukar, before submitting your plan. The played Nexus card opens one Karama response. Allowing it makes five ordinary counters count as Sardaukar for this battle; cancellation prevents the entire enhancement and leaves the Nexus card spent.',
|
||||
'Temporary Sardaukar use the applicable strength and support rules, including the Fremen exception, but remain physically ordinary counters. Their losses go to Tanks as ordinary counters, without changing starred reserves or Tanks. Existing inspected or promised plans remain binding. The role ends after this battle; fewer-than-five groups remain unavailable. Secret Ally independent revival, classic bank and original Richese seller purchases have connected controls; compulsory allied funding remains the separate operation below. Other acquisition sources and full combinations remain open.',
|
||||
'Emperor Secret Ally revival selects exactly three real normal/starred counters for free, outside ordinary total/free and native allied-extra usage. Declaration spends one Nexus and enters original pending revival; no leader or absent native payer is granted. Under the visible provisional independent-source policy, native Tleilaxu may prevent it and actual free returns award income once; ordinary free-rate blocking and Recruits do not reprice or double this fixed grant. Denial keeps both spent cards without forces/income/Tech or refund. Native typed caps, reserve deposits, Axlotl and eligible newly returned Fedaykin placement continue. Tleilax ordinary Free Revival deployment is not extended to this extra source. Source receipts distinguish requested, returned and denied; old physical receipts never replay.',
|
||||
'Original Richese cache and Advanced Black Market sold lots offer every buyer Pay after original Richese Betrayal. An eligible unallied Emperor Nexus holder, with Emperor absent and full price personally affordable, may Use to keep that spice and leave seller/bank unpaid under the visible provisional recommendation. Actual source stock, hand limits, native Harkonnen bonus and successor continue once. Legal self-cache is included; self-Black remains prohibited. Original self-discard produces no acquisition or Emperor Nexus spend. Direct CHOAM/Ambassador purchases remain open.',
|
||||
'Original Richese cache and Advanced Black Market sold lots offer every buyer Pay after original Richese Betrayal. An eligible unallied Emperor Nexus holder, with Emperor absent and full price personally affordable, may Use to retain it and leave seller/bank unpaid under the visible provisional recommendation. Actual stock, hand limits, native Harkonnen bonus and successor run once. Legal self-cache is included; self-Black remains blocked. Original self-discard gives no acquisition or Emperor Nexus spend.',
|
||||
'Original Richese Ambassador trigger offers the beneficiary uniform Pay or eligible Emperor Nexus Use, independently of hidden face or private availability. Under provisional purchase composition, an actual unallied holder proves/retains three own spice and acquires one original deck card, with no bank payment, reimbursement or fake auction. Native source phase/resume authority, private inability, draw/recycling, bonus and movement/worm suffix remain. An ally cannot Use; forming alliance disposes held Nexus originally. CHOAM closing sales/trades are not direct purchases.',
|
||||
'Emperor Betrayal has a battle alternative in Advanced play: an unallied opponent holding the Emperor Nexus card can spend it during the ordinary pre-plan Sardaukar response to suppress actual Sardaukar strength for this battle. Every seat receives the same response opportunity even with no card, keeping hidden custody private. The starred pieces remain physical Sardaukar; no card grants a new force or cancels their separate support benefit.',
|
||||
'During an original positive normal-auction purchase by Emperor’s ally, possible unallied held-Nexus rivals acknowledge in storm order before winner payment, regardless of secret face. Only the current actual Emperor-card holder may Use; Pass keeps cards and original payment. Under the provisional conservative full-price interpretation, native Emperor covers the full original price, reusing prepaid aid and adding only missing own spice. The buyer keeps their own spice and acquires one original card. Native Emperor income remains separately cancelable without refund; original next-lot inspection continues. No excess gift, bank-created subsidy or future-income borrowing is granted. Underfunded/fully prepaid Use is unavailable; special seller lots and full combinations remain open.',
|
||||
'With Fremen absent, an actual unallied Fremen-card holder may enable a fixed ordinary free quota of three at a quiet Revival boundary, then use normal partial/typed revival controls. Activation moves no counters, leader or spice. Actual prior free use counts; paid returns keep their cost and total usage. Native total limits, higher free rates, typed caps, prevention, Homeworld deposits/deployment, Axlotl activity and once-only Tleilaxu income remain original. Recruits doubles its native rate, not this fixed quota. Useless or unknown-ledger grants are unavailable. This is the visibly provisional recommended interpretation; strict old physical receipts adopt metadata without replay. Independent sandworm protection remains the separate operation below.',
|
||||
@@ -4601,11 +4603,11 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
],
|
||||
related: ['ecaz-modules', 'spice-blow', 'alliance-funding', 'choam-worthless', 'choam-kull', 'nexus-choam-kull', 'nexus-richese-betrayal', 'nexus-guild-betrayal', 'nexus-ixian-replacement', 'nexus-ixian-betrayal'],
|
||||
checklist: [
|
||||
{area:'Implementation',status:'Partial',detail:"Existing extra revival and compulsory funding remain. Emperor Secret Ally now retains personally affordable original Richese cache/Advanced Black Market price after original Betrayal, with strict signed source receipts. Seller/bank receive no invented reimbursement; actual stock, hand limits, Hark bonus and successor continue. Basic/Advanced cache and legal self-cache are included; self-Black stays blocked. Direct CHOAM/Ambassador purchases, wider combinations and assurance remain open.",evidence:['game/nexus-cards.ts', 'game/nexus-card-phase.ts', 'game/nexus-traitor-exchange.ts', 'game/traitor-declarations.ts', 'game/nexus-face-dancers.ts', 'game/nexus-suboids.ts', 'game/nexus-advisors.ts', 'game/nexus-sardaukar-options.ts', 'game/nexus-fremen-revival.ts', 'game/nexus-fremen-cunning.ts', 'game/nexus-ecaz-betrayal.ts', 'game/nexus-ecaz-inquiry.ts', 'game/nexus-ecaz-duke.ts', 'game/duke-vidal.ts', 'game/nexus-emperor-secret-ally.ts', 'game/choam-power-options.ts', 'game/nexus-moritani-options.ts', 'game/nexus-moritani-betrayal.ts', 'game/nexus-richese-options.ts', 'game/nexus-richese-betrayal.ts', 'game/nexus-guild-cunning-options.ts', 'game/nexus-guild-secret-ally-options.ts', 'game/nexus-module-profile.ts', 'game/engine.ts', 'game/nexus-tleilaxu-revival.ts', 'game/nexus-borrowed-voice.ts', 'game/combat-response-quote.ts', 'game/karama-context.ts', 'game/nexus-fremen-protection.ts', 'game/admin-seat-ai-shape.ts', 'game/richese-cancellation.ts', 'game/bot-mobility.ts', 'game/planetologist-movement.ts', 'game/revival.ts', 'game/nexus-fremen-revival-options.ts','game/nexus-emperor-payment.ts', 'game/nexus-emperor-secret-ally.ts', 'game/revival-resume.ts', 'game/revival-cancellation.ts', 'game/homeworld-revival-deployment.ts', 'game/homeworld-revival-return.ts', "game/nexus-emperor-purchase.ts", "game/auction-continuation-quote.ts"]},
|
||||
{area:'Player controls',status:'Partial',detail:"Uniform winning-buyer Pay follows original Richese Betrayal regardless concealed face. Actual eligible holder sees Use with retained price and visible provisional seller-unpaid policy. Original Truthtrance discard and bonus controls continue. Actual390px cache, Black Market, self-cache and diverted cache prove usable controls without overflow/errors. Wider surfaces and complete-mode acceptance remain open.",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"]},
|
||||
{area:'AI',status:'Partial',detail:"Minimal profiles use the shared projected seller entitlement and original Pay otherwise, without inspecting rival private custody. Existing extra-return, compulsory funding and other Nexus adapters remain. Strategy and four-tier calibration remain deferred and unfinished.",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:"Printed alternatives retain explicit partial gates. Original Richese seller composition now selects visibly provisional unpaid-seller recommendation under the decision priority, not publisher clarification or a numerical user ruling. Original Betrayal precedes buyer confirmation; direct CHOAM/Ambassador sources and full combinations remain 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/NEXUS_GUILD_RULES.md', "docs/NEXUS_EMPEROR_SECRET_ALLY_RUNTIME.md", "docs/RULE_DECISIONS.md"]},
|
||||
{area:'Verification',status:'Partial',detail:"Seller affected10-file union108/108 and types/lint/build pass. Genuine original setup/worm/pact/draw and positive cache/Black Market sales exercise Pay/Use, source stock, actual Hark bonus, self-cache, Truthtrance and original Betrayal pass/diversion/self-discard. Actual390px four surfaces prove unchanged wallets, one original card and native source successor without overflow/errors. Prior extra266/266, funding155/155 and quota329/329 evidence remains. Full combinations, assurance and deployed acceptance remain open.",evidence:['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-controls.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/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_SECRET_ALLY_RUNTIME.md', "tests/fixture-nexus-emperor-seller.ts", "tests/nexus-emperor-seller-purchase.test.ts", "tests/nexus-emperor-seller-runtime.test.ts"]},
|
||||
{area:'Implementation',status:'Partial',detail:"Existing independent revival, compulsory funding and Richese seller paths remain. Emperor Secret Ally now applies to the original Richese Ambassador purchase through uniform beneficiary confirmation after trigger. Version3 source evidence binds original token, entry, phase, buyer and actual card; native draw/recycling and movement/worm continuation run once. No bank payment, reimbursement or fake auction is invented. Original source-bound Homeworld phase authority remains; full combinations and assurance are open.",evidence:['game/nexus-cards.ts', 'game/nexus-card-phase.ts', 'game/nexus-traitor-exchange.ts', 'game/traitor-declarations.ts', 'game/nexus-face-dancers.ts', 'game/nexus-suboids.ts', 'game/nexus-advisors.ts', 'game/nexus-sardaukar-options.ts', 'game/nexus-fremen-revival.ts', 'game/nexus-fremen-cunning.ts', 'game/nexus-ecaz-betrayal.ts', 'game/nexus-ecaz-inquiry.ts', 'game/nexus-ecaz-duke.ts', 'game/duke-vidal.ts', 'game/nexus-emperor-secret-ally.ts', 'game/choam-power-options.ts', 'game/nexus-moritani-options.ts', 'game/nexus-moritani-betrayal.ts', 'game/nexus-richese-options.ts', 'game/nexus-richese-betrayal.ts', 'game/nexus-guild-cunning-options.ts', 'game/nexus-guild-secret-ally-options.ts', 'game/nexus-module-profile.ts', 'game/engine.ts', 'game/nexus-tleilaxu-revival.ts', 'game/nexus-borrowed-voice.ts', 'game/combat-response-quote.ts', 'game/karama-context.ts', 'game/nexus-fremen-protection.ts', 'game/admin-seat-ai-shape.ts', 'game/richese-cancellation.ts', 'game/bot-mobility.ts', 'game/planetologist-movement.ts', 'game/revival.ts', 'game/nexus-fremen-revival-options.ts','game/nexus-emperor-payment.ts', 'game/nexus-emperor-secret-ally.ts', 'game/revival-resume.ts', 'game/revival-cancellation.ts', 'game/homeworld-revival-deployment.ts', 'game/homeworld-revival-return.ts', "game/nexus-emperor-purchase.ts", "game/auction-continuation-quote.ts", "game/nexus-emperor-purchase.ts", "game/ambassador-phase.ts", "game/ambassador-resume.ts"]},
|
||||
{area:'Player controls',status:'Partial',detail:"Actual original buyer sees Pay and eligible Use after committed Ambassador trigger, with visible provisional purchase composition and no extra decline. Original Truthtrance discard restores the same decision; entrant control remains native. Actual390px Basic Use, Advanced Pay, irrelevant-card Pay and phase-one worm Use prove one original card and native successor without overflow/errors. Existing Nexus surfaces remain; complete-mode acceptance stays open.",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"]},
|
||||
{area:'AI',status:'Partial',detail:"Minimal profiles use shared projected original Ambassador entitlement or native Pay, without private rival inspection or borrowing entrant control. Existing independent return, compulsory funding and seller adapters remain. Strategy and four-tier calibration remain deferred and unfinished.",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:"Printed purchase panel and existing Richese Ambassador purchase classification compose outside Bidding under a visibly provisional recommendation. Uniform confirmation does not add a beneficiary veto; bank payment/reimbursement and fake auctions are not invented. CHOAM closing market only sells/trades, not a missing direct purchase producer. Other real sources, complete combinations and publication gates retain separate contracts.",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/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"]},
|
||||
{area:'Verification',status:'Partial',detail:"Ambassador affected13-file union111/111 and types/lint/build pass. Eight actual Basic/Advanced shipment/worm API profiles prove Pay3 versus Use0, actual card and native suffix. Actual390px four surfaces prove Basic11 to11, Advanced11 to8, irrelevant-card Pay and phase-one worm11 to11 with one card and original entrant/spice controls, no overflow/errors. Native Hark allied Pay bonus once, original alliance disposal and actual Truthtrance remain. Fixture-only nonexistent Spice Card/retained-allied-Nexus assumptions and action/default pins were removed. Prior seller108/108, extra266/266, funding155/155 and quota329/329 evidence remains; broad assurance/deployed acceptance stay open.",evidence:['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-controls.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/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_SECRET_ALLY_RUNTIME.md', "tests/fixture-nexus-emperor-seller.ts", "tests/nexus-emperor-seller-purchase.test.ts", "tests/nexus-emperor-seller-runtime.test.ts", "tests/fixture-nexus-emperor-ambassador.ts", "tests/nexus-emperor-ambassador-purchase.test.ts", "tests/nexus-emperor-ambassador-runtime.test.ts"]},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -92,6 +92,7 @@ const DECISIONS = {
|
||||
nexusFremenProtection: true,
|
||||
nexusEmperorPayment: true,
|
||||
nexusEmperorSellerPurchase: true,
|
||||
nexusEmperorAmbassadorPurchase: true,
|
||||
nexusFremenCunningRide: true,
|
||||
moritaniRetention: true,
|
||||
moritaniSpecialKarama: true,
|
||||
|
||||
Reference in new issue
Block a user