Offer Semuta after clean final Truthtrance answer
This commit is contained in:
1 parent
162f01c160
commit
836af6fb3a
9 files changed
+178
-46
No files matched your search
+16
-15
@@ -7,21 +7,22 @@ Expansion starts and full rules/publication acceptance remain gated.**
|
||||
## Current checkpoint and work
|
||||
|
||||
The explicit [Semuta development preview](SEMUTA_SOURCE_UPDATE_20260907.md#30-september-2026--bounded-clean-discard-runtime)
|
||||
now pauses a clean public ordinary-card discard, the used Box after
|
||||
a completed paid search, an Ornithopter retired on early movement
|
||||
ending, or the public used Distrans after a clean private transfer.
|
||||
Every seat sees the same neutral offer independently of hidden Semuta
|
||||
possession; the transferred card remains private with its recipient.
|
||||
A holder with a free hand slot can commit to the fresh other-player
|
||||
discard; its physical claim and Semuta disposal resume the original
|
||||
effect, Box parent or movement queue once. All-seat decline, four
|
||||
minimal legal AI profiles, exact JSON/SQLite continuation and
|
||||
competing-room CAS are connected. Phone width showed the owned
|
||||
Commit control and identical rival Continue event without a private
|
||||
card face. Completed-flight arrival, competing transactions,
|
||||
multi-card and most other producers, full-hand ordering, normal
|
||||
Richese starts and deployed acceptance remain unfinished; neutral
|
||||
passes and free-slot guard are product safety choices, not rulings.
|
||||
pauses a clean public ordinary-card discard, the used Box after a
|
||||
paid search, early-ended Ornithopter, public used Distrans after a
|
||||
private transfer, or a final definite Truthtrance after its answer
|
||||
is bound. Every seat sees the same neutral offer independently of
|
||||
hidden Semuta possession. The Distrans gift remains private with
|
||||
its recipient; Truthtrance history stays fixed through the claim.
|
||||
A holder with a free hand slot can recover the fresh other-player
|
||||
discard and dispose Semuta; saved effects and the movement queue
|
||||
resume once. All-seat decline, minimal legal AI, exact JSON/SQLite
|
||||
continuation and competing-room CAS are connected. Phone width
|
||||
showed the owned Commit and rival Continue event without a private
|
||||
card face. Promise-bound and queued Truthtrance, completed-flight
|
||||
arrival, competing transactions, multi-card and most other producers,
|
||||
full-hand ordering, normal Richese starts and deployed acceptance
|
||||
remain unfinished. Neutral passes and the free-slot guard are
|
||||
product safety choices, not publisher rulings.
|
||||
|
||||
One [natural Advanced Moritani assassination](MORITANI_ASSASSINATE_LEADERS.md#29-september-2026--natural-revealed-card-game)
|
||||
now proceeds from genuine battle loss to a dead-target Traitor reveal,
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
# Dune implementation status
|
||||
|
||||
## 30 September 2026 — Semuta after a final definite Truthtrance answer
|
||||
|
||||
The explicit Richese Semuta preview now pauses the public card discard
|
||||
after a clean final definite Truthtrance answer. The asker has spent
|
||||
the actual held card, the respondent's answer is already recorded,
|
||||
and the public history is bound before the neutral all-seat offer.
|
||||
Another holder with hand space may take only that freshly consumed
|
||||
card and discard Semuta; all-seat decline retains the original pile.
|
||||
Neither path re-answers, duplicates a promise, restarts a question,
|
||||
or changes the recorded fact.
|
||||
|
||||
Genuine setup and an isolated migrated SQLite room exercise a real
|
||||
declaration, priority passes, privately answerable spice/hand fact,
|
||||
definite answer, persisted reaction and authenticated claim after
|
||||
restart. JSON projections, custody, public answer history, legal AI,
|
||||
owner-only commitment, all-seat decline and saved seat rows remain
|
||||
stable. Setup-era facts, queued follow-ups, promise-bound questions,
|
||||
competing parent transactions, other discard producers and full-hand
|
||||
ordering remain outside this bounded preview.
|
||||
|
||||
Independent rules/continuation and privacy/recovery reviews found no
|
||||
scoped defect. A throwaway production-engine run completed genuine
|
||||
AI setup, declaration, definite hand answer, neutral reaction and
|
||||
bot claim with one fixed public history entry. The phone-width guide
|
||||
rendered this boundary as Partial. `npm run check` passed typecheck,
|
||||
lint and 5,805/5,805 offline cases; `npm run build` and all 55 Vite
|
||||
development HTTP cases passed. No deployed revision was exercised.
|
||||
|
||||
## 30 September 2026 — Semuta after a completed private Distrans transfer
|
||||
|
||||
An explicitly opted-in Richese preview now pauses after a clean
|
||||
|
||||
@@ -12,7 +12,7 @@ Audited 6 September 2026. The ten physical identities and their readable face in
|
||||
|
||||
- Player controls show both original plans, exact undialed counts or honest possible totals, the mode choice and a complete internal guide. No victim/death confirmation is added. The card stays physically reserved until standard cleanup; stale events, duplicate requests and corrupted source custody fail before resources change. All four AI profiles use public force possibilities, legal low-dial plans and revealed leader/bounty information. They preserve leader value when Artillery suppresses bounty and leave traitor/explosion precedence intact.
|
||||
|
||||
- Semuta Drug now has an explicit bounded development preview for clean public ordinary-card, completed paid Box, early-ended Ornithopter and completed private Distrans transfer discards; the private transferred card is not a fresh target. Other producers, full-hand ordering and normal Richese starts remain unfinished. Mirror Weapon and Juice of Sapho have separate bounded paths, not complete combined-mode certification. Other unfinished faction/module systems, mobile acceptance, difficulty-strength calibration and complete Advanced/expansion games still prevent goal completion. No start gate was removed.
|
||||
- Semuta Drug now has an explicit bounded development preview for clean public ordinary-card, paid Box, early-ended Ornithopter, completed private Distrans and final definite Truthtrance discards; no private gift or unresolved promise becomes a fresh target. Other producers, full-hand ordering and normal Richese starts remain unfinished. Mirror Weapon and Juice of Sapho have separate bounded paths, not complete combined-mode certification. Other unfinished faction/module systems, mobile acceptance, difficulty-strength calibration and complete Advanced/expansion games still prevent goal completion. No start gate was removed.
|
||||
|
||||
The complete integrated validation checkpoint is recorded in [IMPLEMENTATION_STATUS.md](IMPLEMENTATION_STATUS.md).
|
||||
|
||||
@@ -112,7 +112,9 @@ supersedes the older inactive-card observations above for one
|
||||
explicitly opted-in Richese development profile. Neutral all-seat
|
||||
responses, physical one-card recovery, a provisional free-slot guard,
|
||||
minimal legal AI and saved continuation now work after clean public
|
||||
ordinary-card discards and the used Box after a completed paid search.
|
||||
Other semantic discard families and full-hand ordering remain
|
||||
unconnected; the ten-card inventory, cache custody and public expansion
|
||||
start gate are unchanged.
|
||||
ordinary-card discards, the used Box after a paid search, early-ended
|
||||
Ornithopter, clean private Distrans transfer and final definite
|
||||
Truthtrance without a queued question or bound promise. Other semantic
|
||||
discard families and full-hand ordering remain unconnected; the
|
||||
ten-card inventory, cache custody and public expansion start gate
|
||||
are unchanged.
|
||||
@@ -14,6 +14,13 @@ gifts and competing parents retain the original direct transfer
|
||||
behavior without this Semuta reaction. The table below remains the
|
||||
broader producer/continuation inventory, not a claim of complete coverage.
|
||||
|
||||
The same preview also pauses a final definite Truthtrance after its
|
||||
public answer, history and physical card disposal are committed. Only
|
||||
an ordinary held Truthtrance with no following queued question, new
|
||||
promise or competing parent qualifies. A claim may move that consumed
|
||||
card, never re-answer the question or rewrite its history; other
|
||||
Truthtrance continuations remain automatic.
|
||||
|
||||
## Main findings
|
||||
|
||||
A post-`applyAction` pile diff cannot implement immediate Semuta. Ix Ambassador and Ixian ally replacement discard then draw directly. Ordinary Karama cancellation/purchase can reach replacement, Harkonnen bonus or a new auction pool indirectly. A stopped frame must resume at a named stage, never rerun its original public action.
|
||||
|
||||
@@ -62,18 +62,21 @@ Do not ask again about normal targets, forced-discard owner bookkeeping, simulta
|
||||
## 30 September 2026 — bounded clean-discard runtime
|
||||
|
||||
The explicit `semuta` development profile starts a fresh Richese roster
|
||||
without opening ordinary expansion starts. It now interrupts **one public
|
||||
ordinary-card discard, the used Box after a completed paid Nullentropy
|
||||
search, an Ornithopter retired by ending movement early, or the used
|
||||
Distrans after a clean completed private transfer**, provided the parent
|
||||
transaction is clean, with a neutral fresh-discard opportunity for every
|
||||
seat regardless of hidden Semuta possession. The flight's already
|
||||
committed movement and physical escrow remain intact; a claim or
|
||||
all-seat decline advances the saved movement queue exactly once.
|
||||
Distrans's transferred card stays privately with its recipient and
|
||||
cannot be claimed; only the played public Distrans enters this fresh
|
||||
batch. Each seat can Continue; only the canonical holder with a free
|
||||
hand slot and a card discarded by another player can commit. The sole
|
||||
without opening ordinary expansion starts. It interrupts **one public
|
||||
ordinary-card discard, the used Box after a paid Nullentropy search, an
|
||||
Ornithopter retired by ending movement early, the used Distrans after
|
||||
a clean private transfer, or a final definite Truthtrance after its
|
||||
answer is bound**, provided the parent transaction is clean. Every seat
|
||||
sees a neutral fresh-discard opportunity regardless of hidden Semuta
|
||||
possession. The flight's committed movement and escrow remain intact;
|
||||
a claim or all-seat decline advances its saved queue once. Distrans's
|
||||
transferred card stays privately with its recipient and cannot be
|
||||
claimed; only the played public Distrans enters the fresh batch. The
|
||||
Truthtrance's public answer and history are already committed before
|
||||
the offer, and neither is re-evaluated afterward.
|
||||
|
||||
Each seat can Continue; only the canonical holder with a free hand
|
||||
slot and a card discarded by another player can commit. The sole
|
||||
eligible fresh card transfers automatically in the same accepted action,
|
||||
then Semuta goes to the normal discard pile. The saved ordinary effect
|
||||
or Box parent resumes once; passing by every seat preserves the
|
||||
@@ -87,11 +90,11 @@ policy**, not a newly discovered publisher timing rule. The explicit
|
||||
free-slot requirement is a provisional safety guard, not the printed
|
||||
resolution of full-hand take-before-discard. Auction, CHOAM market,
|
||||
pending gifts, competing responses, completed Ornithopter flights with
|
||||
arrival follow-up and other typed discard producers retain their
|
||||
existing automatic continuation; Semuta cannot claim those events yet.
|
||||
The completed Box's selected search card stays private and its paid
|
||||
shuffle is not repeated when another seat takes the used Box. No source
|
||||
ruling is inferred from this omission. The
|
||||
arrival follow-up, queued or promise-bound Truthtrance and other typed
|
||||
discard producers retain their existing automatic continuation; Semuta
|
||||
cannot claim those events yet. The completed Box's selected search card
|
||||
stays private and its paid shuffle is not repeated when another seat
|
||||
takes the used Box. No source ruling is inferred from this omission. The
|
||||
separate [reaction privacy audit](SEMUTA_PRIVACY_REVIEW.md) explains why
|
||||
auto-closing only when nobody secretly holds the card would leak
|
||||
possession. The main game and public expansion gates remain closed.
|
||||
+7
-4
@@ -5685,8 +5685,8 @@ function treacheryDiscardIntegrity(g: Game) {
|
||||
} else throw new RuleError('Unknown saved discard continuation.');
|
||||
}
|
||||
/** Bounded public single-card reaction after a completed ordinary effect, paid
|
||||
* Box search, early-ended Ornithopter flight or clean Distrans transfer. A
|
||||
* completed flight's arrival may open further entry windows and stays automatic. */
|
||||
* Box search, early-ended Ornithopter flight, clean Distrans transfer or final
|
||||
* definite Truthtrance answer without a new promise or parent transaction. */
|
||||
function semutaOfferSupported(
|
||||
g: Game,
|
||||
continuation: NonNullable<Game['pendingTreacheryDiscard']>['continuation'],
|
||||
@@ -5695,8 +5695,11 @@ function semutaOfferSupported(
|
||||
if (continuation.kind !== 'ordinaryCardDiscard' &&
|
||||
continuation.kind !== 'nullentropyDiscard' &&
|
||||
continuation.kind !== 'distransDiscard' &&
|
||||
!(continuation.kind === 'ornithopterDiscard' && continuation.source === 'end')) return false;
|
||||
if (continuation.kind === 'distransDiscard' &&
|
||||
!(continuation.kind === 'ornithopterDiscard' && continuation.source === 'end') &&
|
||||
!(continuation.kind === 'truthtranceDiscard' &&
|
||||
continuation.consumed.source === undefined &&
|
||||
continuation.remaining === null && continuation.promise === null)) return false;
|
||||
if ((continuation.kind === 'distransDiscard' || continuation.kind === 'truthtranceDiscard') &&
|
||||
(g.pendingChoamMove || g.pendingIxMove || g.pendingFremenMove ||
|
||||
g.ornithopter || g.summonedWorm || g.wormRides.length > 0)) return false;
|
||||
return g.semutaPreview === true && g.status === 'playing' &&
|
||||
|
||||
+4
-4
@@ -1155,7 +1155,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 an explicitly opted-in Richese development game, every seat sees a neutral response after a clean public ordinary-card discard, a completed paid Box search, early ending an Ornithopter flight, or completing a clean private Distrans transfer. Continue or commit your held Semuta with a free hand slot; only the freshly discarded public card transfers, never the private card given to its recipient. Completed-flight arrival, competing transactions, other discard producers, full-hand sequencing and normal Richese starts remain unfinished.'
|
||||
? 'In an explicitly opted-in Richese development game, every seat sees a neutral response after a clean public ordinary-card discard, completed paid Box search, early-ended Ornithopter flight, clean private Distrans transfer or final definite Truthtrance answer. Continue or commit held Semuta with a free hand slot; only the freshly discarded public card transfers. A Distrans gift remains private with its recipient, and the Truthtrance answer/history is already bound. Competing transactions, later queued questions, full-hand sequencing and normal Richese starts remain unfinished.'
|
||||
: 'Reference component only: this Richese card’s game actions are not enabled.',
|
||||
],
|
||||
...(definition.card.effect === 'semutaDrug'
|
||||
@@ -1165,7 +1165,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
area: 'Implementation' as const,
|
||||
status: 'Partial' as const,
|
||||
detail:
|
||||
'An explicitly opted-in Richese profile interrupts clean public ordinary-card, completed paid Box, early-ended Ornithopter and completed private Distrans discards with a neutral all-seat offer. Committing held Semuta claims the sole eligible other-player discarded card and discards Semuta. The private Distrans gift remains with its recipient; the other saved effects and movement queue resume once. Completed-flight arrival, competing transactions, other typed producers and full-hand exchange remain unfinished.',
|
||||
'An explicitly opted-in Richese profile interrupts clean public ordinary-card, paid Box, early-ended Ornithopter, private Distrans and final definite Truthtrance discards with a neutral all-seat offer. Committing held Semuta claims only the other-player discarded card and discards Semuta. The private gift stays with its recipient, the answered Truthtrance history remains public, and saved effects or movement resume once. Promise-bound or queued questions, competing transactions, other typed producers and full-hand exchange remain unfinished.',
|
||||
},
|
||||
{
|
||||
area: 'Player controls' as const,
|
||||
@@ -1183,13 +1183,13 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
area: 'Documentation' as const,
|
||||
status: 'Partial' as const,
|
||||
detail:
|
||||
'The guide distinguishes bounded public ordinary-card, paid Box, early-ended Ornithopter and clean Distrans previews, privacy-neutral acknowledgements and conservative free-slot policy from unresolved full-hand sequencing and remaining discard sources.',
|
||||
'The guide distinguishes bounded public ordinary-card, paid Box, early-ended Ornithopter, clean Distrans and final definite Truthtrance previews, privacy-neutral acknowledgements and conservative free-slot policy from unresolved full-hand sequencing and remaining discard sources.',
|
||||
},
|
||||
{
|
||||
area: 'Verification' as const,
|
||||
status: 'Partial' as const,
|
||||
detail:
|
||||
'Genuine setup, physical card custody, neutral hidden-hand parity, saved JSON, authenticated SQLite CAS and legal AI participation cover clean ordinary-card, paid Box, early-ended Ornithopter and private Distrans reactions. Other discard producers, full games and live deployment remain unverified.',
|
||||
'Genuine setup, physical card custody, neutral hidden-hand parity, saved JSON, authenticated SQLite CAS and legal AI participation cover clean ordinary-card, paid Box, early-ended Ornithopter, private Distrans and final definite Truthtrance reactions. Other discard producers, full games and live deployment remain unverified.',
|
||||
evidence: [
|
||||
'tests/semuta-engine.test.ts',
|
||||
'tests/semuta-recovery.test.ts',
|
||||
|
||||
@@ -320,3 +320,52 @@ void test('a completed private Distrans transfer offers only its public used car
|
||||
assert.deepEqual(viewGame(reload(claimed), p.id), viewGame(claimed, p.id));
|
||||
assert.throws(() => applyAction(claimed, 'r', { type: 'semutaCommit', event }));
|
||||
});
|
||||
|
||||
void test('a final definite Truthtrance answer offers its consumed card after public history binds', () => {
|
||||
const { g, semuta, hajr } = fixture();
|
||||
player(g, 'a').hand = [];
|
||||
g.deck.push(hajr);
|
||||
const truth = take(g, 'truthtrance');
|
||||
const shield = g.deck.splice(g.deck.findIndex(card => card.name === 'Shield'), 1)[0];
|
||||
assert.ok(shield);
|
||||
player(g, 'a').hand.push(truth);
|
||||
player(g, 'e').hand.push(shield);
|
||||
const original = inventory(g);
|
||||
let declared = applyAction(g, 'a', { type: 'card', card: truth.id });
|
||||
while (declared.truthtrance?.stage === 'priority') {
|
||||
const responder = declared.players.find(p => !declared.truthtrance!.passed.includes(p.id))!;
|
||||
declared = applyAction(declared, responder.id, { type: 'truthPass' });
|
||||
}
|
||||
const asked = applyAction(declared, 'a', { type: 'truthAsk',
|
||||
question: { kind: 'fact', target: 'e', fact: { kind: 'hand', name: 'Shield' } },
|
||||
});
|
||||
assert.equal(viewGame(asked, 'e').truthAnswer, 'yes');
|
||||
const pending = applyAction(asked, 'e', { type: 'truthAnswer', answer: 'yes' });
|
||||
assert.equal(pending.pendingTreacheryDiscard?.continuation.kind, 'truthtranceDiscard');
|
||||
assert.equal(pending.pendingTreacheryDiscard?.reaction?.stage, 'offer');
|
||||
assert.equal(pending.truthHistory?.length, 1);
|
||||
assert.equal(pending.truthHistory?.[0].answer, 'yes');
|
||||
assert.equal(pending.discard.filter(card => card.id === truth.id).length, 1);
|
||||
const event = pending.pendingTreacheryDiscard!.batch.event;
|
||||
assert.equal(viewGame(pending, 'r').semutaReaction?.canCommit, true);
|
||||
assert.deepEqual(normalizeAutomaticGame(reload(pending)), reload(pending));
|
||||
let declined = reload(pending);
|
||||
for (const id of ['e', 'a', 'r'])
|
||||
declined = applyAction(declined, id, { type: 'semutaPass', event });
|
||||
assert.equal(declined.pendingTreacheryDiscard, null);
|
||||
assert.equal(declined.discard.filter(card => card.id === truth.id).length, 1);
|
||||
assert.deepEqual(declined.truthHistory, pending.truthHistory);
|
||||
assert.deepEqual(inventory(declined), original);
|
||||
const claimed = applyAction(reload(pending), 'r', botActions(viewGame(pending, 'r'))[0]!);
|
||||
assert.equal(claimed.pendingTreacheryDiscard, null);
|
||||
assert.equal(claimed.truthtrance, null);
|
||||
assert.deepEqual(claimed.truthHistory, pending.truthHistory);
|
||||
assert.equal(player(claimed, 'e').hand.filter(card => card.id === shield.id).length, 1);
|
||||
assert.equal(player(claimed, 'r').hand.filter(card => card.id === truth.id).length, 1);
|
||||
assert.equal(claimed.discard.filter(card => card.id === semuta.id).length, 1);
|
||||
assert.equal(claimed.discard.some(card => card.id === truth.id), false);
|
||||
assert.deepEqual(inventory(claimed), original);
|
||||
for (const p of claimed.players)
|
||||
assert.deepEqual(viewGame(reload(claimed), p.id), viewGame(claimed, p.id));
|
||||
assert.throws(() => applyAction(claimed, 'r', { type: 'semutaCommit', event }));
|
||||
});
|
||||
@@ -16,7 +16,7 @@ function take(g: Game, identity: string) {
|
||||
throw new Error('The genuine setup lost a physical card.');
|
||||
}
|
||||
|
||||
void test('authenticated ordinary, paid Box, retired flight and private Distrans claims survive restart and room CAS', async () => {
|
||||
void test('authenticated clean Semuta producers claim once through restart and room CAS', async () => {
|
||||
const f = unitStore();
|
||||
try {
|
||||
const created = await f.rooms.createRoom('Semuta QA', 'richese', false, ['choam']);
|
||||
@@ -208,5 +208,44 @@ void test('authenticated ordinary, paid Box, retired flight and private Distrans
|
||||
assert.deepEqual(f.sqlite.prepare('SELECT * FROM seats').all(), originalSeats);
|
||||
for (const auth of auths)
|
||||
assert.deepEqual(await f.restart().readSeatView(code, auth), viewGame(final, auth.playerId));
|
||||
|
||||
const finalAnswer = structuredClone(final);
|
||||
const lastSemuta = take(finalAnswer, SEMUTA_DRUG_ID);
|
||||
finalAnswer.richeseCache!.push(take(finalAnswer, distrans.id));
|
||||
seat(finalAnswer, ids[0]).hand.push(lastSemuta);
|
||||
const truth = take(finalAnswer, 'truthtrance');
|
||||
seat(finalAnswer, ids[1]).hand.push(truth);
|
||||
Object.assign(finalAnswer, { turn: 6, phase: 5, active: ids[1], ready: [],
|
||||
decision: null, response: null, phaseOpening: null, stormPending: null,
|
||||
movementRemaining: [ids[1], ids[0], ids[2]], hajr: [] });
|
||||
finalAnswer.version = final.version + 1;
|
||||
assert.equal(f.sqlite.prepare('UPDATE rooms SET state=?,version=? WHERE code=? AND version=?')
|
||||
.run(JSON.stringify(finalAnswer), finalAnswer.version, code, final.version).changes, 1);
|
||||
let question = await act(1, { type: 'card', card: truth.id });
|
||||
while (question.truthtrance?.stage === 'priority') {
|
||||
const responder = question.players.find(p => !question.truthtrance!.passed.includes(p.id))!;
|
||||
question = await act(ids.indexOf(responder.id), { type: 'truthPass' });
|
||||
}
|
||||
const targetSpice = seat(question, ids[2]).spice;
|
||||
question = await act(1, { type: 'truthAsk',
|
||||
question: { kind: 'fact', target: ids[2],
|
||||
fact: { kind: 'spice', compare: 'eq', value: targetSpice } } });
|
||||
const answered = await act(2, { type: 'truthAnswer', answer: 'yes' });
|
||||
assert.equal(answered.pendingTreacheryDiscard?.reaction?.stage, 'offer');
|
||||
assert.equal(answered.truthHistory?.at(-1)?.answer, 'yes');
|
||||
await f.restart().continueRoomAutomatic(code, clock);
|
||||
assert.equal((await f.restart().readRoom(code)).version, answered.version);
|
||||
assert.equal((await f.restart().readSeatView(code, auths[0])).semutaReaction?.canCommit, true);
|
||||
const recovered = await act(0, { type: 'semutaCommit',
|
||||
event: answered.pendingTreacheryDiscard!.batch.event });
|
||||
assert.equal(recovered.pendingTreacheryDiscard, null);
|
||||
assert.equal(recovered.truthtrance, null);
|
||||
assert.deepEqual(recovered.truthHistory, answered.truthHistory);
|
||||
assert.equal(seat(recovered, ids[0]).hand.filter(card => card.id === truth.id).length, 1);
|
||||
assert.equal(recovered.discard.filter(card => card.id === SEMUTA_DRUG_ID).length, 1);
|
||||
assert.equal(recovered.discard.some(card => card.id === truth.id), false);
|
||||
assert.deepEqual(f.sqlite.prepare('SELECT * FROM seats').all(), originalSeats);
|
||||
for (const auth of auths)
|
||||
assert.deepEqual(await f.restart().readSeatView(code, auth), viewGame(recovered, auth.playerId));
|
||||
} finally { f.sqlite.close(); }
|
||||
});
|
||||
Reference in new issue
Block a user