feat: restore final CHOAM Mentat after Semuta Gamont reactions
This commit is contained in:
1 parent
c468dc9e44
commit
12e4bcbf45
7 files changed
+145
-7
No files matched your search
@@ -29,6 +29,20 @@ optional skill bands, combinations and public expansion starts remain open.
|
|||||||
Rules implementation continues; comprehensive assurance and administration
|
Rules implementation continues; comprehensive assurance and administration
|
||||||
remain deferred until all rules are implemented.
|
remain deferred until all rules are implemented.
|
||||||
|
|
||||||
|
**Semuta / CHOAM final Mentat — 11 October.** Gamont disposal in the native
|
||||||
|
final opportunity now offers Semuta, then restores the same CHOAM-owned window.
|
||||||
|
Claim/pass returns the selected force but never checks victory or advances
|
||||||
|
the turn; the original owner must still finish. The regression fails before
|
||||||
|
and passes after, with a native baseline that would give Atreides victory
|
||||||
|
without the recall. Twelve runtime scenarios cover both modes, claim/pass and
|
||||||
|
four legal AI profiles, including owner-window completion.
|
||||||
|
Types/lint and 82 affected unit cases across six files pass.
|
||||||
|
Real GameTable at 390px performs Return this force → Commit Semuta → restored
|
||||||
|
Finish and check victory → turn3/Storm, still playing, without overflow.
|
||||||
|
Temporary QA scaffolds are removed; no saved store or deployed game was targeted.
|
||||||
|
Nexus and other parents remain separate. See
|
||||||
|
[the source/runtime boundary](SEMUTA_SOURCE_UPDATE_20260907.md).
|
||||||
|
|
||||||
**Semuta / typed and concealed Gamont — 11 October.** A clean proactive
|
**Semuta / typed and concealed Gamont — 11 October.** A clean proactive
|
||||||
discard now preserves the selected elite counter or native No-Field event,
|
discard now preserves the selected elite counter or native No-Field event,
|
||||||
instead of hard-coding an ordinary return. The original native helper performs
|
instead of hard-coding an ordinary return. The original native helper performs
|
||||||
|
|||||||
@@ -1,5 +1,35 @@
|
|||||||
# Dune implementation status
|
# Dune implementation status
|
||||||
|
|
||||||
|
## 11 October 2026 — original final Mentat after Semuta/Gamont
|
||||||
|
|
||||||
|
The Gamont receipt now carries an optional final-Mentat marker and signs the
|
||||||
|
native pending opportunity. After the selected physical return, claim/pass
|
||||||
|
restores the same `choamMentat` decision for CHOAM. No victory check or phase
|
||||||
|
advance runs inside the receipt; the original owner must still finish.
|
||||||
|
Unmarked proactive signatures remain unchanged.
|
||||||
|
|
||||||
|
The regression fails before and passes after. Actual native market, readiness
|
||||||
|
and done actions create the closing window. Its unopposed board gives Atreides
|
||||||
|
a three-stronghold victory; recall removes Arrakeen before the same owner can
|
||||||
|
finish. Claim/pass preserves turn2/Mentat, exact force return and cards;
|
||||||
|
wrong-owner completion and retired reactions reject. CHOAM's actual Done then
|
||||||
|
advances to turn3/Storm with no winner. The fixture initially checked phase
|
||||||
|
before native market completion and placed Tabr in sector13; corrected
|
||||||
|
progression and canonical sector14 required no production rule change.
|
||||||
|
|
||||||
|
Twelve isolated runtime scenarios cover Basic/Advanced, claim/pass and all
|
||||||
|
four legal AI profiles, including each profile finishing the restored owner
|
||||||
|
window. Real production GameTable at 390px performs Return this force, Semuta
|
||||||
|
commitment, the restored Finish and check victory and actual turn3/Storm
|
||||||
|
continuation, without horizontal overflow.
|
||||||
|
Types/lint and 82 affected unit cases across six files pass.
|
||||||
|
|
||||||
|
Temporary scripts, isolated server and browser tab are removed/stopped; no
|
||||||
|
saved store or deployed game was targeted. Nexus and other competing parents
|
||||||
|
remain separate. Full Semuta/CHOAM and deployed acceptance remain open.
|
||||||
|
Contracts: [source/runtime](SEMUTA_SOURCE_UPDATE_20260907.md),
|
||||||
|
[discard suffix](SEMUTA_DISCARD_CONTINUATIONS.md).
|
||||||
|
|
||||||
## 11 October 2026 — typed and concealed Gamont with Semuta
|
## 11 October 2026 — typed and concealed Gamont with Semuta
|
||||||
|
|
||||||
The existing receipt already signs target elite and No-Field custody, but the
|
The existing receipt already signs target elite and No-Field custody, but the
|
||||||
|
|||||||
@@ -144,13 +144,14 @@ ordinary declared move is already stopped without spending its allowance.
|
|||||||
Nexus, movement-card/Ambassador and overlapping arrival parents retain their
|
Nexus, movement-card/Ambassador and overlapping arrival parents retain their
|
||||||
automatic paths.
|
automatic paths.
|
||||||
|
|
||||||
Clean proactive Trip to Gamont stages `choamGamontDiscard` after the accepted
|
Clean proactive or final-Mentat Trip to Gamont stages `choamGamontDiscard` after the accepted
|
||||||
public card cost but before its selected ordinary/elite return or native
|
public card cost but before its selected ordinary/elite return or native
|
||||||
No-Field reveal and capped recall. The receipt binds location, exact counter
|
No-Field reveal and capped recall. The receipt binds location, exact counter
|
||||||
type/marker event, target force/reserve/elite/No-Field custody and CHOAM hand.
|
type/marker event, target force/reserve/elite/No-Field custody and CHOAM hand.
|
||||||
Claim or decline completes the original selection once without replaying the
|
Claim or decline completes the original selection once without replaying the
|
||||||
response. A zero marker reveals without inventing a force; revival usage stays
|
response. A zero marker reveals without inventing a force; revival usage stays
|
||||||
unchanged. Closing Mentat, Nexus and competing parents retain automatic paths.
|
unchanged. A final-Mentat marker restores the same CHOAM-owned decision before
|
||||||
|
victory or turn advancement; Nexus and competing parents retain automatic paths.
|
||||||
|
|
||||||
Accepted printed Jubba Cloak stages `choamJubbaDiscard` after
|
Accepted printed Jubba Cloak stages `choamJubbaDiscard` after
|
||||||
its public Worthless card cost but before protecting CHOAM's
|
its public Worthless card cost but before protecting CHOAM's
|
||||||
|
|||||||
@@ -149,12 +149,13 @@ legal. A clean ordinary declared move is already stopped without spending a
|
|||||||
movement. Movement-card/Ambassador parents, Nexus conversion and competing
|
movement. Movement-card/Ambassador parents, Nexus conversion and competing
|
||||||
arrivals retain their original automatic paths.
|
arrivals retain their original automatic paths.
|
||||||
|
|
||||||
A clean proactive Trip to Gamont response pauses after its public card cost,
|
A clean proactive or final-Mentat Trip to Gamont pauses after its public card cost,
|
||||||
before returning the selected ordinary/elite physical force or performing the
|
before returning the selected ordinary/elite physical force or performing the
|
||||||
native concealed No-Field reveal and capped recall. The receipt binds target,
|
native concealed No-Field reveal and capped recall. The receipt binds target,
|
||||||
location, exact counter type or marker event and pre-return custody. Claim or
|
location, exact counter type or marker event and pre-return custody. Claim or
|
||||||
decline applies the original selection once; a zero marker reveals without
|
decline applies the original selection once; a zero marker reveals without
|
||||||
inventing a force. Closing Mentat, Nexus and competing parents remain automatic.
|
inventing a force. Final Mentat restores the same owner decision before victory;
|
||||||
|
Nexus and competing parents remain automatic.
|
||||||
|
|
||||||
An accepted printed Jubba Cloak response pauses after its
|
An accepted printed Jubba Cloak response pauses after its
|
||||||
public Worthless card cost but before the chosen CHOAM territory
|
public Worthless card cost but before the chosen CHOAM territory
|
||||||
@@ -247,6 +248,34 @@ separate [reaction privacy audit](SEMUTA_PRIVACY_REVIEW.md) explains why
|
|||||||
auto-closing only when nobody secretly holds the card would leak
|
auto-closing only when nobody secretly holds the card would leak
|
||||||
possession. The main game and public expansion gates remain closed.
|
possession. The main game and public expansion gates remain closed.
|
||||||
|
|
||||||
|
**11 October 2026 — original final-Mentat continuation.** Gamont disposal
|
||||||
|
inside CHOAM's final opportunity now offers Semuta too. The receipt signs an
|
||||||
|
optional `mentat` marker and the still-pending native opportunity. Claim or
|
||||||
|
all-seat decline performs the original selected return, then restores
|
||||||
|
`choamMentat` for the same CHOAM owner. It never checks victory or advances the
|
||||||
|
turn itself; the original owner still chooses Finish and check victory.
|
||||||
|
Proactive receipt signatures are unchanged when this marker is absent.
|
||||||
|
|
||||||
|
The regression fails before and passes after. Genuine native Collection/Mentat
|
||||||
|
market, readiness and done actions create the final window. Without Gamont,
|
||||||
|
the same closing board gives Atreides a three-stronghold victory. With the
|
||||||
|
return and claim/pass, one force leaves Arrakeen, the same owner window remains
|
||||||
|
on turn2/Mentat, wrong-owner completion and retired reaction reject, and only
|
||||||
|
CHOAM's Done advances to turn3/Storm with no winner. The fixture first placed
|
||||||
|
its phase assertion before the market completion and used the wrong Tabr
|
||||||
|
sector; corrected actual progression and canonical sector14 require no rule
|
||||||
|
change.
|
||||||
|
|
||||||
|
Twelve isolated runtime scenarios cover Basic/Advanced, claim/pass and all four
|
||||||
|
legal AI profiles; the four profiles also complete the restored owner window.
|
||||||
|
Real GameTable at 390px uses Return this force, Commit Semuta, the restored
|
||||||
|
Finish and check victory control and actual turn3/Storm continuation. The game
|
||||||
|
stays playing and has no horizontal overflow. Temporary scripts/server/browser
|
||||||
|
are removed or stopped; no saved store or deployed game was targeted.
|
||||||
|
Types/lint and 82 affected unit cases across six files pass.
|
||||||
|
Nexus and competing parents remain separate; no complete or deployed acceptance
|
||||||
|
is claimed.
|
||||||
|
|
||||||
**11 October 2026 — elite and concealed Gamont targets.** The existing
|
**11 October 2026 — elite and concealed Gamont targets.** The existing
|
||||||
receipt already signed target elite and No-Field custody, but stored neither
|
receipt already signed target elite and No-Field custody, but stored neither
|
||||||
selection and resumed with a literal ordinary-force argument. It now carries
|
selection and resumed with a literal ordinary-force argument. It now carries
|
||||||
@@ -276,8 +305,9 @@ Types/lint and 163 affected unit cases across nine files pass.
|
|||||||
Optional signature fields now serialize directly, and reactive stopped markers
|
Optional signature fields now serialize directly, and reactive stopped markers
|
||||||
are assigned without temporary spread objects. Absent optional fields are
|
are assigned without temporary spread objects. Absent optional fields are
|
||||||
omitted by JSON, preserving the prior ordinary/proactive signature shape.
|
omitted by JSON, preserving the prior ordinary/proactive signature shape.
|
||||||
Closing Mentat, Nexus and competing parents remain separate. This is bounded
|
Closing Mentat remained separate at this counter-selection checkpoint; the
|
||||||
counter-selection coverage, not complete Semuta/CHOAM or deployed acceptance.
|
later continuation above admits it. Nexus and competing parents remain separate.
|
||||||
|
This is bounded coverage, not complete Semuta/CHOAM or deployed acceptance.
|
||||||
|
|
||||||
**11 October 2026 — reactive Baliset and La La La.** Accepted native
|
**11 October 2026 — reactive Baliset and La La La.** Accepted native
|
||||||
declarations now offer their used public card in the clean reactive paths too.
|
declarations now offer their used public card in the clean reactive paths too.
|
||||||
|
|||||||
+6
-1
@@ -1399,6 +1399,7 @@ export type Game = {
|
|||||||
location: string;
|
location: string;
|
||||||
elite?: 1;
|
elite?: 1;
|
||||||
noFieldEvent?: string;
|
noFieldEvent?: string;
|
||||||
|
mentat?: true;
|
||||||
stateSignature: string;
|
stateSignature: string;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
@@ -5259,6 +5260,7 @@ function choamGamontDiscardSignature(g: Game, c: ChoamGamontDiscardContinuation)
|
|||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
player: c.player, card: c.card, target: c.target, location: c.location,
|
player: c.player, card: c.card, target: c.target, location: c.location,
|
||||||
elite: c.elite, noFieldEvent: c.noFieldEvent,
|
elite: c.elite, noFieldEvent: c.noFieldEvent,
|
||||||
|
mentat: c.mentat, mentatPending: c.mentat ? g.choamMentatPending : undefined,
|
||||||
turn: g.turn, phase: g.phase, active: g.active, order: g.order,
|
turn: g.turn, phase: g.phase, active: g.active, order: g.order,
|
||||||
ready: g.ready, storm: g.storm,
|
ready: g.ready, storm: g.storm,
|
||||||
parent: nullentropyParentSignature(g,
|
parent: nullentropyParentSignature(g,
|
||||||
@@ -5972,6 +5974,7 @@ function treacheryDiscardIntegrity(g: Game) {
|
|||||||
(c.elite === undefined || c.elite === 1) &&
|
(c.elite === undefined || c.elite === 1) &&
|
||||||
(c.noFieldEvent === undefined ||
|
(c.noFieldEvent === undefined ||
|
||||||
(typeof c.noFieldEvent === 'string' && c.noFieldEvent.length > 0)) &&
|
(typeof c.noFieldEvent === 'string' && c.noFieldEvent.length > 0)) &&
|
||||||
|
(c.mentat === undefined || (c.mentat === true && g.choamMentatPending === true)) &&
|
||||||
gamontAvailable(g, c.target, c.location, c.elite ?? 0, c.noFieldEvent) &&
|
gamontAvailable(g, c.target, c.location, c.elite ?? 0, c.noFieldEvent) &&
|
||||||
!g.pendingChoamWorthless && !g.response && !g.decision &&
|
!g.pendingChoamWorthless && !g.response && !g.decision &&
|
||||||
!g.phaseOpening && !g.choamMarket &&
|
!g.phaseOpening && !g.choamMarket &&
|
||||||
@@ -6966,6 +6969,7 @@ function finishTreacheryDiscard(g: Game, claim?: { player: string; card: string
|
|||||||
} else if (next.kind === 'choamGamontDiscard') {
|
} else if (next.kind === 'choamGamontDiscard') {
|
||||||
completeChoamGamont(g, getPlayer(g, next.player), next.target,
|
completeChoamGamont(g, getPlayer(g, next.player), next.target,
|
||||||
next.location, next.elite ?? 0, next.noFieldEvent);
|
next.location, next.elite ?? 0, next.noFieldEvent);
|
||||||
|
if (next.mentat) g.decision = { kind: 'choamMentat', player: next.player };
|
||||||
} else if (next.kind === 'choamJubbaDiscard') {
|
} else if (next.kind === 'choamJubbaDiscard') {
|
||||||
completeChoamJubba(g, getPlayer(g, next.player), next.territory);
|
completeChoamJubba(g, getPlayer(g, next.player), next.territory);
|
||||||
offerChoamStorm(g);
|
offerChoamStorm(g);
|
||||||
@@ -24059,7 +24063,7 @@ function finishResponse(g: Game, canceled: boolean,bureaucratDiversion?:number)
|
|||||||
} else if (pending.effect === 'gamont') {
|
} else if (pending.effect === 'gamont') {
|
||||||
if (g.semutaPreview && used.name === 'Trip to Gamont' &&
|
if (g.semutaPreview && used.name === 'Trip to Gamont' &&
|
||||||
!pending.storm && !pending.revival && !pending.movement &&
|
!pending.storm && !pending.revival && !pending.movement &&
|
||||||
!pending.mentat && !pending.nexusEvent) {
|
!pending.nexusEvent) {
|
||||||
const continuation: ChoamGamontDiscardContinuation = {
|
const continuation: ChoamGamontDiscardContinuation = {
|
||||||
kind: 'choamGamontDiscard', player: choam.id,
|
kind: 'choamGamontDiscard', player: choam.id,
|
||||||
card: used.id, target: pending.target!,
|
card: used.id, target: pending.target!,
|
||||||
@@ -24067,6 +24071,7 @@ function finishResponse(g: Game, canceled: boolean,bureaucratDiversion?:number)
|
|||||||
};
|
};
|
||||||
if (pending.elite === 1) continuation.elite = 1;
|
if (pending.elite === 1) continuation.elite = 1;
|
||||||
if (pending.noFieldEvent) continuation.noFieldEvent = pending.noFieldEvent;
|
if (pending.noFieldEvent) continuation.noFieldEvent = pending.noFieldEvent;
|
||||||
|
if (pending.mentat) continuation.mentat = true;
|
||||||
const entries = [{ card: used, discardedBy: choam.id, publicFace: true }];
|
const entries = [{ card: used, discardedBy: choam.id, publicFace: true }];
|
||||||
if (semutaOfferSupported(g, continuation, entries)) {
|
if (semutaOfferSupported(g, continuation, entries)) {
|
||||||
continuation.stateSignature = choamGamontDiscardSignature(g, continuation);
|
continuation.stateSignature = choamGamontDiscardSignature(g, continuation);
|
||||||
|
|||||||
@@ -1178,6 +1178,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
|||||||
'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.',
|
'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.',
|
'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.',
|
'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.',
|
||||||
|
'Trip to Gamont in CHOAM’s final Mentat opportunity also offers the used card. Claim or Continue completes the selected return and restores that same CHOAM-owned opportunity. It does not check victory or advance the turn: CHOAM must still choose Finish and check victory, using the changed board.',
|
||||||
'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.',
|
'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.',
|
||||||
] : []),
|
] : []),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -53,6 +53,63 @@ function fixture(ix = false, ixianAlly = false, advanced = false, opponent: 'emp
|
|||||||
return { g, semuta, hajr };
|
return { g, semuta, hajr };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test('Semuta restores closing CHOAM Mentat after Gamont before the native victory check', () => {
|
||||||
|
const { g, semuta } = fixture(false, false, false, 'choam');
|
||||||
|
const card = take(g, g.deck.find(c => c.name === 'Trip to Gamont')!.id);
|
||||||
|
player(g, 'e').hand.push(card);
|
||||||
|
for (const p of g.players) Object.assign(p, { forces: {}, reserves: 20, tanks: 0 });
|
||||||
|
Object.assign(player(g, 'a'), { forces: {
|
||||||
|
'arrakeen:10': 1, 'carthag:11': 1, 'sietch_tabr:14': 1,
|
||||||
|
}, reserves: 17 });
|
||||||
|
Object.assign(g, { phase: 7, active: null, movementRemaining: [], ready: [] });
|
||||||
|
const original = inventory(g);
|
||||||
|
let state = g;
|
||||||
|
for (const id of ['a', 'e', 'r']) state = applyAction(state, id, { type: 'ready' });
|
||||||
|
state = applyAction(state, 'e', { type: 'decision', done: true });
|
||||||
|
assert.equal(state.phase, 8);
|
||||||
|
assert.equal(state.status, 'playing');
|
||||||
|
for (const id of ['a', 'e', 'r']) state = applyAction(state, id, { type: 'ready' });
|
||||||
|
state = applyAction(state, 'e', { type: 'decision', done: true });
|
||||||
|
assert.equal(state.decision?.kind, 'choamMentat');
|
||||||
|
assert.equal(state.status, 'playing');
|
||||||
|
const unopposed = applyAction(state, 'e', { type: 'decision', done: true });
|
||||||
|
assert.equal(unopposed.status, 'finished');
|
||||||
|
assert.deepEqual(unopposed.winner, ['a']);
|
||||||
|
let pending = applyAction(state, 'e', { type: 'card', mode: 'choam', card: card.id,
|
||||||
|
target: 'a', from: 'arrakeen:10', elite: 0 });
|
||||||
|
while (pending.response) {
|
||||||
|
const actor = pending.players.find(p => !pending.response!.passed.includes(p.id))!;
|
||||||
|
pending = applyAction(pending, actor.id, { type: 'passResponse' });
|
||||||
|
}
|
||||||
|
assert.equal(pending.pendingTreacheryDiscard?.reaction?.stage, 'offer');
|
||||||
|
assert.equal(player(pending, 'a').forces['arrakeen:10'], 1);
|
||||||
|
assert.equal(pending.status, 'playing');
|
||||||
|
const event = pending.pendingTreacheryDiscard!.batch.event;
|
||||||
|
let declined = reload(pending);
|
||||||
|
for (const id of ['a', 'e', 'r']) declined = applyAction(declined, id, { type: 'semutaPass', event });
|
||||||
|
const claimed = applyAction(reload(pending), 'r', { type: 'semutaCommit', event });
|
||||||
|
for (const resumed of [declined, claimed]) {
|
||||||
|
assert.equal(resumed.decision?.kind, 'choamMentat');
|
||||||
|
assert.equal(resumed.decision?.player, 'e');
|
||||||
|
assert.equal(resumed.turn, 2);
|
||||||
|
assert.equal(resumed.phase, 8);
|
||||||
|
assert.equal(resumed.status, 'playing');
|
||||||
|
assert.deepEqual(player(resumed, 'a').forces, { 'carthag:11': 1, 'sietch_tabr:14': 1 });
|
||||||
|
assert.equal(player(resumed, 'a').reserves, 18);
|
||||||
|
assert.deepEqual(inventory(resumed), original);
|
||||||
|
assert.throws(() => applyAction(resumed, 'a', { type: 'decision', done: true }));
|
||||||
|
assert.throws(() => applyAction(resumed, 'r', { type: 'semutaCommit', event }));
|
||||||
|
const next = applyAction(resumed, 'e', { type: 'decision', done: true });
|
||||||
|
assert.equal(next.status, 'playing');
|
||||||
|
assert.equal(next.turn, 3);
|
||||||
|
assert.equal(next.phase, 0);
|
||||||
|
assert.deepEqual(next.winner, []);
|
||||||
|
assert.deepEqual(inventory(next), original);
|
||||||
|
}
|
||||||
|
assert.equal(player(claimed, 'r').hand.some(c => c.id === card.id), true);
|
||||||
|
assert.equal(claimed.discard.some(c => c.id === semuta.id), true);
|
||||||
|
});
|
||||||
|
|
||||||
void test('Semuta preserves one elite Gamont return without spending revival usage', () => {
|
void test('Semuta preserves one elite Gamont return without spending revival usage', () => {
|
||||||
const { g, semuta } = fixture(false, false, true, 'choam', 'emperor');
|
const { g, semuta } = fixture(false, false, true, 'choam', 'emperor');
|
||||||
const card = take(g, g.deck.find(c => c.name === 'Trip to Gamont')!.id);
|
const card = take(g, g.deck.find(c => c.name === 'Trip to Gamont')!.id);
|
||||||
|
|||||||
Reference in new issue
Block a user