import assert from 'node:assert/strict'; import test from 'node:test'; import { applyAction, createGame, newPlayer, viewGame, type Game } from '../game/engine'; import { richeseRevealInterruptSupported } from '../game/richese-reveal-interruption'; import { createRicheseNoField, deployRicheseNoField } from '../game/richese-no-field'; import { bindTruthMixedBribe, createTruthMixedBribeFixture, truthMixedBribeFact, truthMixedBribeGroup, truthMixedBribePayment, } from './fixture-truth-mixed-bribe'; // The public question and residual are produced by the native mixed-question // fixture. The pending auction parent, transfer of its records to a Richese // table, marker, and alternate no-residual ledger are explicitly staged for // this availability comparison; they are not a replay of the original game. function stagedPair() { const fixture = createTruthMixedBribeFixture(); const mixed = truthMixedBribeGroup('or', truthMixedBribeFact(fixture.hajrFact), truthMixedBribePayment(fixture.asker)); const answered = bindTruthMixedBribe(fixture, 'yes', mixed).game; const record = answered.truthHistory?.at(-1); const residuals = answered.bribeExpressionPromises; assert.ok(record && residuals?.length); assert.equal(record.answer, 'yes'); assert.ok(residuals.some(row => !row.fulfilled && !row.released)); const base = createGame('REVEALPRIVACY', newPlayer('r', 'Richese', 'richese'), false, ['choam']); base.players.push(...answered.players.map(player => newPlayer(player.id, player.name, player.faction))); const richese = base.players[0]; richese.noField = deployRicheseNoField(createRicheseNoField(['zero', 'three', 'five']), { tokenId: 'three', controller: 'r', location: { territory: 'polar_sink', sector: 0 }, }); richese.noFieldEvent = 'marker-event'; base.status = 'playing'; base.phase = 3; base.turn = record.turn; base.response = null; base.phaseOpening = null; base.decision = { kind: 'auctionPayment', player: fixture.target }; base.auction = { cards: [], index: 0, bid: 3, bidder: fixture.target, active: fixture.target, passed: [], opener: 0 }; base.truthHistory = [structuredClone(record)]; const withResidual = structuredClone(base); withResidual.bribeExpressionPromises = structuredClone(residuals); const withoutResidual = structuredClone(base); withoutResidual.bribeExpressionPromises = []; return { withResidual, withoutResidual, record }; } void test('a public current-turn mixed answer uniformly hides private residual-dependent interruption availability', () => { const { withResidual, withoutResidual, record } = stagedPair(); const baseline = structuredClone(withoutResidual); baseline.truthHistory = []; assert.equal(richeseRevealInterruptSupported(baseline), true, 'the staged economic parent is otherwise admitted'); for (const game of [withResidual, withoutResidual]) { const before = structuredClone(game); assert.deepEqual(viewGame(game, 'r').truthHistory, [record]); assert.equal(richeseRevealInterruptSupported(game), false); const info = viewGame(game, 'r').richeseNoField; assert.ok(info?.private?.deployed); assert.equal(info.canReveal, false); assert.throws(() => applyAction(game, 'r', { type: 'revealNoField', event: info.event, token: info.private!.deployed!.tokenId })); assert.deepEqual(game, before); } assert.equal(viewGame(withResidual, 'r').richeseNoField!.revealBlock, viewGame(withoutResidual, 'r').richeseNoField!.revealBlock); }); void test('the public history fence applies only to the current turn', () => { const { withoutResidual } = stagedPair(); const game: Game = structuredClone(withoutResidual); game.turn++; assert.equal(richeseRevealInterruptSupported(game), true); assert.equal(viewGame(game, 'r').richeseNoField?.canReveal, true); });