feat: preserve elite and No-Field Gamont selections through Semuta
This commit is contained in:
1 parent
3171afc522
commit
c468dc9e44
8 files changed
+197
-28
No files matched your search
@@ -40,7 +40,7 @@ export function richeseCardActionBlock(card: VisibleCard): string | null {
|
||||
if (definition?.card.effect === 'distrans')
|
||||
return 'Choose a recipient and card in the Distrans transfer panel.';
|
||||
if (definition?.card.effect === 'semutaDrug')
|
||||
return 'Use the neutral fresh-discard response in the Semuta development preview. Clean ordinary-card, paid Box, retired Ornithopter (before native Intrusion), Distrans, Truthtrance, four Sapho scopes, unready pre-leader Residual Poison, printed Karama canceling CHOAM Charity, Inflation or Bene Gesserit Charity, battle-card and Ix-deck Thumper/Amal discards are connected, as are private normal-auction and Black Market Ixian allied replacements. Battle discards may pause before native Advanced Harkonnen capture; capture resumes after cleanup. A Basic Richese/Moritani CHOAM/Ecaz profile also reacts to private Sabotage and Robbery overflow after clean shipment/movement arrivals. Clean reactive Baliset and La La La also pause after cost without completing the stopped move or free revival. The approved full-hand exchange spends Semuta without increasing the completed hand. Other pending rewards, producers and nested responses remain unfinished.';
|
||||
return 'Use the neutral fresh-discard response in the Semuta development preview. Clean ordinary-card, paid Box, retired Ornithopter (before native Intrusion), Distrans, Truthtrance, four Sapho scopes, unready pre-leader Residual Poison, printed Karama canceling CHOAM Charity, Inflation or Bene Gesserit Charity, battle-card and Ix-deck Thumper/Amal discards are connected, as are private normal-auction and Black Market Ixian allied replacements. Battle discards may pause before native Advanced Harkonnen capture; capture resumes after cleanup. A Basic Richese/Moritani CHOAM/Ecaz profile also reacts to private Sabotage and Robbery overflow after clean shipment/movement arrivals. Clean reactive Baliset and La La La pause after cost without completing the stopped move or free revival. Proactive Trip to Gamont preserves the exact ordinary/elite return or native No-Field reveal and capped recall. The approved full-hand exchange spends Semuta without increasing the completed hand. Other pending rewards, producers and nested responses remain unfinished.';
|
||||
return definition && definition.card.effect !== 'karama'
|
||||
? 'This Richese card’s effect is not implemented yet. You can inspect its rules.'
|
||||
: null;
|
||||
@@ -92,7 +92,7 @@ export function cardPresentation(card: VisibleCard): CardPresentation {
|
||||
richese.card.effect === 'juiceOfSapho'
|
||||
? 'Richese effect integration and verification are incomplete. Supported controls cover Once Around first or last while your bid remains open, bounded movement and battle-choice ordering, and aggressor during the shared pre-plan opportunity. Earlier bids stay committed. Later aggressor intervention and other phase or auction modes remain unfinished; expansion starts remain disabled.'
|
||||
: richese.card.effect === 'semutaDrug'
|
||||
? 'The explicit Semuta development preview offers neutral responses after clean public ordinary-card discards, paid Box disposal, retired Ornithopter movement (including a following Bene Gesserit Intrusion), Distrans, ordinary and queued definite Truthtrance answers, four Sapho scopes, unready pre-leader Residual Poison, printed Karama cancelling CHOAM Charity/Inflation/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, ordinary Trip to Gamont Worthless use, accepted Jubba Cloak before storm protection, Ix-deck Thumper/Amal, three battle discard stages (including before native Advanced Harkonnen capture) and private normal-auction or Black Market Ixian ally replacements. The Basic Richese/Moritani CHOAM/Ecaz entry adds private Sabotage and Robbery overflow after clean shipment/movement arrivals, preserving the original victim/draw and arrival suffix. Commitment is owner-only; the approved full-hand exchange spends Semuta without increasing your completed hand. Other pending rewards, producers, nested responses and normal expansion starts remain unfinished.'
|
||||
? 'The explicit Semuta development preview offers neutral responses after clean public ordinary-card discards, paid Box disposal, retired Ornithopter movement (including a following Bene Gesserit Intrusion), Distrans, ordinary and queued definite Truthtrance answers, four Sapho scopes, unready pre-leader Residual Poison, printed Karama cancelling CHOAM Charity/Inflation/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, proactive Trip to Gamont with an ordinary/elite force or concealed No-Field, accepted Jubba Cloak before storm protection, Ix-deck Thumper/Amal, three battle discard stages (including before native Advanced Harkonnen capture) and private normal-auction or Black Market Ixian ally replacements. The Basic Richese/Moritani CHOAM/Ecaz entry adds private Sabotage and Robbery overflow after clean shipment/movement arrivals, preserving the original victim/draw and arrival suffix. Commitment is owner-only; the approved full-hand exchange spends Semuta without increasing your completed hand. Other pending rewards, producers, nested responses and normal expansion starts remain unfinished.'
|
||||
: richese.card.effect === 'mirrorWeapon'
|
||||
? 'Mirror Weapon works in bounded CHOAM/Richese-deck classic-faction battles; copied Tooth and Stone choices resolve separately, and a winning physical Mirror may be kept even after copying Tooth or Artillery. This winner-retention treatment is a user-selected interpretation, not an official clarification. Combined modes and full Richese starts remain gated.'
|
||||
: richese.card.effect === 'stoneBurner'
|
||||
|
||||
+15
-8
@@ -1397,6 +1397,8 @@ export type Game = {
|
||||
card: string;
|
||||
target: string;
|
||||
location: string;
|
||||
elite?: 1;
|
||||
noFieldEvent?: string;
|
||||
stateSignature: string;
|
||||
}
|
||||
| {
|
||||
@@ -5220,7 +5222,7 @@ function choamLaLaLaDiscardSignature(g: Game, c: ChoamLaLaLaDiscardContinuation)
|
||||
const owner = getPlayer(g, c.player), target = getPlayer(g, c.target);
|
||||
return JSON.stringify({
|
||||
player: c.player, card: c.card, target: c.target,
|
||||
...(c.stoppedRevival ? { stoppedRevival: true } : {}),
|
||||
stoppedRevival: c.stoppedRevival,
|
||||
turn: g.turn, phase: g.phase, active: g.active, order: g.order,
|
||||
revivalRules: g.revivalRules, pendingRevival: g.pendingRevival,
|
||||
parent: nullentropyParentSignature(g,
|
||||
@@ -5238,7 +5240,7 @@ function choamBalisetDiscardSignature(g: Game, c: ChoamBalisetDiscardContinuatio
|
||||
const owner = getPlayer(g, c.player);
|
||||
return JSON.stringify({
|
||||
player: c.player, card: c.card, target: c.target, territory: c.territory,
|
||||
...(c.stoppedMovement ? { stoppedMovement: true } : {}),
|
||||
stoppedMovement: c.stoppedMovement,
|
||||
turn: g.turn, phase: g.phase, active: g.active, order: g.order,
|
||||
restrictions: g.choamBaliset, remaining: g.movementRemaining,
|
||||
parent: nullentropyParentSignature(g,
|
||||
@@ -5256,6 +5258,7 @@ function choamGamontDiscardSignature(g: Game, c: ChoamGamontDiscardContinuation)
|
||||
const owner = getPlayer(g, c.player), target = getPlayer(g, c.target);
|
||||
return JSON.stringify({
|
||||
player: c.player, card: c.card, target: c.target, location: c.location,
|
||||
elite: c.elite, noFieldEvent: c.noFieldEvent,
|
||||
turn: g.turn, phase: g.phase, active: g.active, order: g.order,
|
||||
ready: g.ready, storm: g.storm,
|
||||
parent: nullentropyParentSignature(g,
|
||||
@@ -5966,7 +5969,10 @@ function treacheryDiscardIntegrity(g: Game) {
|
||||
requireRule(
|
||||
g.phase === 8 && owner?.faction === 'choam' &&
|
||||
c.target !== c.player &&
|
||||
gamontAvailable(g, c.target, c.location, 0) &&
|
||||
(c.elite === undefined || c.elite === 1) &&
|
||||
(c.noFieldEvent === undefined ||
|
||||
(typeof c.noFieldEvent === 'string' && c.noFieldEvent.length > 0)) &&
|
||||
gamontAvailable(g, c.target, c.location, c.elite ?? 0, c.noFieldEvent) &&
|
||||
!g.pendingChoamWorthless && !g.response && !g.decision &&
|
||||
!g.phaseOpening && !g.choamMarket &&
|
||||
batch.cause === 'choam:gamont' && batch.entries.length === 1 &&
|
||||
@@ -5974,7 +5980,7 @@ function treacheryDiscardIntegrity(g: Game) {
|
||||
entry.card.id === c.card && entry.card.kind === 'worthless' &&
|
||||
entry.card.name === 'Trip to Gamont' &&
|
||||
c.stateSignature === choamGamontDiscardSignature(g, c),
|
||||
'The used Trip to Gamont no longer matches its pending ordinary force return.',
|
||||
'The used Trip to Gamont no longer matches its pending typed force return.',
|
||||
);
|
||||
} else if (continuation?.kind === 'choamJubbaDiscard') {
|
||||
const c = continuation, entry = batch.entries[0];
|
||||
@@ -6959,7 +6965,7 @@ function finishTreacheryDiscard(g: Game, claim?: { player: string; card: string
|
||||
log(g, 'The declared move was prevented without moving forces or spending a movement.');
|
||||
} else if (next.kind === 'choamGamontDiscard') {
|
||||
completeChoamGamont(g, getPlayer(g, next.player), next.target,
|
||||
next.location, 0);
|
||||
next.location, next.elite ?? 0, next.noFieldEvent);
|
||||
} else if (next.kind === 'choamJubbaDiscard') {
|
||||
completeChoamJubba(g, getPlayer(g, next.player), next.territory);
|
||||
offerChoamStorm(g);
|
||||
@@ -24028,8 +24034,8 @@ function finishResponse(g: Game, canceled: boolean,bureaucratDiversion?:number)
|
||||
kind: 'choamBalisetDiscard', player: choam.id,
|
||||
card: used.id, target: pending.target!,
|
||||
territory: pending.location!, stateSignature: '',
|
||||
...(movement ? { stoppedMovement: true as const } : {}),
|
||||
};
|
||||
if (movement) continuation.stoppedMovement = true;
|
||||
const entries = [{ card: used, discardedBy: choam.id, publicFace: true }];
|
||||
if (semutaOfferSupported(g, continuation, entries)) {
|
||||
continuation.stateSignature = choamBalisetDiscardSignature(g, continuation);
|
||||
@@ -24052,7 +24058,6 @@ function finishResponse(g: Game, canceled: boolean,bureaucratDiversion?:number)
|
||||
}
|
||||
} else if (pending.effect === 'gamont') {
|
||||
if (g.semutaPreview && used.name === 'Trip to Gamont' &&
|
||||
pending.elite === 0 && !pending.noFieldEvent &&
|
||||
!pending.storm && !pending.revival && !pending.movement &&
|
||||
!pending.mentat && !pending.nexusEvent) {
|
||||
const continuation: ChoamGamontDiscardContinuation = {
|
||||
@@ -24060,6 +24065,8 @@ function finishResponse(g: Game, canceled: boolean,bureaucratDiversion?:number)
|
||||
card: used.id, target: pending.target!,
|
||||
location: pending.location!, stateSignature: '',
|
||||
};
|
||||
if (pending.elite === 1) continuation.elite = 1;
|
||||
if (pending.noFieldEvent) continuation.noFieldEvent = pending.noFieldEvent;
|
||||
const entries = [{ card: used, discardedBy: choam.id, publicFace: true }];
|
||||
if (semutaOfferSupported(g, continuation, entries)) {
|
||||
continuation.stateSignature = choamGamontDiscardSignature(g, continuation);
|
||||
@@ -24079,8 +24086,8 @@ function finishResponse(g: Game, canceled: boolean,bureaucratDiversion?:number)
|
||||
const continuation: ChoamLaLaLaDiscardContinuation = {
|
||||
kind: 'choamLaLaLaDiscard', player: choam.id,
|
||||
card: used.id, target: pending.target!, stateSignature: '',
|
||||
...(revival ? { stoppedRevival: true as const } : {}),
|
||||
};
|
||||
if (revival) continuation.stoppedRevival = true;
|
||||
const entries = [{ card: used, discardedBy: choam.id, publicFace: true }];
|
||||
if (semutaOfferSupported(g, continuation, entries)) {
|
||||
continuation.stateSignature = choamLaLaLaDiscardSignature(g, continuation);
|
||||
|
||||
+2
-1
@@ -1169,7 +1169,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, definite ordinary Truthtrance answers including queued questions, supported shipment promises and battle answers before or after plan submission/reveal, 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 or reactive La La La disposal before blocking free revival, proactive or reactive 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 or Black Market Ixian ally replacement also pauses before drawing, without showing its face until a holder commits; original payment and seller income settle once. Cache purchases cannot use native Ixian allied replacement. Ordinary definite Truthtrance answers pause even when another question stays reserved or the answer bound a supported shipment or unsubmitted battle-plan promise. Submitted/revealed plans bind no new promise, but their answers still offer Semuta without changing the plan. Under the user-selected exchange ruling, Commit may replace Semuta even at your faction hand limit: acquisition and Semuta disposal settle atomically, without increasing your completed hand. Reserved incoming cards still need room. For multiple eligible cards, inspect and select exactly one after commitment. Saved effects resume once. Other private discards, Karama uses, other reactive Worthless uses, elite or concealed Trip returns, inspected/partly ready Residual Poison battles, nested responses, other pending rewards 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, definite ordinary Truthtrance answers including queued questions, supported shipment promises and battle answers before or after plan submission/reveal, 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 or reactive La La La disposal before blocking free revival, proactive or reactive Baliset before restricting movement, proactive Trip to Gamont before its ordinary/elite return or native No-Field reveal and capped recall, accepted Jubba Cloak before storm protection, and three battle batches. A private normal-auction or Black Market Ixian ally replacement also pauses before drawing, without showing its face until a holder commits; original payment and seller income settle once. Cache purchases cannot use native Ixian allied replacement. Ordinary definite Truthtrance answers pause even when another question stays reserved or the answer bound a supported shipment or unsubmitted battle-plan promise. Submitted/revealed plans bind no new promise, but their answers still offer Semuta without changing the plan. Under the user-selected exchange ruling, Commit may replace Semuta even at your faction hand limit: acquisition and Semuta disposal settle atomically, without increasing your completed hand. Reserved incoming cards still need room. For multiple eligible cards, inspect and select exactly one after commitment. Saved effects resume once. Other private discards, Karama uses, other reactive Worthless uses, inspected/partly ready Residual Poison battles, nested responses, other pending rewards and public starts remain unfinished.'
|
||||
: 'Reference component only: this Richese card’s game actions are not enabled.',
|
||||
...(definition.card.effect === 'semutaDrug' ? [
|
||||
'Mandatory and winner battle discards also pause before native Advanced Harkonnen capture. Claim or Continue completes the original casualties and card cleanup before the capture offer; it does not sample a captive early or repeat capture. Other pending rewards remain outside this bounded path.',
|
||||
@@ -1177,6 +1177,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'A completed paid Nullentropy Box search during battle offers only the used Box. Claim or Continue preserves the two-spice payment, private recovered card, already-shuffled pile and sealed opposing plan. The recovered card may be used in normal legal planning; the search is not repeated.',
|
||||
'A clean Distrans transfer during battle also offers only the used Distrans. The private card has already reached its recipient; claim or Continue cannot repeat the gift or change a sealed plan. A recipient holding Semuta may claim the used Distrans and keep the already-transferred card.',
|
||||
'Reactive Baliset and La La La also offer their used card after the accepted cost. The original uncommitted move or free-revival request is already stopped: claim or Continue installs the original restriction without moving forces, consuming movement, reviving or charging the denied request. Another legal move or paid revival remains available. Movement-card, Ambassador, Nexus and other competing parents remain separate.',
|
||||
'A clean proactive Trip to Gamont also pauses for a selected elite or concealed No-Field. Claim or Continue returns the exact selected physical counter, or performs the native No-Field reveal and returns at most one actual force after reserve-capped materialization. A zero token reveals without inventing a force; taking the used card does not undo that effect or spend revival usage.',
|
||||
'The same local semuta entry also admits Basic native Richese and Moritani with classic opponents, exactly CHOAM/Ecaz decks and no optional modules. A clean shipment/movement Sabotage or Robbery overflow offers its private fresh discard without showing the face before commitment. Claim or Continue preserves the original victim/draw, removed token, shipment payment and forces; Sabotage may then offer its original optional gift. Advanced Moritani, native Ecaz, Ix/E3 mixtures and worm/Ambassador/overlap arrivals remain outside this bounded entry. The table visibly labels the development preview.',
|
||||
] : []),
|
||||
],
|
||||
|
||||
Reference in new issue
Block a user