Bind direct Richese Nexus purchases and release paid bid commitments
This commit is contained in:
1 parent
32b139281c
commit
5d13374f7e
16 files changed
+793
-56
No files matched your search
+56
-14
@@ -56,7 +56,7 @@ import { moritaniTerrorAssassinationPool, moritaniTerrorAssassinationValue } fro
|
||||
import { createNexusGuildSecretAlly, validateNexusGuildSecretAlly, quoteNexusGuildSecretShipment, type NexusGuildSecretAllyReceipt } from './nexus-guild-secret-ally';
|
||||
import { shipmentAvailable } from './shipment-opportunity';
|
||||
import { createGuildBetrayalInvoice, createGuildBetrayalReceipt, guildBetrayalEligible, guildBetrayalResponders, validateGuildBetrayalInvoice, validateGuildBetrayalHistory, nexusGuildBetrayalSourceReceipt, type GuildBetrayalInvoice, type GuildBetrayalCursor, type GuildBetrayalReceipt, type GuildBetrayalAuthority } from './nexus-guild-betrayal';
|
||||
import { createIxianReplacementSource, validateIxianReplacementSource, closeIxianReplacementSource, validateIxianReplacementHistory, initialIxianReplacementCursor, ixianReplacementEvent, type IxianReplacementSource, type IxianReplacementReceipt, type IxianReplacementCursor } from './nexus-ixian-replacement';
|
||||
import { createIxianReplacementSource, validateIxianReplacementSource, closeIxianReplacementSource, validateIxianReplacementHistory, initialIxianReplacementCursor, ixianReplacementEvent, type IxianReplacementSource, type IxianReplacementReceipt, type IxianReplacementCursor, type RicheseSpecialPurchaseReceipt } from './nexus-ixian-replacement';
|
||||
import { createIxianBetrayalSource, validateIxianBetrayalFrame, initialIxianBetrayalCursor, closeIxianBetrayalSource, validateIxianBetrayalHistory, ixianBetrayalResponders, ixianBetrayalEligible, type IxianBetrayalSource, type IxianBetrayalCursor, type IxianBetrayalReceipt } from './nexus-ixian-betrayal';
|
||||
import { createHarkonnenBetrayalSource, validateHarkonnenBetrayalFrame, initialHarkonnenBetrayalCursor, closeHarkonnenBetrayalSource, validateHarkonnenBetrayalHistory, harkonnenBetrayalResponders, harkonnenBetrayalEligible, createHarkonnenBetrayalReplacement, validateHarkonnenBetrayalReplacements, drawHarkonnenBetrayalReplacement, type HarkonnenBetrayalSource, type HarkonnenBetrayalCursor, type HarkonnenBetrayalReceipt, type HarkonnenBetrayalReplacement, type HarkonnenBetrayalAuthority } from './nexus-harkonnen-betrayal';
|
||||
import { createNexusGuildCunning, validateNexusGuildCunning, nexusGuildCunningMoves, type NexusGuildCunningReceipt } from './nexus-guild-cunning';
|
||||
@@ -1232,9 +1232,12 @@ export type Game = {
|
||||
/** Explicit development profile; never enabled by a public start or player action. */
|
||||
kullPreview?: boolean;
|
||||
nexusKullPreview?: boolean;
|
||||
/** Fresh bounded classic Nexus purchased-card prototype; never inferred for old saves. */
|
||||
/** Fresh bounded Nexus purchased-card prototype; never inferred for old saves. */
|
||||
nexusIxianReplacementPreview?: true;
|
||||
pendingNexusIxianReplacement?: IxianReplacementSource | null;
|
||||
pendingNexusIxianSpecialPurchase?: (RicheseSpecialPurchaseReceipt & {
|
||||
resume: Pick<Game, 'active' | 'response' | 'decision' | 'pendingKarama'>;
|
||||
}) | null;
|
||||
nexusIxianReplacementHistory?: IxianReplacementReceipt[];
|
||||
nexusIxianReplacementCursor?: IxianReplacementCursor;
|
||||
/** Fresh native Ixian attempt prevention; never inferred from held cards. */
|
||||
@@ -9913,6 +9916,7 @@ function decideIxianNexusBetrayal(g: Game,id: string,action: Action): void {
|
||||
/** Fresh classic, Tleilaxu or Richese profile; no public start or retrofit. */
|
||||
export function initializeIxianNexusReplacementGameForAudit(state: Game): Game {
|
||||
requireRule(!state.nexusIxianReplacementPreview && !state.pendingNexusIxianReplacement &&
|
||||
!state.pendingNexusIxianSpecialPurchase &&
|
||||
state.nexusIxianReplacementHistory === undefined && state.nexusIxianReplacementCursor === undefined &&
|
||||
ixianNexusReplacementModeSupported(state),
|
||||
'Ixian Nexus replacement needs a fresh classic/base, Tleilaxu/Ix or Richese/CHOAM lobby with Nexus alone.');
|
||||
@@ -9948,20 +9952,25 @@ function ixianNexusReplacementParent(g: Game): string {
|
||||
...(byFaction(g, 'richese') ? { richeseBidding:g.richeseBidding,
|
||||
richeseCache:g.richeseCache,richeseRemoved:g.richeseRemoved,
|
||||
...(g.richeseAuction ? { richeseAuction:g.richeseAuction } : {}) } : {}),
|
||||
...(g.pendingNexusIxianSpecialPurchase ? { specialPurchase:g.pendingNexusIxianSpecialPurchase,
|
||||
purchaseIncome:g.pendingRichesePurchaseIncome } : {}),
|
||||
decision:g.decision,response:g.response,phaseOpening:g.phaseOpening});
|
||||
}
|
||||
function ixianNexusReplacementContext(g: Game) {
|
||||
requireRule(g.auction || g.richeseAuction, 'The Nexus purchased-card choice lost its original auction.');
|
||||
requireRule(g.pendingNexusIxianSpecialPurchase || g.auction || g.richeseAuction,
|
||||
'The Nexus purchased-card choice lost its original purchase.');
|
||||
return {status:g.status,phase:g.phase,turn:g.turn,sequence:g.nexusIxianReplacementCursor!.sequence,
|
||||
advanced:g.advanced,expansions:g.expansions,richese:!!byFaction(g,'richese'),
|
||||
auction:g.auction,richeseAuction:g.richeseAuction ?? null,sale:g.currentAuctionSale ?? null,
|
||||
specialPurchase:g.pendingNexusIxianSpecialPurchase ?? null,
|
||||
players:g.players.map(p => ({id:p.id,hand:p.hand,handLimit:handLimit(p)})),
|
||||
physicalCards:physicalTreacheryCards(g)};
|
||||
}
|
||||
function ixianNexusReplacementIntegrity(g: Game): void {
|
||||
const pending = g.pendingNexusIxianReplacement;
|
||||
const special = g.pendingNexusIxianSpecialPurchase;
|
||||
if (!g.nexusIxianReplacementPreview) {
|
||||
requireRule(g.nexusIxianReplacementPreview === undefined && !pending &&
|
||||
requireRule(g.nexusIxianReplacementPreview === undefined && !pending && !special &&
|
||||
g.nexusIxianReplacementHistory === undefined && g.nexusIxianReplacementCursor === undefined &&
|
||||
g.decision?.kind !== 'nexusIxianReplacement',
|
||||
'The Ixian Nexus replacement lost its explicit fresh profile.');
|
||||
@@ -9972,47 +9981,72 @@ function ixianNexusReplacementIntegrity(g: Game): void {
|
||||
nexusRule(() => validateIxianReplacementHistory(g.nexusIxianReplacementHistory!,
|
||||
g.nexusIxianReplacementCursor!,physicalTreacheryCards(g)));
|
||||
if (!pending) {
|
||||
requireRule(g.decision?.kind !== 'nexusIxianReplacement', 'The purchased-card choice lost its source.');
|
||||
requireRule(!special && g.decision?.kind !== 'nexusIxianReplacement', 'The purchased-card choice lost its source.');
|
||||
return;
|
||||
}
|
||||
const buyer = getPlayer(g,pending.buyer);
|
||||
if (special) requireRule(special.buyer === buyer.id && buyer.faction === 'richese' &&
|
||||
buyer.specialKaramaUsed === true && special.resume && typeof special.resume === 'object',
|
||||
'The direct Richese purchase lost its committed native buyer, once-use or original controls.');
|
||||
requireRule(!buyer.ally && !!g.nexusCards?.cards?.hands[buyer.id] &&
|
||||
g.decision?.kind === 'nexusIxianReplacement' && g.decision.player === buyer.id &&
|
||||
g.decision.event === pending.event &&
|
||||
Object.keys(g.decision).every(key => ['kind','player','event'].includes(key)) &&
|
||||
!g.response && !g.phaseOpening && !g.truthtrance && !g.pendingKarama && !g.pendingNullentropy &&
|
||||
!g.pendingTreacheryDiscard && (g.currentAuctionSale?.origin === 'normal'
|
||||
? !g.richeseAuction : !g.auction && !!g.richeseAuction),
|
||||
!g.pendingTreacheryDiscard && (special || (g.currentAuctionSale?.origin === 'normal'
|
||||
? !g.richeseAuction : !g.auction && !!g.richeseAuction)),
|
||||
'The purchased-card choice lost its buyer or native decision priority.');
|
||||
nexusRule(() => validateIxianReplacementSource(ixianNexusReplacementContext(g),pending,
|
||||
ixianNexusReplacementParent(g)));
|
||||
}
|
||||
function offerIxianNexusReplacement(g: Game): boolean {
|
||||
if (!g.nexusIxianReplacementPreview) return false;
|
||||
const special = g.pendingNexusIxianSpecialPurchase;
|
||||
const sale = g.currentAuctionSale;
|
||||
const buyer = sale && getPlayer(g,sale.winner);
|
||||
if (!sale || !buyer || buyer.ally || !g.nexusCards?.cards?.hands[buyer.id]) return false;
|
||||
const buyer = special ? getPlayer(g,special.buyer) : sale ? getPlayer(g,sale.winner) : null;
|
||||
if (!buyer || buyer.ally || !g.nexusCards?.cards?.hands[buyer.id]) return false;
|
||||
requireRule(!g.response && !g.phaseOpening && !g.truthtrance && !g.pendingKarama,
|
||||
'Finish the original auction priority before offering the purchased-card choice.');
|
||||
if (sale.origin !== 'normal') currentAuctionContinuationQuote(g, { kind: 'sale', free: sale.free });
|
||||
const event = ixianReplacementEvent(g.turn,buyer.id,g.nexusIxianReplacementCursor!.sequence,g.auction?.index ?? 0);
|
||||
if (!special && sale && sale.origin !== 'normal')
|
||||
currentAuctionContinuationQuote(g, { kind: 'sale', free: sale.free });
|
||||
const event = ixianReplacementEvent(g.turn,buyer.id,g.nexusIxianReplacementCursor!.sequence,
|
||||
special ? 0 : g.auction?.index ?? 0);
|
||||
g.decision = {kind:'nexusIxianReplacement',player:buyer.id,event};
|
||||
g.active = buyer.id;
|
||||
g.pendingNexusIxianReplacement = nexusRule(() => createIxianReplacementSource(
|
||||
ixianNexusReplacementContext(g),event,ixianNexusReplacementParent(g)));
|
||||
return true;
|
||||
}
|
||||
/** The native direct purchase has already paid, delivered and spent its power. */
|
||||
function offerRicheseSpecialReplacement(g: Game, buyer: Player, karama: Card, cardId: string): boolean {
|
||||
if (!g.nexusIxianReplacementPreview || !g.advanced || g.phase !== 3 ||
|
||||
buyer.ally || !g.nexusCards?.cards?.hands[buyer.id]) return false;
|
||||
requireRule(!g.pendingNexusIxianReplacement && !g.pendingNexusIxianSpecialPurchase &&
|
||||
buyer.faction === 'richese' && buyer.specialKaramaUsed === true,
|
||||
'Finish the original purchase before opening another purchased-card choice.');
|
||||
g.pendingNexusIxianSpecialPurchase = {
|
||||
buyer:buyer.id,cardId,karama:karama.id,amount:3,turn:g.turn,phase:3,
|
||||
resume:{active:g.active,response:g.response,decision:g.decision,pendingKarama:g.pendingKarama},
|
||||
};
|
||||
g.response = null;
|
||||
g.decision = null;
|
||||
g.pendingKarama = null;
|
||||
requireRule(offerIxianNexusReplacement(g), 'The committed special purchase lost its original Nexus opportunity.');
|
||||
return true;
|
||||
}
|
||||
function ixianNexusReplacementView(g: Game,id: string) {
|
||||
const source = g.pendingNexusIxianReplacement;
|
||||
if (!source) return null;
|
||||
const own = source.buyer === id;
|
||||
return {event:source.event,buyer:source.buyer,origin:g.currentAuctionSale!.origin,canPass:own,
|
||||
return {event:source.event,buyer:source.buyer,
|
||||
origin:g.pendingNexusIxianSpecialPurchase ? 'richeseSpecial' as const : g.currentAuctionSale!.origin,canPass:own,
|
||||
canUse:own && g.nexusCards?.cards?.hands[id] === 'ixians',
|
||||
blocked:null as string | null,purchased:own ? structuredClone(source.card) : null};
|
||||
}
|
||||
function decideIxianNexusReplacement(g: Game,id: string,action: Action): void {
|
||||
ixianNexusReplacementIntegrity(g);
|
||||
const source = g.pendingNexusIxianReplacement!;
|
||||
const special = g.pendingNexusIxianSpecialPurchase;
|
||||
requireRule(source.buyer === id && action.event === source.event &&
|
||||
['nexusIxianReplacementPass','nexusIxianReplacementUse'].includes(action.type) &&
|
||||
Object.keys(action).length === 2 && Object.keys(action).every(key => ['type','event'].includes(key)),
|
||||
@@ -10037,7 +10071,13 @@ function decideIxianNexusReplacement(g: Game,id: string,action: Action): void {
|
||||
g.decision = null;
|
||||
if (use) log(g,`${getPlayer(g,id).name} spent Ixian Nexus Secret Ally to discard the purchased Treachery Card and draw a private replacement.`,
|
||||
{faction:getPlayer(g,id).faction,name:'Nexus purchased-card replacement'});
|
||||
continueAuctionSale(g,source.free);
|
||||
if (special) {
|
||||
g.pendingNexusIxianSpecialPurchase = null;
|
||||
g.active = special.resume.active;
|
||||
g.response = special.resume.response;
|
||||
g.decision = special.resume.decision;
|
||||
g.pendingKarama = special.resume.pendingKarama;
|
||||
} else continueAuctionSale(g,source.free);
|
||||
}
|
||||
/** Opt-in fresh classic setup through the real physical Nexus/Homeworld setup. */
|
||||
export function initializeGuildBetrayalGameForAudit(state: Game): Game {
|
||||
@@ -13057,7 +13097,8 @@ function uncommittedSpice(g: Game, p: Player) {
|
||||
return (
|
||||
p.spice -
|
||||
richeseOwnCommitment(g.richeseAuction, g.richeseFunding ?? {}, p.id) -
|
||||
(g.auction?.bidder === p.id
|
||||
// A current sale already consumed the bid; only an unpaid bid stays reserved.
|
||||
(g.auction?.bidder === p.id && !g.currentAuctionSale
|
||||
? g.auction.bid - (g.auction.allyPayment ?? 0)
|
||||
: 0) -
|
||||
(battleSupportCost(g, p, g.battle?.plans[p.id]?.support ?? 0) -
|
||||
@@ -29204,6 +29245,7 @@ function resolveSpecialKaramaEffect(g: Game, p: Player, card: Card, intent: Spec
|
||||
passed: [],
|
||||
};
|
||||
}
|
||||
if (!intent.nexusEvent) offerRicheseSpecialReplacement(g, p, card, intent.acquire);
|
||||
} else if (intent.kind === 'choam') {
|
||||
const selected = intent.cards;
|
||||
const entries: FreshDiscardBatch['entries'][number][] | null = g.semutaPreview ? [] : null;
|
||||
|
||||
@@ -4,6 +4,15 @@ import type { RicheseAuction } from './richese-auction';
|
||||
|
||||
export class IxianReplacementError extends Error {}
|
||||
|
||||
export type RicheseSpecialPurchaseReceipt = {
|
||||
buyer: string;
|
||||
cardId: string;
|
||||
karama: string;
|
||||
amount: 3;
|
||||
turn: number;
|
||||
phase: 3;
|
||||
};
|
||||
|
||||
export type IxianReplacementContext = {
|
||||
status: string;
|
||||
phase: number;
|
||||
@@ -11,7 +20,7 @@ export type IxianReplacementContext = {
|
||||
sequence: number;
|
||||
expansions: readonly string[];
|
||||
richese: boolean;
|
||||
/** Native Black Market bidding only opens in Advanced play. */
|
||||
/** Native Black Market bidding and Richese special Karama require Advanced play. */
|
||||
advanced: boolean;
|
||||
auction: {
|
||||
cards: readonly Card[];
|
||||
@@ -27,6 +36,7 @@ export type IxianReplacementContext = {
|
||||
origin: 'normal' | 'cache' | 'blackMarket';
|
||||
seller: string | null;
|
||||
} | null;
|
||||
specialPurchase: RicheseSpecialPurchaseReceipt | null;
|
||||
players: readonly {
|
||||
id: string;
|
||||
hand: readonly Card[];
|
||||
@@ -147,6 +157,51 @@ function validatePhysical(physical: readonly Card[], ctx?: IxianReplacementConte
|
||||
);
|
||||
}
|
||||
|
||||
type PurchaseTerms = {
|
||||
card: Card;
|
||||
buyer: string;
|
||||
amount: number;
|
||||
free: boolean;
|
||||
auctionIndex: number;
|
||||
};
|
||||
|
||||
/** The committed direct receipt takes authority without consulting older auctions. */
|
||||
function specialPurchaseTerms(ctx: IxianReplacementContext): PurchaseTerms {
|
||||
const purchase = ctx.specialPurchase;
|
||||
requireReplacement(
|
||||
plain(purchase) && ctx.advanced && ctx.richese && ctx.expansions.includes('choam') &&
|
||||
ctx.phase === 3 && purchase.phase === 3 && purchase.turn === ctx.turn &&
|
||||
whole(purchase.turn) && purchase.turn > 0 && purchase.amount === 3 &&
|
||||
text(purchase.buyer) && text(purchase.cardId) && text(purchase.karama) &&
|
||||
purchase.cardId !== purchase.karama && Object.hasOwn(cache, purchase.cardId),
|
||||
'Replacement needs its committed Advanced Richese special Karama purchase.',
|
||||
);
|
||||
const buyer = Array.isArray(ctx.players) &&
|
||||
ctx.players.find(player => plain(player) && player.id === purchase.buyer);
|
||||
requireReplacement(buyer && Array.isArray(buyer.hand),
|
||||
'Replacement needs the special purchase in its original buyer hand.');
|
||||
const card = buyer.hand.find((held: Card) => held?.id === purchase.cardId);
|
||||
requireReplacement(selectedCard(card, ctx),
|
||||
'Replacement needs the exact canonical delivered Richese cache card.');
|
||||
const cost = Array.isArray(ctx.physicalCards) &&
|
||||
ctx.physicalCards.find(physical => physical?.id === purchase.karama);
|
||||
requireReplacement(
|
||||
selectedCard(cost, ctx) && cost.effect === 'karama' &&
|
||||
!buyer.hand.some((held: Card) => held?.id === purchase.karama),
|
||||
'The special purchase must retain its spent canonical printed Karama cost.',
|
||||
);
|
||||
return { card, buyer: purchase.buyer, amount: 3, free: false, auctionIndex: 0 };
|
||||
}
|
||||
|
||||
function purchaseTerms(ctx: IxianReplacementContext): PurchaseTerms {
|
||||
if (ctx.specialPurchase !== null) return specialPurchaseTerms(ctx);
|
||||
const card = purchasedCard(ctx), sale = ctx.sale!;
|
||||
return {
|
||||
card, buyer: sale.winner, amount: sale.amount, free: sale.free,
|
||||
auctionIndex: ctx.auction?.index ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
/** Select only a delivered card backed by the current native sale authority. */
|
||||
function purchasedCard(ctx: IxianReplacementContext): Card {
|
||||
const sale = ctx.sale;
|
||||
@@ -188,7 +243,7 @@ function purchasedCard(ctx: IxianReplacementContext): Card {
|
||||
return card;
|
||||
}
|
||||
|
||||
/** Authorize the original normal or native Richese purchase, not an arbitrary held card.
|
||||
/** Authorize the original normal, native lot or direct Richese purchase.
|
||||
* The engine supplies its current source-specific parent and live custody census.
|
||||
* Neither payment nor draw allocation is reconstructed here. */
|
||||
export function validateIxianReplacementSource(
|
||||
@@ -201,11 +256,11 @@ export function validateIxianReplacementSource(
|
||||
text(parent) && source.parent === parent,
|
||||
'The purchased card choice has lost its current parent or cursor.',
|
||||
);
|
||||
const card = purchasedCard(ctx), sale = ctx.sale!;
|
||||
const terms = purchaseTerms(ctx);
|
||||
requireReplacement(
|
||||
sale.winner === source.buyer && sale.amount === source.amount && sale.free === source.free &&
|
||||
source.auctionIndex === (ctx.auction?.index ?? 0) && sameCard(card, source.card),
|
||||
'Replacement must retain its exact original auction purchase.',
|
||||
terms.buyer === source.buyer && terms.amount === source.amount && terms.free === source.free &&
|
||||
source.auctionIndex === terms.auctionIndex && sameCard(terms.card, source.card),
|
||||
'Replacement must retain its exact original purchase.',
|
||||
);
|
||||
validatePhysical(ctx.physicalCards, ctx);
|
||||
requireReplacement(
|
||||
@@ -233,11 +288,11 @@ export function validateIxianReplacementSource(
|
||||
export function createIxianReplacementSource(
|
||||
ctx: IxianReplacementContext, event: string, parent: string,
|
||||
): IxianReplacementSource {
|
||||
const card = purchasedCard(ctx), sale = ctx.sale!;
|
||||
const terms = purchaseTerms(ctx);
|
||||
const source: IxianReplacementSource = {
|
||||
version: 1, event, buyer: sale.winner, turn: ctx.turn, sequence: ctx.sequence,
|
||||
auctionIndex: ctx.auction?.index ?? 0, amount: sale.amount, free: sale.free,
|
||||
card: { ...card }, parent, signature: '',
|
||||
version: 1, event, buyer: terms.buyer, turn: ctx.turn, sequence: ctx.sequence,
|
||||
auctionIndex: terms.auctionIndex, amount: terms.amount, free: terms.free,
|
||||
card: { ...terms.card }, parent, signature: '',
|
||||
};
|
||||
source.signature = sourceSignature(source);
|
||||
validateIxianReplacementSource(ctx, source, parent);
|
||||
|
||||
+6
-6
@@ -4516,7 +4516,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'The fresh local profile admits two-to-six unique seats in Basic/Advanced: classics/base33, Tleilaxu plus classics/Ix47, or Richese plus classics/CHOAM35 and original cache, with Ixians absent and Nexus alone. Normal purchases qualify, as do genuine positive Richese cache sales in Basic/Advanced and Black Market sales in Advanced. Sandtrout is omitted only for fresh native Ix setup. Other natives/modules and public starts or played-save conversion remain excluded.',
|
||||
'Nexus has no starting deal. A draw requires a qualifying closing Nexus with an alliance. A two-seat profile can continue its base game but cannot naturally create an unallied Secret Ally holder; actual replacement needs at least three seats, or four for two simultaneous unallied recipients.',
|
||||
'The photographed publisher face grants an independent Secret Ally operation when Ixians are absent. Remain unallied and hold the actual singleton Ixian Nexus. Native Ixian allied replacement is a different cancelable advantage and is not removed or used as a fallback. The adopted unofficial Advanced rulebook supplies relevant core rules, not this absent Nexus face or a new Nexus ruling.',
|
||||
'A normal purchase may be paid with spice or Karama, including successful Advanced BG conversion after its native counter. A denied direct conversion supplies no purchase; funded winning-payment recovery instead supplies a paid receipt. Actual Richese native sales are already paid and delivered before the same owned choice; original recipient/income and native lot continuation follow once. Unbid owner keeps/retains, gifts, bonus draws, Technology swaps and old hand cards supply no purchase. Direct Richese special-Karama sources remain separate.',
|
||||
'Normal paid/Karama purchases, genuine native Richese sold lots and actual Advanced special-Karama acquisition during Bidding supply their own purchase authority. A direct purchase has already spent its Karama, three spice and once-use; its target/price3 are not an unrelated row or sale. Use/Keep resumes original income, active player and suspended controls. Only unpaid bids remain reserved; already-delivered sale commitments are not held twice. Other phases keep native acquisition without a Bidding-only Nexus choice. Unbid keeps, gifts, bonus draws, Technology swaps and old hand cards supply no purchase.',
|
||||
'Provisional cache scope: the independent absent-Ixian Nexus grant applies to a genuine cache purchase under the selected literal reading. It is not the native Ixian alliance advantage: that advantage’s printed cache exclusion is unchanged. The table explains this policy before Use; it is product interpretation, not a newly located publisher FAQ or an explicit user-selected rule.',
|
||||
'Every publicly unallied supported buyer with public held-Nexus presence receives the same neutral Purchased card choice, irrespective of the secret face. An irrelevant-face buyer may Pass but cannot Use. This is an inferred development privacy convention, not publisher-prescribed timing or a general resolution of pending anytime and response-priority questions.',
|
||||
'Only the buyer may inspect its already-owned purchased card in this choice. You may inspect your own actually held Nexus through the existing authorized inspector; the prompt does not identify anybody else’s Nexus. Rivals receive no purchased or replacement face, private price, receipt, deck order or eligibility reason. Earlier Atreides auction inspection does not grant a peek of the replacement.',
|
||||
@@ -4530,11 +4530,11 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
example: 'You buy a card for four spice and inspect it privately. Pass keeps it and the Nexus. Use keeps the original four-spice purchase settled, spends Ixian Nexus, discards that exact card and draws the next deck card. If the draw deck is empty, its new discard joins the actual recycled pile and can be drawn again.',
|
||||
related: ['nexus-cards', 'bidding', 'ix-technology', 'card-karama', 'alliance-funding', 'privacy', 'implementation-checklist'],
|
||||
checklist: [
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Original normal and native Richese sold-lot receipts bind delivered card, price and native continuation. Cache works in both modes under a labelled provisional independent grant; Black Market stays Advanced-only. Unbid retention is not a purchase. Single native lots use index0 without a fabricated normal row; v1 receipt shape and normal parents remain unchanged. Direct special sources and other modules stay separate.', evidence: ['game/nexus-ixian-replacement.ts', 'game/engine.ts', 'tools/prototype-room.ts'] },
|
||||
{ area: 'Player controls', status: 'Partial', detail: 'Buyer-only purchased-card inspection and Use/Keep reuse the neutral protocol. Actual390px cache Use shows the provisional scope, retires to Revival and preserves one seller credit; Advanced Black Market Keep resumes original Richese first/last controls. Origin is public category only. Harkonnen keeps its separate visible provisional order. No overflow or browser/application error; broader acceptance remains open.', evidence: ['components/nexus-ixian-replacement.tsx', 'components/game-table.tsx'] },
|
||||
{ area: 'AI', status: 'Partial', detail: 'Four minimal legal policies use their own projected offer while original settlement waits. Twelve original-state actions cover cache Basic/Advanced and Advanced Black Market; earlier source-qualified paths remain in the contract. No full strategy or calibration is claimed.', evidence: ['game/bots.ts'] },
|
||||
{ area: 'Documentation', status: 'Partial', detail: 'Real sold-lot authority, v1 source preservation, original recipient/continuation, selected catalogue, same-card/full-hand semantics and explicit provisional cache/Harkonnen interpretations are documented. The native ally cache exclusion is unchanged; direct special sources, other modules and publisher priority remain separate.', evidence: ['docs/NEXUS_IXIAN_REPLACEMENT_RULES.md', 'docs/NEXUS_CARD_RULES.md', 'docs/RULE_DECISIONS.md'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Affected nine-file98/98, types/lint/build, six original runtime paths, twelve legal policy actions and two390px native-origin journeys pass. Eleven actual native scenario cases cover already-paid timing, Use/Keep and unbid retention; source tests reject missing/mismatched/competing/open/unbid lots and Basic Black Market. The fixture follows actual response priority. An obsolete DTO shape pin was deleted without losing semantic rival privacy assertions. No new broad, HTTP, saved/deployed assurance; all five facets remain Partial.', evidence: ['tests/nexus-ixian-replacement.test.ts', 'tests/nexus-ixian-replacement-engine.test.ts', 'tests/fixtures/nexus-ixian-native-depleted.json', 'tests/fixture-nexus-ixian-replacement.ts', 'tests/fixture-native-ixian-replacement.ts', 'tests/native-ixian-replacement.test.ts', 'tests/bg-ixian-replacement.test.ts', 'tests/fixture-bg-ixian-replacement.ts', 'tests/harkonnen-ixian-replacement.test.ts', 'tests/fixture-harkonnen-ixian-replacement.ts', 'tests/richese-ixian-replacement.test.ts', 'tests/fixture-richese-ixian-replacement.ts', 'tests/richese-ixian-special-replacement.test.ts', 'tests/fixture-richese-ixian-special-replacement.ts'] },
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Normal, native sold-lot and Bidding-phase direct Richese receipts bind actual delivered card, native price and continuation. A special receipt takes authority over unrelated rows/income, preserving active player, once-use and controls without a fake auction. Only unpaid bids stay reserved. Other phases keep native special acquisition without a Nexus offer. Receipt v1 shapes and ordinary parents remain unchanged; other modules stay separate.', evidence: ['game/nexus-ixian-replacement.ts', 'game/engine.ts', 'tools/prototype-room.ts'] },
|
||||
{ area: 'Player controls', status: 'Partial', detail: 'Actual390px native special purchase controls are enabled at three remaining spice during older paid income. Direct Use preserves the older card and both credits; reserved-bid Keep reaches a distinct genuine normal offer, then one row advance. Guidance states original cost/once-use committed and no new auction. Provisional cache/Harkonnen scope remains visible. No overflow/browser/application error; broader acceptance remains open.', evidence: ['components/nexus-ixian-replacement.tsx', 'components/richese-special-karama.tsx', 'components/game-table.tsx'] },
|
||||
{ area: 'AI', status: 'Partial', detail: 'Four minimal legal policies use their own projected offer while original settlement waits. Sixteen original-state actions cover quiet Emperor/bank, reserved bid and nested paid income direct purchases; previous source-qualified paths remain in the contract. No strategy or difficulty calibration is claimed.', evidence: ['game/bots.ts'] },
|
||||
{ area: 'Documentation', status: 'Partial', detail: 'Actual auction/direct authority, native price3 and cost commitment, once-use, active/control restoration, paid-versus-unpaid reservations, source-v1 preservation and provisional cache/Harkonnen interpretations are documented. Outside Bidding the native special purchase adds no Nexus offer. Other modules and universal/publisher priority remain separate.', evidence: ['docs/NEXUS_IXIAN_REPLACEMENT_RULES.md', 'docs/NEXUS_CARD_RULES.md', 'docs/RULE_DECISIONS.md'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Ten new direct scenarios and affected eleven-file107/107 pass, including existing funding consumers, with types/lint/build. Ten original runtime paths, sixteen legal policy actions and two390px actual native-action journeys prove original cost/target, Emperor/bank, quiet controls, reserved-bid automatic payment, distinct following normal offer, nested income and real Revival exclusion. A paid-bid double reservation was fixed; an obsolete serialized-identifier substring pin was deleted while semantic rival privacy checks remain. No broad, HTTP, saved/deployed assurance; all five facets remain Partial.', evidence: ['tests/nexus-ixian-replacement.test.ts', 'tests/nexus-ixian-replacement-engine.test.ts', 'tests/fixtures/nexus-ixian-native-depleted.json', 'tests/fixture-nexus-ixian-replacement.ts', 'tests/fixture-native-ixian-replacement.ts', 'tests/native-ixian-replacement.test.ts', 'tests/bg-ixian-replacement.test.ts', 'tests/fixture-bg-ixian-replacement.ts', 'tests/harkonnen-ixian-replacement.test.ts', 'tests/fixture-harkonnen-ixian-replacement.ts', 'tests/richese-ixian-replacement.test.ts', 'tests/fixture-richese-ixian-replacement.ts', 'tests/richese-ixian-special-replacement.test.ts', 'tests/fixture-richese-ixian-special-replacement.ts', 'tests/richese-ixian-direct-replacement.test.ts', 'tests/fixture-richese-ixian-direct-replacement.ts'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user