import assert from 'node:assert/strict'; import test from 'node:test'; import { applyAction, viewGame, type Game } from '../game/engine'; import { botActions } from '../game/bots'; import { DIFFICULTIES } from '../game/bot-profiles'; import { moveSemutaEmperorCard } from './fixture-semuta-emperor-share'; import { knowledgeGame, knowledgePhysical, rejectKnowledge } from './truthtrance-knowledge-fixture'; /** Actual Truthtrance actions after conserving the target's staged hand. */ function pending(hasWeapon: boolean, scope: 'fact' | 'currentTurn' = 'fact'): Game { let game = knowledgeGame(); const target = game.players.find(seat => seat.id === 'b')!; for (const card of target.hand.slice()) moveSemutaEmperorCard(game, card.id, 'f'); if (hasWeapon) { const weapon = [...game.deck, ...game.discard, ...game.players.flatMap(seat => seat.hand)] .find(card => card.kind === 'projectile'); assert.ok(weapon); moveSemutaEmperorCard(game, weapon.id, target.id); } const truth = game.players[0].hand.find(card => card.effect === 'truthtrance'); assert.ok(truth); game = applyAction(game, 'a', { type: 'card', card: truth.id }); while (game.truthtrance?.stage === 'priority') { const seat = game.players.find(row => !game.truthtrance!.passed.includes(row.id)); assert.ok(seat); game = applyAction(game, seat.id, { type: 'truthPass' }); } return applyAction(game, 'a', { type: 'truthAsk', question: { kind: 'freeform', target: 'b', scope, text: 'Do you have a weapon?', } }); } for (const hasWeapon of [true, false]) { void test(`a category question checks the target's current primary weapon role: ${hasWeapon}`, () => { const game = pending(hasWeapon), answer = hasWeapon ? 'yes' : 'no'; const question = game.truthtrance!.question; assert.equal(question?.kind, 'freeform'); if (question?.kind !== 'freeform') throw Error('Expected freeform question'); assert.deepEqual(question.interpreted, { version: 3, fact: { kind: 'handInventory', category: 'weapon', compare: 'gte', value: 1 } }); assert.equal(viewGame(game, 'b').truthAnswer, answer); for (const observer of ['a', 'f', 'o']) assert.equal(viewGame(game, observer).truthAnswer, null); rejectKnowledge(game, 'b', { type: 'truthAnswer', answer: hasWeapon ? 'no' : 'yes' }); const physical = knowledgePhysical(game); for (const difficulty of DIFFICULTIES) { const view = viewGame(game, 'b'); view.players.find(seat => seat.id === 'b')!.bot = difficulty; assert.deepEqual(botActions(view), [{ type: 'truthAnswer', answer }]); } const restored = JSON.parse(JSON.stringify(game)) as Game; const done = applyAction(restored, 'b', { type: 'truthAnswer', answer }); assert.equal(done.truthHistory!.at(-1)!.answer, answer); assert.deepEqual(knowledgePhysical(done), physical); }); } void test('unstamped and current-turn category questions keep their prior manual semantics', () => { const currentTurn = pending(true, 'currentTurn'); assert.equal(currentTurn.truthtrance?.question?.kind, 'freeform'); assert.equal(currentTurn.truthtrance?.question?.kind === 'freeform' && currentTurn.truthtrance.question.interpreted, undefined); const legacy = pending(true); const question = legacy.truthtrance!.question; assert.equal(question?.kind, 'freeform'); if (question?.kind !== 'freeform') throw Error('Expected freeform question'); delete question.interpreted; assert.equal(viewGame(legacy, 'b').truthAnswer, null); assert.equal(applyAction(legacy, 'b', { type: 'truthAnswer', answer: 'no' }).truthHistory!.at(-1)!.answer, 'no'); });