import assert from 'node:assert/strict'; import test from 'node:test'; import { applyAction, viewGame } from '../game/engine'; import { homeworldShipmentActions, richeseHomeworldMarkerChoice } from '../game/homeworld-shipment-options'; import { botActions } from '../game/bots'; import { DIFFICULTIES } from '../game/bot-profiles'; import { freshNativeMarkerFixture, native, owner, pass, player } from './fixture-richese-native-marker'; for (const value of [0, 3, 5] as const) void test( `Advanced fresh No-Field ${value} reaches native Richese without duplicating reserves`, () => { const f = freshNativeMarkerFixture(true, value), before = structuredClone(f.game); assert.equal(f.quote.blocked, null); assert.equal(f.quote.amount, 1); assert.equal(f.quote.cost, 1); assert.ok(f.quote.action); let game = applyAction(f.game, owner, f.quote.action); assert.equal(game.response?.kind, 'richeseNoField'); assert.equal(player(game, owner).spice, player(before, owner).spice); assert.equal(player(game, owner).reserves, player(before, owner).reserves); game = pass(game); assert.ok(player(game, owner).shipped); assert.equal(player(game, owner).spice, player(before, owner).spice - 1); assert.equal(player(game, owner).reserves, player(before, owner).reserves); assert.deepEqual(game.homeworlds!.custody, before.homeworlds!.custody); assert.deepEqual(player(game, owner).noField!.deployed!.location, {homeworld:native}); assert.equal(player(game, owner).noField!.lastShipped, f.token); const publicMarker = viewGame(game, 'emperor').richeseNoField!; assert.equal(publicMarker.private, null); assert.equal(publicMarker.public.deployed?.effectiveForces, 1); assert.equal(JSON.stringify(publicMarker.public).includes(f.token), false); const own = player(game, owner); game = pass(applyAction(game, owner, {type:'revealNoField', token:f.token, event:own.noFieldEvent})); assert.equal(player(game, owner).noField!.deployed, null); assert.equal(player(game, owner).reserves, player(before, owner).reserves); assert.deepEqual(game.homeworlds!.custody, before.homeworlds!.custody); assert.deepEqual(f.game, before, 'Applying the shipment leaves its source snapshot intact'); }); void test('Basic rejects fresh native placement while Advanced rejects same-source companions and stale events', () => { const basic = freshNativeMarkerFixture(false); const originalBasic = structuredClone(basic.game); assert.equal(basic.quote.action, null); assert.ok(basic.quote.blocked); assert.deepEqual(basic.game, originalBasic); const f = freshNativeMarkerFixture(true), before = structuredClone(f.game); const mixed = richeseHomeworldMarkerChoice(f.view, native, f.token, 0, undefined, {normal:1, elite:0}); assert.equal(mixed.action, null, 'Native reserves cannot move from a World back to itself'); assert.ok(mixed.blocked); assert.ok(f.quote.action); assert.throws(() => applyAction(f.game, owner, {...f.quote.action!, noFieldEvent:'stale-marker'})); assert.deepEqual(f.game, before); }); void test('existing bot profiles retain a legal Homeworld action in the fresh native position', () => { const f = freshNativeMarkerFixture(true); for (const [index, difficulty] of DIFFICULTIES.entries()) { const view = viewGame(f.game, owner); view.players.find(row => row.id === owner)!.bot = difficulty; const candidates = homeworldShipmentActions(view, index); assert.ok(candidates.length, difficulty); assert.doesNotThrow(() => applyAction(f.game, owner, candidates[0]), difficulty); const nativeCandidate = candidates.find(action => action.type === 'homeworldShip' && action.destination === native && action.noField !== undefined); assert.ok(nativeCandidate, `${difficulty} can select the native marker route`); assert.doesNotThrow(() => applyAction(f.game, owner, nativeCandidate), difficulty); const action = botActions(view)[0]; assert.ok(action, difficulty); assert.doesNotThrow(() => applyAction(f.game, owner, action), difficulty); } });