Offer Semuta before CHOAM Jubba storm protection
This commit is contained in:
1 parent
953be21d38
commit
98d556a0e0
8 files changed
+313
-19
No files matched your search
+69
-5
@@ -1244,6 +1244,13 @@ export type Game = {
|
||||
location: string;
|
||||
stateSignature: string;
|
||||
}
|
||||
| {
|
||||
kind: 'choamJubbaDiscard';
|
||||
player: string;
|
||||
card: string;
|
||||
territory: string;
|
||||
stateSignature: string;
|
||||
}
|
||||
| {
|
||||
kind: 'thumperDiscard';
|
||||
player: string;
|
||||
@@ -5031,6 +5038,24 @@ function choamGamontDiscardSignature(g: Game, c: ChoamGamontDiscardContinuation)
|
||||
targetElites: target.elites, targetNoField: target.noField,
|
||||
});
|
||||
}
|
||||
type ChoamJubbaDiscardContinuation = Extract<
|
||||
NonNullable<Game['pendingTreacheryDiscard']>['continuation'],
|
||||
{ kind: 'choamJubbaDiscard' }
|
||||
>;
|
||||
function choamJubbaDiscardSignature(g: Game, c: ChoamJubbaDiscardContinuation) {
|
||||
const owner = getPlayer(g, c.player);
|
||||
return JSON.stringify({
|
||||
player: c.player, card: c.card, territory: c.territory,
|
||||
turn: g.turn, phase: g.phase, active: g.active, order: g.order,
|
||||
storm: g.storm, stormPending: g.stormPending,
|
||||
resolution: g.stormResolution,
|
||||
parent: nullentropyParentSignature(g,
|
||||
{ response: null, decision: null, pendingKarama: null, phaseOpening: null },
|
||||
`choam:jubba:${c.card}`),
|
||||
ownerHand: owner.hand.map(card => card.id),
|
||||
ownerForces: owner.forces, ownerElites: owner.elites,
|
||||
});
|
||||
}
|
||||
type ThumperDiscardContinuation = Extract<
|
||||
NonNullable<Game['pendingTreacheryDiscard']>['continuation'],
|
||||
{ kind: 'thumperDiscard' }
|
||||
@@ -5721,6 +5746,22 @@ function treacheryDiscardIntegrity(g: Game) {
|
||||
c.stateSignature === choamGamontDiscardSignature(g, c),
|
||||
'The used Trip to Gamont no longer matches its pending ordinary force return.',
|
||||
);
|
||||
} else if (continuation?.kind === 'choamJubbaDiscard') {
|
||||
const c = continuation, entry = batch.entries[0];
|
||||
const owner = g.players.find(p => p.id === c.player);
|
||||
requireRule(
|
||||
g.phase === 0 && owner?.faction === 'choam' &&
|
||||
!!g.stormResolution &&
|
||||
choamStormOptions(g).some(option => option.territory === c.territory) &&
|
||||
!g.pendingChoamWorthless && !g.response && !g.decision &&
|
||||
!g.phaseOpening &&
|
||||
batch.cause === 'choam:jubba' && batch.entries.length === 1 &&
|
||||
entry.publicFace && entry.discardedBy === c.player &&
|
||||
entry.card.id === c.card && entry.card.kind === 'worthless' &&
|
||||
entry.card.name === 'Jubba Cloak' &&
|
||||
c.stateSignature === choamJubbaDiscardSignature(g, c),
|
||||
'The used Jubba Cloak no longer matches its pending storm protection.',
|
||||
);
|
||||
} else if (continuation?.kind === 'thumperDiscard') {
|
||||
const c = continuation, entry = batch.entries[0];
|
||||
requireRule(
|
||||
@@ -6391,6 +6432,7 @@ function semutaOfferSupported(
|
||||
continuation.kind !== 'choamLaLaLaDiscard' &&
|
||||
continuation.kind !== 'choamBalisetDiscard' &&
|
||||
continuation.kind !== 'choamGamontDiscard' &&
|
||||
continuation.kind !== 'choamJubbaDiscard' &&
|
||||
!(continuation.kind === 'battleCleanup' && continuation.source === 'winner') &&
|
||||
continuation.kind !== 'winnerMandatoryDiscard' &&
|
||||
continuation.kind !== 'battleResolved' &&
|
||||
@@ -6462,6 +6504,7 @@ function semutaOfferSupported(
|
||||
continuation.kind === 'choamLaLaLaDiscard' ||
|
||||
continuation.kind === 'choamBalisetDiscard' ||
|
||||
continuation.kind === 'choamGamontDiscard' ||
|
||||
continuation.kind === 'choamJubbaDiscard' ||
|
||||
(continuation.kind === 'karamaCharityDiscard' ||
|
||||
continuation.kind === 'karamaInflationDiscard' ||
|
||||
continuation.kind === 'karamaBgCharityDiscard' ||
|
||||
@@ -6491,6 +6534,10 @@ function semutaOfferSupported(
|
||||
(continuation.kind !== 'choamGamontDiscard' ||
|
||||
(!g.pendingChoamMove && !g.pendingArrivalOverlap &&
|
||||
!g.pendingRevival && !g.ornithopter)) &&
|
||||
(continuation.kind !== 'choamJubbaDiscard' ||
|
||||
(!!g.stormResolution && !g.pendingChoamMove &&
|
||||
!g.pendingArrivalOverlap && !g.pendingRevival &&
|
||||
!g.ornithopter && !g.ecologicalStorm)) &&
|
||||
!g.pendingExchange && !g.pendingIxAlly && !g.pendingNullentropy &&
|
||||
!g.pendingAmbassador && !g.pendingTerrorEntry && !g.pendingShipment &&
|
||||
!g.pendingHomeworldShipment && !g.pendingRicheseGift &&
|
||||
@@ -6650,6 +6697,9 @@ function finishTreacheryDiscard(g: Game, claim?: { player: string; card: string
|
||||
} else if (next.kind === 'choamGamontDiscard') {
|
||||
completeChoamGamont(g, getPlayer(g, next.player), next.target,
|
||||
next.location, 0);
|
||||
} else if (next.kind === 'choamJubbaDiscard') {
|
||||
completeChoamJubba(g, getPlayer(g, next.player), next.territory);
|
||||
offerChoamStorm(g);
|
||||
} else if (next.kind === 'nullentropyDiscard') {
|
||||
g.response = next.resume.response;
|
||||
g.decision = next.resume.decision;
|
||||
@@ -20484,6 +20534,10 @@ function completeChoamBaliset(g: Game, target: string, location: string) {
|
||||
(g.choamBaliset ??= []).push({ turn: g.turn, player: target, territory: location });
|
||||
log(g, `${getPlayer(g, target).name} cannot move into CHOAM's ${territory(location).name} this phase; shipment remains possible.`);
|
||||
}
|
||||
function completeChoamJubba(g: Game, owner: Player, location: string) {
|
||||
(g.stormResolution!.choamProtected ??= []).push(location);
|
||||
log(g, `${owner.name} protected its own forces in ${territory(location).name} for this storm movement with Jubba Cloak.`);
|
||||
}
|
||||
function completeChoamGamont(
|
||||
g: Game, owner: Player, targetId: string, from: string,
|
||||
elite: number, noFieldEvent?: string,
|
||||
@@ -20871,11 +20925,21 @@ function finishResponse(g: Game, canceled: boolean,bureaucratDiversion?:number)
|
||||
}
|
||||
completeChoamKulon(g, choam);
|
||||
} else if (pending.effect === 'jubba') {
|
||||
(g.stormResolution!.choamProtected ??= []).push(pending.location!);
|
||||
log(
|
||||
g,
|
||||
`${choam.name} protected its own forces in ${territory(pending.location!).name} for this storm movement with Jubba Cloak.`,
|
||||
);
|
||||
if (g.semutaPreview && used.name === 'Jubba Cloak' &&
|
||||
pending.storm && !pending.revival && !pending.movement &&
|
||||
!pending.mentat && !pending.nexusEvent) {
|
||||
const continuation: ChoamJubbaDiscardContinuation = {
|
||||
kind: 'choamJubbaDiscard', player: choam.id,
|
||||
card: used.id, territory: pending.location!, stateSignature: '',
|
||||
};
|
||||
const entries = [{ card: used, discardedBy: choam.id, publicFace: true }];
|
||||
if (semutaOfferSupported(g, continuation, entries)) {
|
||||
continuation.stateSignature = choamJubbaDiscardSignature(g, continuation);
|
||||
stageTreacheryDiscard(g, 'choam:jubba', entries, continuation);
|
||||
return;
|
||||
}
|
||||
}
|
||||
completeChoamJubba(g, choam, pending.location!);
|
||||
} else if (pending.effect === 'baliset') {
|
||||
if (g.semutaPreview && used.name === 'Baliset' &&
|
||||
!pending.storm && !pending.revival && !pending.movement &&
|
||||
|
||||
+4
-4
@@ -1156,7 +1156,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
: definition.card.effect === 'portableSnooper'
|
||||
? 'Choose Portable Snooper as your ordinary poison defense, or use the late-defense panel after reveal before your own traitor decision. It uses ordinary winner retention and cannot stop Poison Tooth.'
|
||||
: definition.card.effect === 'semutaDrug'
|
||||
? 'In the explicit Richese development preview, every seat sees a neutral response to supported clean discards. A CHOAM & Richese game may include the Ix deck: Thumper pauses before worm/spice draw; Amal pauses after halving spice before opening resumes. Other public sources include ordinary cards, paid Box, Ornithopter, Distrans, final Truthtrance, four clean Sapho scopes, pre-leader Residual Poison, printed Karama canceling clean CHOAM Charity, Inflation or Bene Gesserit Charity, a clean printed Karama normal-auction purchase or winning-bid payment before lot settlement, a completed CHOAM card sale after its bank income, clean Kulon Worthless disposal before its movement bonus, proactive La La La disposal before blocking free revival, proactive Baliset before restricting movement, proactive ordinary Trip to Gamont before returning a force, and three battle batches. A private normal-auction Ixian ally replacement also pauses before drawing, without showing its face until a holder commits. Commit held Semuta with a free hand slot; for multiple eligible cards, inspect and select exactly one after commitment. Saved effects resume once. Other private discards, Karama uses, reactive Worthless uses, elite or concealed Trip returns, inspected/partly ready battles, nested responses, pending rewards, full-hand sequencing and public starts remain unfinished.'
|
||||
? 'In the explicit Richese development preview, every seat sees a neutral response to supported clean discards. A CHOAM & Richese game may include the Ix deck: Thumper pauses before worm/spice draw; Amal pauses after halving spice before opening resumes. Other public sources include ordinary cards, paid Box, Ornithopter, Distrans, final Truthtrance, four clean Sapho scopes, pre-leader Residual Poison, printed Karama canceling clean CHOAM Charity, Inflation or Bene Gesserit Charity, a clean printed Karama normal-auction purchase or winning-bid payment before lot settlement, a completed CHOAM card sale after its bank income, clean Kulon Worthless disposal before its movement bonus, proactive La La La disposal before blocking free revival, proactive Baliset before restricting movement, proactive ordinary Trip to Gamont before returning a force, accepted Jubba Cloak before storm protection, and three battle batches. A private normal-auction Ixian ally replacement also pauses before drawing, without showing its face until a holder commits. Commit held Semuta with a free hand slot; for multiple eligible cards, inspect and select exactly one after commitment. Saved effects resume once. Other private discards, Karama uses, reactive Worthless uses except printed Jubba, elite or concealed Trip returns, inspected/partly ready battles, nested responses, pending rewards, full-hand sequencing and public starts remain unfinished.'
|
||||
: 'Reference component only: this Richese card’s game actions are not enabled.',
|
||||
],
|
||||
...(definition.card.effect === 'semutaDrug'
|
||||
@@ -1166,7 +1166,7 @@ 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 and proactive La La La/Baliset/ordinary Trip to Gamont Worthless use, Ix-deck Thumper/Amal and three battle discard stages. A private normal-auction Ixian ally replacement pauses before its draw with a neutral, face-hidden offer. 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 and ordinary Trip returns one selected force 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. Other Karama uses, reactive Worthless uses, private discards, nested responses, pending rewards and full-hand exchange remain unfinished.',
|
||||
'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 and proactive La La La/Baliset/ordinary Trip to Gamont Worthless use, accepted printed Jubba before storm protection, Ix-deck Thumper/Amal and three battle discard stages. A private normal-auction Ixian ally replacement pauses before its draw with a neutral, face-hidden offer. 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. Other Karama uses, other reactive Worthless uses, private discards, nested responses, pending rewards and full-hand exchange remain unfinished.',
|
||||
},
|
||||
{
|
||||
area: 'Player controls' as const,
|
||||
@@ -1184,13 +1184,13 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
area: 'Documentation' as const,
|
||||
status: 'Partial' as const,
|
||||
detail:
|
||||
'The guide distinguishes clean public 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, Ix-deck Thumper/Amal and three battle batches from a private normal-auction Ixian ally replacement, privacy-neutral acknowledgements, provisional free-slot policy and remaining discard sources.',
|
||||
'The guide distinguishes clean public 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 and three battle batches from a private normal-auction Ixian ally replacement, privacy-neutral acknowledgements, provisional free-slot policy and remaining discard sources.',
|
||||
},
|
||||
{
|
||||
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, Ix-deck Thumper/Amal, a private normal-auction Ixian ally replacement and three battle discard stages. Other private producers, Karama uses, reactive Worthless uses, pending rewards, full games and live deployment remain unverified.',
|
||||
'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.',
|
||||
evidence: [
|
||||
'tests/semuta-engine.test.ts',
|
||||
'tests/semuta-recovery.test.ts',
|
||||
|
||||
Reference in new issue
Block a user