Preserve native BG conversion through Semuta physical costs and counters
This commit is contained in:
1 parent
8a2df97be8
commit
f587374923
13 files changed
+693
-63
No files matched your search
+138
-43
@@ -1394,13 +1394,21 @@ export type Game = {
|
||||
stateSignature: string;
|
||||
}
|
||||
| {
|
||||
kind: 'karamaCharityDiscard' | 'karamaInflationDiscard' | 'karamaBgCharityDiscard' | 'karamaBattleDiscard' | 'karamaChoamWorthlessDiscard' | 'karamaEmperorShareDiscard' | 'karamaIncomeDiscard' | 'karamaShipmentDiscard';
|
||||
kind: 'karamaCharityDiscard' | 'karamaInflationDiscard' | 'karamaBgCharityDiscard' | 'karamaBattleDiscard' | 'karamaChoamWorthlessDiscard' | 'karamaEmperorShareDiscard' | 'karamaIncomeDiscard' | 'karamaShipmentDiscard' | 'karamaBgConversionCancelDiscard';
|
||||
player: string;
|
||||
card: string;
|
||||
use: Extract<KaramaUse, { kind: 'cancel' }>;
|
||||
resume: NonNullable<Game['pendingNullentropy']>['resume'];
|
||||
stateSignature: string;
|
||||
}
|
||||
| {
|
||||
kind: 'bgKaramaCostDiscard';
|
||||
player: string;
|
||||
card: string;
|
||||
use: KaramaUse;
|
||||
resume: NonNullable<Game['pendingNullentropy']>['resume'];
|
||||
stateSignature: string;
|
||||
}
|
||||
| {
|
||||
kind: 'karamaShipmentPriceDiscard';
|
||||
player: string;
|
||||
@@ -2085,6 +2093,8 @@ export type Game = {
|
||||
pendingKarama?: {
|
||||
owner: string;
|
||||
use: KaramaUse;
|
||||
/** Physical recovery does not undo conversion, but ends a later price refund. */
|
||||
costClaimed?: true;
|
||||
/** New conversions bind their original power, shipment or lot. Absent in legacy saves. */
|
||||
opportunity?: {
|
||||
kind: 'cancel' | 'shipment' | 'auction';
|
||||
@@ -5469,9 +5479,35 @@ function semutaGuildShipmentKaramaResponse(g: Game, response: ResponseWindow): b
|
||||
else validateGuildRateResponse(g, response);
|
||||
return true;
|
||||
}
|
||||
function semutaBgConversionCancelResponse(
|
||||
g: Game, response: ResponseWindow, pending: Game['pendingKarama'],
|
||||
): boolean {
|
||||
return response.kind === 'worthlessKarama' && g.advanced && !!pending &&
|
||||
response.owner === pending.owner && getPlayer(g, pending.owner).faction === 'beneGesserit' &&
|
||||
(pending.opportunity === undefined ||
|
||||
pending.opportunity.signature === karamaOpportunitySignature(g, pending));
|
||||
}
|
||||
type BgKaramaCostDiscardContinuation = Extract<
|
||||
NonNullable<Game['pendingTreacheryDiscard']>['continuation'],
|
||||
{ kind: 'bgKaramaCostDiscard' }
|
||||
>;
|
||||
function bgKaramaCostDiscardSignature(g: Game, c: BgKaramaCostDiscardContinuation) {
|
||||
return JSON.stringify({
|
||||
player: c.player, card: c.card, use: c.use,
|
||||
parent: nullentropyParentSignature(g, c.resume, `bgKarama:${c.card}`),
|
||||
opportunity: karamaOpportunitySignature(g, { owner: c.player, use: c.use }),
|
||||
battle: g.battle, price: g.karamaShipping, homeworlds: g.homeworlds,
|
||||
rate: g.guildRateBlocked, permission: g.guildTransportBlocked,
|
||||
players: g.players.map(p => ({
|
||||
id: p.id, faction: p.faction, ally: p.ally, hand: p.hand.map(card => card.id),
|
||||
spice: p.spice, forces: p.forces, reserves: p.reserves, tanks: p.tanks,
|
||||
elites: p.elites, shipped: p.shipped, moved: p.moved,
|
||||
})),
|
||||
});
|
||||
}
|
||||
type KaramaResponseDiscardContinuation = Extract<
|
||||
NonNullable<Game['pendingTreacheryDiscard']>['continuation'],
|
||||
{ kind: 'karamaCharityDiscard' | 'karamaInflationDiscard' | 'karamaBgCharityDiscard' | 'karamaBattleDiscard' | 'karamaChoamWorthlessDiscard' | 'karamaEmperorShareDiscard' | 'karamaIncomeDiscard' | 'karamaShipmentDiscard' }
|
||||
{ kind: 'karamaCharityDiscard' | 'karamaInflationDiscard' | 'karamaBgCharityDiscard' | 'karamaBattleDiscard' | 'karamaChoamWorthlessDiscard' | 'karamaEmperorShareDiscard' | 'karamaIncomeDiscard' | 'karamaShipmentDiscard' | 'karamaBgConversionCancelDiscard' }
|
||||
>;
|
||||
function karamaResponseDiscardSignature(g: Game, c: KaramaResponseDiscardContinuation) {
|
||||
const battle = c.kind === 'karamaBattleDiscard';
|
||||
@@ -5479,6 +5515,7 @@ function karamaResponseDiscardSignature(g: Game, c: KaramaResponseDiscardContinu
|
||||
const emperorShare = c.kind === 'karamaEmperorShareDiscard';
|
||||
const income = c.kind === 'karamaIncomeDiscard';
|
||||
const shipment = c.kind === 'karamaShipmentDiscard';
|
||||
const conversion = c.kind === 'karamaBgConversionCancelDiscard';
|
||||
return JSON.stringify({
|
||||
player: c.player, card: c.card, use: c.use,
|
||||
parent: nullentropyParentSignature(g, c.resume,
|
||||
@@ -5522,12 +5559,17 @@ function karamaResponseDiscardSignature(g: Game, c: KaramaResponseDiscardContinu
|
||||
purchased: g.karamaShipping, cunning: g.nexusGuildCunningHistory?.at(-1),
|
||||
active: g.active, ready: g.ready, movementRemaining: g.movementRemaining,
|
||||
} : undefined,
|
||||
conversion: conversion ? {
|
||||
pending: c.resume.pendingKarama, battle: g.battle, price: g.karamaShipping,
|
||||
homeworlds: g.homeworlds, rate: g.guildRateBlocked, permission: g.guildTransportBlocked,
|
||||
auction: g.auction, richeseAuction: g.richeseAuction,
|
||||
} : undefined,
|
||||
order: g.order,
|
||||
players: g.players.map(p => ({
|
||||
id: p.id, faction: p.faction, ally: p.ally,
|
||||
hand: p.hand.map(card => card.id), spice: p.spice,
|
||||
shipped: income || shipment ? p.shipped : undefined,
|
||||
physical: battle || worthless || income || shipment
|
||||
shipped: income || shipment || conversion ? p.shipped : undefined,
|
||||
physical: battle || worthless || income || shipment || conversion
|
||||
? { forces: p.forces, reserves: p.reserves, tanks: p.tanks,
|
||||
elites: p.elites, leaders: battle ? p.leaders : undefined,
|
||||
noField: shipment ? p.noField : undefined,
|
||||
@@ -6389,6 +6431,19 @@ function treacheryDiscardIntegrity(g: Game) {
|
||||
c.stateSignature === residualPoisonDiscardSignature(g, c),
|
||||
'The used Residual Poison no longer matches its committed leader death.',
|
||||
);
|
||||
} else if (continuation?.kind === 'bgKaramaCostDiscard') {
|
||||
const c = continuation, entry = batch.entries[0], owner = getPlayer(g, c.player);
|
||||
requireRule(
|
||||
g.advanced && owner.faction === 'beneGesserit' &&
|
||||
!g.response && !g.decision && !g.pendingKarama && !g.phaseOpening &&
|
||||
batch.cause === 'karama:conversion' && batch.entries.length === 1 &&
|
||||
entry.publicFace && entry.discardedBy === c.player &&
|
||||
entry.card.id === c.card && entry.card.kind === 'worthless' &&
|
||||
(c.use.kind !== 'shipment' || c.use.card === c.card) &&
|
||||
c.stateSignature === bgKaramaCostDiscardSignature(g, c),
|
||||
'The spent Worthless no longer matches its original native Karama conversion.',
|
||||
);
|
||||
validateKaramaUse(g, owner, c.use);
|
||||
} else if (continuation?.kind === 'karamaCharityDiscard' ||
|
||||
continuation?.kind === 'karamaInflationDiscard' ||
|
||||
continuation?.kind === 'karamaBgCharityDiscard' ||
|
||||
@@ -6396,7 +6451,8 @@ function treacheryDiscardIntegrity(g: Game) {
|
||||
continuation?.kind === 'karamaChoamWorthlessDiscard' ||
|
||||
continuation?.kind === 'karamaEmperorShareDiscard' ||
|
||||
continuation?.kind === 'karamaIncomeDiscard' ||
|
||||
continuation?.kind === 'karamaShipmentDiscard') {
|
||||
continuation?.kind === 'karamaShipmentDiscard' ||
|
||||
continuation?.kind === 'karamaBgConversionCancelDiscard') {
|
||||
const c = continuation, entry = batch.entries[0];
|
||||
requireRule(
|
||||
!g.response &&
|
||||
@@ -6420,11 +6476,14 @@ function treacheryDiscardIntegrity(g: Game) {
|
||||
(c.kind === 'karamaIncomeDiscard' &&
|
||||
semutaIncomeKaramaResponse(g, c.use.response)) ||
|
||||
(c.kind === 'karamaShipmentDiscard' &&
|
||||
semutaGuildShipmentKaramaResponse(g, c.use.response))) &&
|
||||
semutaGuildShipmentKaramaResponse(g, c.use.response)) ||
|
||||
(c.kind === 'karamaBgConversionCancelDiscard' &&
|
||||
semutaBgConversionCancelResponse(g, c.use.response, c.resume.pendingKarama))) &&
|
||||
c.use.response.owner !== c.player &&
|
||||
c.resume.response?.kind === c.use.response.kind &&
|
||||
JSON.stringify(c.resume.response) === JSON.stringify(c.use.response) &&
|
||||
!c.resume.decision && !c.resume.pendingKarama && !c.resume.phaseOpening &&
|
||||
(c.kind === 'karamaBgConversionCancelDiscard' ||
|
||||
(!c.resume.decision && !c.resume.pendingKarama && !c.resume.phaseOpening)) &&
|
||||
batch.cause === `karama:${c.use.response.kind}` &&
|
||||
batch.entries.length === 1 &&
|
||||
entry.publicFace && entry.discardedBy === c.player &&
|
||||
@@ -7292,6 +7351,10 @@ function semutaOfferSupported(
|
||||
continuation: NonNullable<Game['pendingTreacheryDiscard']>['continuation'],
|
||||
entries: FreshDiscardBatch['entries'],
|
||||
) {
|
||||
const bgDiscard = continuation.kind === 'bgKaramaCostDiscard' ||
|
||||
continuation.kind === 'karamaBgConversionCancelDiscard';
|
||||
if (continuation.kind === 'karamaBgConversionCancelDiscard' &&
|
||||
!semutaBgConversionCancelResponse(g, continuation.use.response, continuation.resume.pendingKarama)) return false;
|
||||
if (continuation.kind !== 'ordinaryCardDiscard' &&
|
||||
continuation.kind !== 'nullentropyDiscard' &&
|
||||
continuation.kind !== 'distransDiscard' &&
|
||||
@@ -7313,6 +7376,7 @@ function semutaOfferSupported(
|
||||
continuation.kind !== 'karamaShipmentDiscard' &&
|
||||
continuation.kind !== 'karamaShipmentPriceDiscard' &&
|
||||
continuation.kind !== 'specialKaramaDiscard' &&
|
||||
!bgDiscard &&
|
||||
continuation.kind !== 'kaitainDiscard' &&
|
||||
continuation.kind !== 'tupileCleanupDiscard' &&
|
||||
continuation.kind !== 'karamaPurchaseDiscard' &&
|
||||
@@ -7342,7 +7406,7 @@ function semutaOfferSupported(
|
||||
(continuation.promise === null ||
|
||||
continuation.promise.kind === 'shipment' ||
|
||||
continuation.promise.kind === 'battle'))) return false;
|
||||
if (g.homeworlds && continuation.kind !== 'kaitainDiscard' &&
|
||||
if (g.homeworlds && !bgDiscard && continuation.kind !== 'kaitainDiscard' &&
|
||||
continuation.kind !== 'tupileCleanupDiscard' &&
|
||||
continuation.kind !== 'karamaIncomeDiscard' &&
|
||||
continuation.kind !== 'karamaShipmentDiscard' &&
|
||||
@@ -7451,6 +7515,7 @@ function semutaOfferSupported(
|
||||
(!g.response || JSON.stringify(g.response) === JSON.stringify(continuation.resume.response))
|
||||
: true) &&
|
||||
(continuation.kind === 'battleCleanup' ||
|
||||
bgDiscard ||
|
||||
continuation.kind === 'specialKaramaDiscard' ||
|
||||
continuation.kind === 'winnerMandatoryDiscard' ||
|
||||
continuation.kind === 'battleResolved' ||
|
||||
@@ -7480,8 +7545,8 @@ function semutaOfferSupported(
|
||||
? continuation.resume.phaseOpening?.passed.length === 0
|
||||
: !continuation.resume.phaseOpening)))) &&
|
||||
(continuation.kind === 'karamaShipmentDiscard' ||
|
||||
continuation.kind === 'specialKaramaDiscard' || !g.karamaShipping) &&
|
||||
(continuation.kind === 'specialKaramaDiscard' || (continuation.kind === 'ixAllyCard'
|
||||
continuation.kind === 'specialKaramaDiscard' || bgDiscard || !g.karamaShipping) &&
|
||||
(bgDiscard || continuation.kind === 'specialKaramaDiscard' || (continuation.kind === 'ixAllyCard'
|
||||
? !!g.currentAuctionSale && continuation.sale.origin === g.currentAuctionSale.origin &&
|
||||
(continuation.sale.origin === 'normal'
|
||||
? !!g.auction && !g.richeseAuction
|
||||
@@ -7495,11 +7560,12 @@ function semutaOfferSupported(
|
||||
? !!g.currentAuctionSale && (g.currentAuctionSale.origin === 'normal'
|
||||
? !!g.auction && !g.richeseAuction : !!g.richeseAuction && !g.auction)
|
||||
: !g.auction)) &&
|
||||
(continuation.kind === 'specialKaramaDiscard' || continuation.kind === 'saphoAuctionDiscard' ||
|
||||
(bgDiscard || continuation.kind === 'specialKaramaDiscard' || continuation.kind === 'saphoAuctionDiscard' ||
|
||||
continuation.kind === 'ixAllyCard' ||
|
||||
(continuation.kind === 'karamaIncomeDiscard' &&
|
||||
continuation.use.response.kind === 'emperorIncome') || !g.richeseAuction) &&
|
||||
(continuation.kind === 'saphoAggressorDiscard' ||
|
||||
bgDiscard ||
|
||||
continuation.kind === 'specialKaramaDiscard' ||
|
||||
continuation.kind === 'residualPoisonDiscard' ||
|
||||
continuation.kind === 'truthtranceDiscard' ||
|
||||
@@ -7525,6 +7591,7 @@ function semutaOfferSupported(
|
||||
!g.pendingAmbassador && !g.pendingTerrorEntry &&
|
||||
(continuation.kind === 'karamaShipmentDiscard' ||
|
||||
continuation.kind === 'specialKaramaDiscard' ||
|
||||
bgDiscard ||
|
||||
(!g.pendingShipment && !g.pendingHomeworldShipment)) &&
|
||||
!g.pendingRicheseGift &&
|
||||
((continuation.kind === 'karamaIncomeDiscard' &&
|
||||
@@ -7752,6 +7819,13 @@ function finishTreacheryDiscard(g: Game, claim?: { player: string; card: string
|
||||
const battle = g.battle!;
|
||||
if (battle.preLeader && !battle.preLeader.closed)
|
||||
battle.preLeader.ready = battle.preLeader.ready.filter(id => id !== next.player);
|
||||
} else if (next.kind === 'bgKaramaCostDiscard') {
|
||||
g.response = next.resume.response;
|
||||
g.decision = next.resume.decision;
|
||||
g.pendingKarama = next.resume.pendingKarama;
|
||||
g.phaseOpening = next.resume.phaseOpening;
|
||||
beginWorthlessKaramaConversion(g, getPlayer(g, next.player),
|
||||
pending.batch.entries[0].card, next.use, !g.discard.some(card => card.id === next.card));
|
||||
} else if (next.kind === 'specialKaramaDiscard') {
|
||||
g.response = next.resume.response;
|
||||
g.decision = next.resume.decision;
|
||||
@@ -7775,6 +7849,7 @@ function finishTreacheryDiscard(g: Game, claim?: { player: string; card: string
|
||||
next.kind === 'karamaEmperorShareDiscard' ||
|
||||
next.kind === 'karamaIncomeDiscard' ||
|
||||
next.kind === 'karamaShipmentDiscard' ||
|
||||
next.kind === 'karamaBgConversionCancelDiscard' ||
|
||||
next.kind === 'karamaPurchaseDiscard' ||
|
||||
next.kind === 'karamaPaymentDiscard') {
|
||||
g.response = next.resume.response;
|
||||
@@ -14417,6 +14492,8 @@ function spendKarama(g: Game, p: Player, card: Card, use: KaramaUse, kullOffered
|
||||
const used = discard(g, p, card.id);
|
||||
const guildShipmentDiscard = g.semutaPreview && card.effect === 'karama' &&
|
||||
use.kind === 'cancel' && semutaGuildShipmentKaramaResponse(g, use.response);
|
||||
const bgConversionCancelDiscard = g.semutaPreview && card.effect === 'karama' &&
|
||||
use.kind === 'cancel' && semutaBgConversionCancelResponse(g, use.response, g.pendingKarama);
|
||||
if (g.semutaPreview && card.effect === 'karama' &&
|
||||
use.kind === 'cancel' &&
|
||||
(use.response.kind === 'choamCharity' ||
|
||||
@@ -14424,9 +14501,10 @@ function spendKarama(g: Game, p: Player, card: Card, use: KaramaUse, kullOffered
|
||||
use.response.kind === 'bgCharity' || semutaBattleKaramaResponse(g, use.response) ||
|
||||
semutaChoamKaramaResponse(g, use.response) ||
|
||||
semutaEmperorShareKaramaResponse(g, use.response) ||
|
||||
semutaIncomeKaramaResponse(g, use.response) || guildShipmentDiscard)) {
|
||||
semutaIncomeKaramaResponse(g, use.response) || guildShipmentDiscard || bgConversionCancelDiscard)) {
|
||||
const continuation: KaramaResponseDiscardContinuation = {
|
||||
kind: guildShipmentDiscard ? 'karamaShipmentDiscard'
|
||||
kind: bgConversionCancelDiscard ? 'karamaBgConversionCancelDiscard'
|
||||
: guildShipmentDiscard ? 'karamaShipmentDiscard'
|
||||
: use.response.kind === 'choamCharity' ? 'karamaCharityDiscard'
|
||||
: use.response.kind === 'choamInflation'
|
||||
? 'karamaInflationDiscard'
|
||||
@@ -14445,6 +14523,9 @@ function spendKarama(g: Game, p: Player, card: Card, use: KaramaUse, kullOffered
|
||||
const entries = [{ card: used, discardedBy: p.id, publicFace: true }];
|
||||
if (semutaOfferSupported(g, continuation, entries)) {
|
||||
g.response = null;
|
||||
if (bgConversionCancelDiscard) {
|
||||
g.decision = null; g.pendingKarama = null; g.phaseOpening = null;
|
||||
}
|
||||
continuation.stateSignature = karamaResponseDiscardSignature(g, continuation);
|
||||
stageTreacheryDiscard(g, `karama:${use.response.kind}`, entries, continuation);
|
||||
return;
|
||||
@@ -14481,36 +14562,47 @@ function spendKarama(g: Game, p: Player, card: Card, use: KaramaUse, kullOffered
|
||||
}
|
||||
}
|
||||
if (card.kind === 'worthless') {
|
||||
requireRule(!g.pendingKarama, 'A Worthless conversion is already pending.');
|
||||
g.pendingKarama = { owner: p.id, use };
|
||||
g.pendingKarama.opportunity = {
|
||||
kind:
|
||||
use.kind === 'cancel'
|
||||
? 'cancel'
|
||||
: use.kind === 'shipment'
|
||||
? 'shipment'
|
||||
: 'auction',
|
||||
signature: karamaOpportunitySignature(g, g.pendingKarama),
|
||||
};
|
||||
g.response = {
|
||||
kind: 'worthlessKarama',
|
||||
owner: p.id,
|
||||
passed: [],
|
||||
intent:
|
||||
use.kind === 'cancel'
|
||||
? `Cancel a power used by ${getPlayer(g, use.response.owner).name}.`
|
||||
: use.kind === 'shipment'
|
||||
? `Give ${getPlayer(g, use.recipient).name} Guild shipment rates paid to the bank.`
|
||||
: use.kind === 'purchase'
|
||||
? 'Take the current auction card without paying spice.'
|
||||
: 'Pay the winning auction bid without spending spice.',
|
||||
};
|
||||
log(
|
||||
g,
|
||||
`${p.name} used ${card.name} as Karama; the faction power awaits responses.`,
|
||||
);
|
||||
if (g.semutaPreview) {
|
||||
const continuation: BgKaramaCostDiscardContinuation = {
|
||||
kind: 'bgKaramaCostDiscard', player: p.id, card: used.id, use,
|
||||
resume: { response: g.response, decision: g.decision,
|
||||
pendingKarama: g.pendingKarama, phaseOpening: g.phaseOpening },
|
||||
stateSignature: '',
|
||||
};
|
||||
const entries = [{ card: used, discardedBy: p.id, publicFace: true }];
|
||||
if (semutaOfferSupported(g, continuation, entries)) {
|
||||
g.response = null; g.decision = null; g.pendingKarama = null; g.phaseOpening = null;
|
||||
continuation.stateSignature = bgKaramaCostDiscardSignature(g, continuation);
|
||||
stageTreacheryDiscard(g, 'karama:conversion', entries, continuation);
|
||||
return;
|
||||
}
|
||||
}
|
||||
beginWorthlessKaramaConversion(g, p, card, use);
|
||||
} else completeKarama(g, p, use, card);
|
||||
}
|
||||
function beginWorthlessKaramaConversion(
|
||||
g: Game, p: Player, card: Card, use: KaramaUse, costClaimed = false,
|
||||
) {
|
||||
requireRule(!g.pendingKarama, 'A Worthless conversion is already pending.');
|
||||
const pending: NonNullable<Game['pendingKarama']> = { owner: p.id, use };
|
||||
if (costClaimed) pending.costClaimed = true;
|
||||
g.pendingKarama = pending;
|
||||
pending.opportunity = {
|
||||
kind: use.kind === 'cancel' ? 'cancel' : use.kind === 'shipment' ? 'shipment' : 'auction',
|
||||
signature: karamaOpportunitySignature(g, pending),
|
||||
};
|
||||
g.response = {
|
||||
kind: 'worthlessKarama', owner: p.id, passed: [],
|
||||
intent: use.kind === 'cancel'
|
||||
? `Cancel a power used by ${getPlayer(g, use.response.owner).name}.`
|
||||
: use.kind === 'shipment'
|
||||
? `Give ${getPlayer(g, use.recipient).name} Guild shipment rates paid to the bank.`
|
||||
: use.kind === 'purchase'
|
||||
? 'Take the current auction card without paying spice.'
|
||||
: 'Pay the winning auction bid without spending spice.',
|
||||
};
|
||||
log(g, `${p.name} used ${card.name} as Karama; the faction power awaits responses.`);
|
||||
}
|
||||
function completeKarama(g: Game, p: Player, use: KaramaUse, card?: Card) {
|
||||
validateKaramaUse(g, p, use);
|
||||
const played = card?.effect === 'truthtrance' ? 'Truthtrance as Karama' : 'Karama';
|
||||
@@ -25777,8 +25869,11 @@ function finishResponse(g: Game, canceled: boolean, settlement?: BureaucratSettl
|
||||
const pending = g.pendingKarama!;
|
||||
g.pendingKarama = null;
|
||||
const owner = getPlayer(g, pending.owner);
|
||||
if (!canceled) completeKarama(g, owner, pending.use);
|
||||
else if (pending.use.kind === 'cancel') g.response = pending.use.response;
|
||||
if (!canceled) {
|
||||
completeKarama(g, owner, pending.use);
|
||||
if (pending.costClaimed && pending.use.kind === 'shipment')
|
||||
g.karamaShipping!.costClaimed = true;
|
||||
} else if (pending.use.kind === 'cancel') g.response = pending.use.response;
|
||||
else if (pending.use.kind === 'auctionPayment')
|
||||
recoverAuctionPayment(g, owner);
|
||||
} else if (response.kind === 'choamSale') {
|
||||
|
||||
+9
-7
@@ -1203,7 +1203,8 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'A printed Karama canceling original paid Guild shipment income, non-Ambassador Emperor auction income or Richese special-purchase income also offers its exact public cost before the native denial. The price and physical transaction have already committed: claim or Continue cannot refund spice, re-ship forces, undo cache acquisition or replay a purchase. Original Movement, next-lot inspection or the Richese purchase’s suspended controls resume once. A winner may spend the very Karama it just bought; claiming that identity does not reverse its payment. Homeworld and Ambassador payment compositions remain separate.',
|
||||
'A printed Karama canceling native Guild transport permission or shipment rate now offers that exact public cost while the original reserve, cross/return or typed Homeworld declaration remains unpaid. Claim or Continue restores and applies that cancellation once. Recovering the cost never restores a denied route, retains the native discount, finances an unfunded group or repeats shipment. Ordinary movement and legal replacements remain; independent price grants stay distinct.',
|
||||
'A printed Karama buying another active player’s shipment price also offers its exact fresh cost before that same original grant activates. Claim or Continue preserves the original beneficiary, creator and half-normal bank-paid price. Physical recovery ends the provisional stop-refund eligibility for that cost, even if the claimant later discards it again. Guild can spend that very recovered card for an actual special stop without also refunding it to the former owner. Unclaimed original costs retain their older refund; this is selected source composition, not a publisher worked example.',
|
||||
'Printed native special-Karama costs now offer Semuta at their original disposal point. Recovery or Continue resumes the same committed native effect once even when its physical cost changed hands. Emperor revival already happened before its native disposal and is not repeated. CHOAM has a separate later private batch for its selected cashout cards after one earned credit: commit before inspection and recover one fresh card, never the old activation or one per discarded card. Original once-use, typed sources and refund remain; Advanced E3 entry and converted costs keep their separate gates.',
|
||||
'Printed native special-Karama costs now offer Semuta at their original disposal point. Recovery or Continue resumes the same committed native effect once even when its physical cost changed hands. Emperor revival already happened before its native disposal and is not repeated. CHOAM has a separate later private batch for its selected cashout cards after one earned credit: commit before inspection and recover one fresh card, never the old activation or one per discarded card. Original once-use, typed sources and refund remain; Advanced E3 entry and Shrine-source costs keep separate gates.',
|
||||
'A native Advanced BG Worthless-as-Karama cost also offers its actual physical card before the original conversion response. Claim or Continue cannot turn that card into a printed Karama, grant BG’s role to its new faction or bypass its native counter. A later printed counter cost is a different fresh event; taking it still denies the conversion. Original shipment/power/auction/winning-payment bindings and passed history remain. Allowed converted prices retain claimed-cost provenance even after later real CHOAM disposal, without a duplicate stop refund.',
|
||||
'The separate fresh semuta --homeworlds entry retains original Richese/CHOAM and classic Homeworld occupation setup with exact CHOAM and no other modules. Paid Box search, clean Distrans transfer, definite Truthtrance answers, used ordinary specials, paid Kaitain disposal, Tupile cleanup and printed Karama canceling native Guild/Emperor/Richese-purchase income offer Semuta. Native printed Kulon, Baliset, La La La, Trip to Gamont and Jubba costs also pause before their original effect: claim/pass adds one movement bonus, denies free allowance without charging/moving forces, stops the uncommitted Baliset move, returns one original board counter before the same CHOAM close, or applies protection before the same recorded storm moves. Printed Karama canceling these powers pauses separately before denial; the retained Worthless fuel is not a candidate. A canceled reactive Baliset resumes its original declared move once; canceled Kulon/La La La/Gamont/Jubba do not apply their effects. A recovered card grants no other faction CHOAM power. Kaitain private groups require commitment before selection; each Tupile holder has a separate cost. Native income denial retains payment and the original occupied-share allocation. Basic unresolved epochs, Kull, other producers and public starts remain guarded.',
|
||||
'Legal completed CHOAM market sales also offer their used card in the Homeworld profile. Worthless cashout keeps its original two-spice credit; a non-Worthless duplicate keeps its three-spice credit and the original same-name witness stays held. The high-Tupile Worthless-sale prohibition still applies, including duplicate Worthless sales. Claim/pass cannot repeat payout, undo the actual reserve shipment or replace the original market close; no new bank-income interpretation is made.',
|
||||
'Final Ornithopter disposal also offers its used card in the Homeworld profile after the one-group range-three or original two-group movement completes, including an early end with no useful original group. Claim/pass cannot move those counters again or replay the paid native shipment; prior-move/Hajr, advisor and Homeworld-transfer boundaries remain. Clean Sapho Once Around and unstarted Movement first/last costs likewise offer after reordering the finite unacted queue. Earlier bids/funding and physical storm order stay committed, and the same next bidder or combined turn resumes once. Cyclic auctions, partly completed turns, first-versus-Guild and late battle timing are not opened.',
|
||||
@@ -1228,31 +1229,31 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
area: 'Implementation' as const,
|
||||
status: 'Partial' as const,
|
||||
detail:
|
||||
'The Richese preview interrupts clean ordinary-card, paid Box, Ornithopter, Distrans, Truthtrance, four Sapho scopes, pre-leader Residual Poison, printed Karama canceling clean CHOAM Charity, Inflation or Bene Gesserit Charity or settling a normal auction purchase or winning bid, a completed CHOAM sale, clean Kulon, proactive or reactive La La La/Baliset and ordinary Trip to Gamont Worthless use, accepted printed Jubba before storm protection, Ix-deck Thumper/Amal and three battle discard stages, including before native Advanced Harkonnen capture. A private normal-auction or Black Market Ixian ally replacement pauses before its draw with a neutral, face-hidden offer; original payment and seller income settle once. Cache purchases retain their native allied-replacement prohibition. Ordinary definite Truthtrance answers pause whether or not another question remains reserved, and also when the answer bound a supported shipment or unsubmitted battle-plan promise. CHOAM sale income is committed before its neutral offer and the same market resumes once; Kulon adds one movement range, La La La blocks one target’s free revival, Baliset restricts the chosen player’s movement, ordinary Trip returns one selected force, and Jubba protects one CHOAM territory only after their offers. Karama’s physical cost is discarded before its neutral offer; the original response is canceled or the reserved lot settles once. Residual Poison commits one random leader death; Sapho commits its finite priority. Thumper waits before worm/spice draw; Amal restores an already-halved opening. Owner-only candidates and one physical Semuta transfer retain privacy. The user-approved full-hand exchange preserves final hand count and incoming reservations. Other Karama uses, other reactive Worthless uses, other private discards, nested responses and other pending rewards remain unfinished. Printed native Guild permission/rate costs now use a dedicated unpaid shipment continuation for reserve, cross/return and typed Homeworld sources; claim/pass applies the same denial/repricing once without delivery replay or invented funding. Printed shipment-price activation now has its own continuation: claim/decline creates the same original price once. Recovered physical cost retains the beneficiary/creator but loses provisional refund eligibility even after rediscard; the same recovered card can pay a real stop without being returned to its former owner. Printed native special costs now resume one committed native effect through effect/complete/cashout stages. Original Emperor post-revival disposal is preserved; CHOAM selected cards are a later private simultaneous batch after one credit. Existing native once-use, joint ordering and typed parent remain.',
|
||||
'The Richese preview interrupts clean ordinary-card, paid Box, Ornithopter, Distrans, Truthtrance, four Sapho scopes, pre-leader Residual Poison, printed Karama canceling clean CHOAM Charity, Inflation or Bene Gesserit Charity or settling a normal auction purchase or winning bid, a completed CHOAM sale, clean Kulon, proactive or reactive La La La/Baliset and ordinary Trip to Gamont Worthless use, accepted printed Jubba before storm protection, Ix-deck Thumper/Amal and three battle discard stages, including before native Advanced Harkonnen capture. A private normal-auction or Black Market Ixian ally replacement pauses before its draw with a neutral, face-hidden offer; original payment and seller income settle once. Cache purchases retain their native allied-replacement prohibition. Ordinary definite Truthtrance answers pause whether or not another question remains reserved, and also when the answer bound a supported shipment or unsubmitted battle-plan promise. CHOAM sale income is committed before its neutral offer and the same market resumes once; Kulon adds one movement range, La La La blocks one target’s free revival, Baliset restricts the chosen player’s movement, ordinary Trip returns one selected force, and Jubba protects one CHOAM territory only after their offers. Karama’s physical cost is discarded before its neutral offer; the original response is canceled or the reserved lot settles once. Residual Poison commits one random leader death; Sapho commits its finite priority. Thumper waits before worm/spice draw; Amal restores an already-halved opening. Owner-only candidates and one physical Semuta transfer retain privacy. The user-approved full-hand exchange preserves final hand count and incoming reservations. Other Karama uses, other reactive Worthless uses, other private discards, nested responses and other pending rewards remain unfinished. Printed native Guild permission/rate costs now use a dedicated unpaid shipment continuation for reserve, cross/return and typed Homeworld sources; claim/pass applies the same denial/repricing once without delivery replay or invented funding. Printed shipment-price activation now has its own continuation: claim/decline creates the same original price once. Recovered physical cost retains the beneficiary/creator but loses provisional refund eligibility even after rediscard; the same recovered card can pay a real stop without being returned to its former owner. Printed native special costs now resume one committed native effect through effect/complete/cashout stages. Original Emperor post-revival disposal is preserved; CHOAM selected cards are a later private simultaneous batch after one credit. Existing native once-use, joint ordering and typed parent remain. Native BG physical Worthless cost now precedes the single original conversion emitter; the separately printed cancellation cost retains that pending use and still denies it. All four use bindings, incoming reservations and older passes remain. Allowed converted prices carry claimed-cost provenance through actual later rediscard/refund.',
|
||||
},
|
||||
{
|
||||
area: 'Player controls' as const,
|
||||
status: 'Partial' as const,
|
||||
detail:
|
||||
'All seats can Continue at the neutral fresh-discard boundary; only the actual holder sees Commit. In a mixed-owner battle batch, a committed holder can inspect and select only cards discarded by another player; nonholders receive no candidates. Normal Richese starts remain gated. Earlier four original-action390px native cost/continuation journeys exercise full4 reserve claim, denied cross plus ordinary1, typed full2 all-seat decline, and poor2/full4 release then one-force full2. No overflow/browser/application error; one tool-only stale-node race was resolved with fresh evidence, not replay. Earlier four original-action390px price journeys start at actual activation: bank3 allowance while Guild holds the cost, same-card reserve stop, typed bank2 after all-seat decline, and same-card typed stop followed by real ordinary movement. No overflow/browser/application errors; two tool-only stale-node races were resolved without replay. Four current390px journeys cover original reserve/typed Guild prevention claim/decline and controlled CHOAM private Kulon selection plus Emperor post-revival cost claim. Two original/two controlled; actual native movement/public force inspectors, no overflow/browser/application errors.',
|
||||
'All seats can Continue at the neutral fresh-discard boundary; only the actual holder sees Commit. In a mixed-owner battle batch, a committed holder can inspect and select only cards discarded by another player; nonholders receive no candidates. Normal Richese starts remain gated. Earlier four original-action390px native cost/continuation journeys exercise full4 reserve claim, denied cross plus ordinary1, typed full2 all-seat decline, and poor2/full4 release then one-force full2. No overflow/browser/application error; one tool-only stale-node race was resolved with fresh evidence, not replay. Earlier four original-action390px price journeys start at actual activation: bank3 allowance while Guild holds the cost, same-card reserve stop, typed bank2 after all-seat decline, and same-card typed stop followed by real ordinary movement. No overflow/browser/application errors; two tool-only stale-node races were resolved without replay. Earlier four390px special-cost journeys cover original reserve/typed Guild prevention claim/decline and controlled CHOAM private Kulon selection plus Emperor post-revival cost claim. Two original/two controlled; actual native movement/public force inspectors, no overflow/browser/application errors. Four new original-action390px journeys start from actual Baliset controls: bank3 after native allowance, printed counter recovery with denial intact, original full4 Guild repricing and unchanged free Chaumurky acquisition. Native advisors/next-lot inspection finish, with no overflow/browser/application errors.',
|
||||
},
|
||||
{
|
||||
area: 'AI' as const,
|
||||
status: 'Partial' as const,
|
||||
detail:
|
||||
'All four AI profiles use their own projected hand to commit or pass the exact current event, and can choose only privately projected committed candidates. This is minimal legal participation, not tuned strategy. For both new native permission and rate cost parents, all four actual original-state profiles recover the exact cost through legal actions and preserve the original denial/repricing. No tuning or calibration. All four original-state profiles claim the price cost; a subsequent real owner action executes the retained bank-paid shipment. This is legal source participation, not a new strategy preference or calibration. All four existing profiles recover the original Guild stop cost via legal private actions, and the native prevention then executes once. No new preference, tuning or calibration.',
|
||||
'All four AI profiles use their own projected hand to commit or pass the exact current event, and can choose only privately projected committed candidates. This is minimal legal participation, not tuned strategy. For both new native permission and rate cost parents, all four actual original-state profiles recover the exact cost through legal actions and preserve the original denial/repricing. No tuning or calibration. All four original-state profiles claim the price cost; a subsequent real owner action executes the retained bank-paid shipment. This is legal source participation, not a new strategy preference or calibration. All four existing profiles recover the original Guild stop cost via legal private actions, and the native prevention then executes once. No new preference, tuning or calibration. For each native BG cost and printed counter-cost family, all four existing profiles recover only the current fresh card through their private legal action. Native response allowance/denial remains separate; subsequent owner actions exercise the real retained source. No tuning or calibration.',
|
||||
},
|
||||
{
|
||||
area: 'Documentation' as const,
|
||||
status: 'Partial' as const,
|
||||
detail:
|
||||
'The guide distinguishes clean public ordinary-card, paid Box, Ornithopter, Distrans, definite ordinary Truthtrance answers (including queued questions, supported shipment promises and battle answers before or after plan submission/reveal), four Sapho scopes, pre-leader Residual Poison, printed Karama cancellation of CHOAM Charity, Inflation or Bene Gesserit Charity or clean normal-auction purchase or winning-bid payment, completed CHOAM card sales, clean Kulon/proactive La La La/Baliset/ordinary Trip to Gamont Worthless and accepted printed Jubba Cloak, Ix-deck Thumper/Amal and three battle batches from a private normal-auction or Black Market Ixian ally replacement, the cache replacement prohibition, privacy-neutral acknowledgements, the user-approved atomic full-hand exchange and remaining discard sources. The native Guild followup distinguishes unpaid transport from completed income and keeps existing denial/no-debt interpretations. Both original cards are actually auctioned; two extra active-price probes explicitly disclose controlled second-card acquisition. Printed price-cost activation is connected below; converted and special costs remain separate. The printed price-cost continuation and claimed-cost nonduplication extension are explicit. Unclaimed original refunds remain provisional and unchanged; recovery does not transfer or repeat the price benefit. Converted costs and wider E3 entries remain separate; printed special costs are connected below. Printed special costs and CHOAM cashout stages follow native disposal order without a new publisher timing ruling. Eight original and18 controlled paths plus2 original/2 controlled surfaces are explicit. Advanced E3 entry, converted costs and full assurance remain separate.',
|
||||
'The guide distinguishes clean public ordinary-card, paid Box, Ornithopter, Distrans, definite ordinary Truthtrance answers (including queued questions, supported shipment promises and battle answers before or after plan submission/reveal), four Sapho scopes, pre-leader Residual Poison, printed Karama cancellation of CHOAM Charity, Inflation or Bene Gesserit Charity or clean normal-auction purchase or winning-bid payment, completed CHOAM card sales, clean Kulon/proactive La La La/Baliset/ordinary Trip to Gamont Worthless and accepted printed Jubba Cloak, Ix-deck Thumper/Amal and three battle batches from a private normal-auction or Black Market Ixian ally replacement, the cache replacement prohibition, privacy-neutral acknowledgements, the user-approved atomic full-hand exchange and remaining discard sources. The native Guild followup distinguishes unpaid transport from completed income and keeps existing denial/no-debt interpretations. Both original cards are actually auctioned; two extra active-price probes explicitly disclose controlled second-card acquisition. Printed price-cost activation is connected below; converted and special costs remain separate. The printed price-cost continuation and claimed-cost nonduplication extension are explicit. Unclaimed original refunds remain provisional and unchanged; recovery does not transfer or repeat the price benefit. Shrine costs and wider E3 entries remain separate; native BG and printed special costs are connected. Printed special costs and CHOAM cashout stages follow native disposal order without a new publisher timing ruling. Eight original and18 controlled paths plus2 original/2 controlled surfaces are explicit. Advanced E3 entry, Shrine costs and full assurance remain separate. The Worthless remains its actual printed role and does not grant BG conversion to a new faction. Native cost versus printed counter, four-use source/incoming bindings and claimed converted-price refund are explicit. Kull pre-discard priority is unchanged; stale native Kull fixture queues were corrected through real decisions. Shrine/E3 and final assurance stay separate.',
|
||||
},
|
||||
{
|
||||
area: 'Verification' as const,
|
||||
status: 'Partial' as const,
|
||||
detail:
|
||||
'Genuine setup, custody, hidden-hand parity, saved JSON, authenticated SQLite continuation and legal AI participation cover clean ordinary-card, paid Box, Ornithopter, Distrans, Truthtrance, four Sapho scopes, pre-leader Residual Poison, printed Karama cancellation of CHOAM Charity, Inflation or Bene Gesserit Charity or clean normal-auction purchase or winning-bid payment, completed CHOAM card sales, clean Kulon/proactive La La La/Baliset/ordinary Trip to Gamont Worthless and accepted printed Jubba Cloak, Ix-deck Thumper/Amal, a private normal-auction Ixian ally replacement and three battle discard stages. Other private producers, Karama uses, other reactive Worthless uses, pending rewards, full games and live deployment remain unverified. Earlier thirteen-file158/158 passes, including12 new cases. Thirty-two original-action runtime/policy paths and four original-action390px journeys pass; two separately controlled cross/typed active-price probes retain the original independent grant. No worker/D1/real-room/deployed assurance or complete-mode claim. Earlier eighteen-file234/234 passes, including8 new cases. Fourteen original-action runtime/policy paths and four actual original-action390px journeys pass; two explicitly controlled extra-stop-card acquisitions prove unclaimed refunds. The original same-card stop uses no extra card or staged acquisition. No worker/D1/saved/deployed or complete-mode assurance. Current sixteen-file224/224 passes with13 new behavior cases. Twenty-six runtime/policy paths pass:8 original-action Guild paths including4 profiles,18 explicitly controlled native-effect paths. Four actual390px journeys pass (2 original/2 controlled). Existing income/refund consumers use actual neutral passes. No worker/D1/saved/deployed or complete-mode claim.',
|
||||
'Genuine setup, custody, hidden-hand parity, saved JSON, authenticated SQLite continuation and legal AI participation cover clean ordinary-card, paid Box, Ornithopter, Distrans, Truthtrance, four Sapho scopes, pre-leader Residual Poison, printed Karama cancellation of CHOAM Charity, Inflation or Bene Gesserit Charity or clean normal-auction purchase or winning-bid payment, completed CHOAM card sales, clean Kulon/proactive La La La/Baliset/ordinary Trip to Gamont Worthless and accepted printed Jubba Cloak, Ix-deck Thumper/Amal, a private normal-auction Ixian ally replacement and three battle discard stages. Other private producers, Karama uses, other reactive Worthless uses, pending rewards, full games and live deployment remain unverified. Earlier thirteen-file158/158 passes, including12 new cases. Thirty-two original-action runtime/policy paths and four original-action390px journeys pass; two separately controlled cross/typed active-price probes retain the original independent grant. No worker/D1/real-room/deployed assurance or complete-mode claim. Earlier eighteen-file234/234 passes, including8 new cases. Fourteen original-action runtime/policy paths and four actual original-action390px journeys pass; two explicitly controlled extra-stop-card acquisitions prove unclaimed refunds. The original same-card stop uses no extra card or staged acquisition. No worker/D1/saved/deployed or complete-mode assurance. Earlier sixteen-file224/224 passes with13 new behavior cases. Twenty-six runtime/policy paths pass:8 original-action Guild paths including4 profiles,18 explicitly controlled native-effect paths. Four actual390px journeys pass (2 original/2 controlled). Existing income/refund consumers use actual neutral passes. No worker/D1/saved/deployed or complete-mode claim. Current nineteen-file238/238 passes with8 new cases. Twenty-six original-action runtime/policy paths and four actual original-action390px journeys pass. One controlled acquisition/phase probe uses a REAL subsequent CHOAM rediscard and Guild stop without force/wallet staging or duplicate refund. Separate2 controlled Kull parent probes and3 repaired focused cases pass. No worker/D1/saved/deployed certification.',
|
||||
evidence: [
|
||||
'tests/semuta-engine.test.ts',
|
||||
'tests/semuta-recovery.test.ts',
|
||||
@@ -1307,6 +1308,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'tests/semuta-guild-transport.test.ts',
|
||||
'tests/semuta-shipment-price.test.ts',
|
||||
'tests/semuta-special-karama.test.ts',
|
||||
'tests/semuta-bg-karama.test.ts',
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -2570,7 +2572,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'Native Guild cross/return permission has its own unpaid counter before price. Preventing it keeps a legal different shipment/movement without removing native half price. The conservative provisional scope is that original opportunity, not a separately signed Cunning second opportunity; immune Junction and common world-to-foreign-world permission remain independent. Native rate cancellation bills the same paid units and original approved split. Free counters stay free and own/allied No-Field prices one marker. Supplier whole-fee1 requires a new offer/consent before becoming2.',
|
||||
'A native Guild holder of Richese Secret Ally still ships up to five real forces at one-counter cost. Canceling its native rate changes sand1 to2, not five physical forces to ten paid units. If full2 is unaffordable, the declared Nexus stays spent without refund or reusable coupon while ordinary shipment remains available; this is the selected affordability composition. A still-held card uses the corrected full price. An independent purchased Karama price stays separate.',
|
||||
'Use a held Karama to purchase the active player’s Force Shipment at half normal price, rounded up, paid entirely to bank. Original typed World routes retain this independent source without new permission or Nexus stacking. Semuta can recover the printed cost without changing its original price beneficiary or creator; a recovered cost cannot also receive the provisional Guild-stop refund, even after rediscard. Unclaimed original costs keep that existing refund. Completion consumes only the grant. Immune Junction keeps its original tariff/payees; accepting it explicitly ends the separate purchased price unused without refund, only upon delivery.',
|
||||
'Advanced Bene Gesserit may spend Worthless cards as Karama. Each conversion opens a separate cancellation response; the Worthless card is discarded even if the power is canceled. Normal Karama cards resolve without that conversion response.',
|
||||
'Advanced Bene Gesserit may spend Worthless cards as Karama. Each opens a separate native cancellation response and stays spent if the power is denied. In the Semuta preview, its physical cost offers recovery before that response; the card remains Worthless for its new faction. A later printed cancellation cost is a separate fresh event whose recovery does not undo denial. Allowed converted prices retain the original owner/beneficiary and claimed-cost refund boundary. Normal printed Karama has no BG conversion response.',
|
||||
'A pending Worthless purchase keeps space for its original auction card. Gifts and hand exchanges must leave enough room for every committed incoming card. Refreshing restores the same auction, unused shipment opportunity or original canceled power.',
|
||||
'Atreides full-plan inspection, Emperor revival, Fremen’s summoned worm, Harkonnen hand exchange the Guild shipment stop and Tleilaxu revival prevention are implemented as once-per-game uses. Other cancellations and special Karamas are still being implemented.',
|
||||
'In the opt-in Kull development preview, another player’s legal ordinary Karama or supported special activation pauses before its costs for CHOAM’s neutral reaction. A declared Kull is pending, not yet a phase ban. Only a different eligible physical Karama can counter it; the original stays reserved. Successful Kull retains the unplayed original, including an advanced Bene Gesserit Worthless card intercepted before conversion, and prevents further Karama activations for that phase. See Kull Wahad for the explicit preview policy and deferred winning-overbid warning.',
|
||||
|
||||
Reference in new issue
Block a user