Files
Dune/tests/smuggler-no-field-controls.test.tsx
T
Quintine 788453e07e Integrate Smuggler companion with owned Richese No-Field
Connect the GF9 FAQ combination: one free physical reserve force accompanies an owned concealed No-Field at its unchanged one-marker price. Preserve zero/three/five token values, actual supply at later reveal, optional human choice and all four legal AI paths. Genuine CHOAM & Richese Leader Skills development setup adds the two sourced battle cards without duplicating their Ix identities. Public mode and publication gates stay closed; allied and remaining combined routes are unfinished.

Bind the private declaration across initial No-Field cancellation and later Advanced Guild interception, including saved interruptions. Validate before projection/actions/normalization, hide the internal proof, reject altered declarations immutably, and withdraw the companion once only on successful commitment. Independent rules/privacy/persistence review found no blocker. Preserve other audit setup gates despite expanded deck assembly. Repair the existing randomized recovery test with a genuine seeded Rihani offer and explicit human choice, replacing an inappropriate AI-policy call on a human seat. Initial Karama and later Guild stop retain their distinct existing opportunity outcomes.

Validation: 72 focused regressions plus 10 deck tests; typecheck, lint, 4520 offline tests, production build and 43 HTTP tests passed. SQLite restart, concurrent allowance/cancellation and exact physical custody passed. Browser genuine assignment, private token selection, opt-in, one-spice shipment, refresh, five-force reveal and final refresh preserved six physical Arrakeen forces, 14 reserves, zero Tanks, four spice, Crysknife and Soo-Soo Sook. Genuine four-profile Basic/Advanced games completed in 77/116 actions with 3/4 JSON restores and no rejected candidates; Advanced exercised one companion shipment and later reveal.

After the outage the existing server was healthy. Preserved all 529 opening rooms and original seat/recovery records (563 after QA/HTTP). SQLite integrity and foreign keys passed. No restart, database reset or automation.

Verified source tree: c2aee4138bcb46a05814a340d62eadace3470247ca620844eb0375e78ea4d6c4
2026-09-13 21:37:50 +10:00

129 lines
4.8 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import {
RicheseNoFieldControls,
SmugglerNoFieldChoice,
} from '../components/richese-no-field';
import { botActions } from '../game/bots';
import { DIFFICULTIES } from '../game/bot-profiles';
import { applyAction, viewGame, type Game } from '../game/engine';
import { presenceAt } from '../game/force-presence';
import {
smugglerNoFieldAction,
smugglerNoFieldGame,
} from './smuggler-no-field-fixture';
function markup(game: Game, viewer = 'r', destination = 'arrakeen') {
return renderToStaticMarkup(
createElement(RicheseNoFieldControls, {
game: viewGame(game, viewer),
act() {},
busy: false,
destination,
sector: destination === 'arrakeen' ? 10 : 9,
allyPayment: '',
}),
);
}
void test('the private No-Field panel offers an unchecked free real-force companion without changing the marker price', () => {
const html = markup(smugglerNoFieldGame());
assert.match(html, /Private No-Field inventory/);
for (const value of [0, 3, 5])
assert.match(html, new RegExp(`No-Field ${value}`));
assert.match(
html,
/Use Smuggler to add 1 real reserve force at no added cost/,
);
assert.doesNotMatch(html, /type="checkbox" checked=""/);
assert.match(
html,
/No-Field token still uses the normal one-force shipment price/,
);
assert.match(html, /Ship concealed No-Field [035] · 1 spice/);
assert.match(
html,
/Smuggler companion is a separate real force that leaves reserves immediately/,
);
const optedIn = renderToStaticMarkup(
createElement(SmugglerNoFieldChoice, {
quote: { leader: 'richese-0', amount: 1 },
use: true,
busy: false,
onChange() {},
}),
);
assert.match(optedIn, /type="checkbox" checked=""/);
assert.match(optedIn, /hidden forces remain in reserves until revelation/);
assert.match(markup(smugglerNoFieldGame(true)), /Use Smuggler/);
});
void test('the companion choice follows empty-territory and real-reserve gates and remains owner-private', () => {
const occupied = smugglerNoFieldGame();
occupied.players.find((player) => player.id === 'h')!.forces = {
'arrakeen:9': 1,
};
occupied.players.find((player) => player.id === 'h')!.reserves = 19;
assert.doesNotMatch(markup(occupied), /Use Smuggler/);
const emptyReserves = smugglerNoFieldGame();
emptyReserves.players.find((player) => player.id === 'r')!.reserves = 0;
assert.doesNotMatch(markup(emptyReserves), /Use Smuggler/);
const privateGame = smugglerNoFieldGame();
assert.equal(markup(privateGame, 'h'), '');
assert.equal(viewGame(privateGame, 'h').richeseNoField!.private, null);
assert.doesNotMatch(markup(privateGame, 'h'), /No-Field [035]/);
});
void test('all four bot profiles add the legal companion to owned No-Field shipments', () => {
for (const advanced of [false, true])
for (const difficulty of DIFFICULTIES) {
const game = smugglerNoFieldGame(advanced);
const view = viewGame(game, 'r');
view.players.find((player) => player.id === 'r')!.bot = difficulty;
const noFieldActions = botActions(view).filter(
(action) => action.type === 'ship' && action.noField,
);
const candidates = noFieldActions.filter(
(action) => action.smuggler === true,
);
assert.ok(
candidates.length,
`${advanced ? 'Advanced' : 'Basic'} ${difficulty} omitted the companion`,
);
assert.ok(
noFieldActions.every((action) => action.smuggler === true),
`${advanced ? 'Advanced' : 'Basic'} ${difficulty} left a legal owned No-Field candidate unaugmented`,
);
const destination = String(candidates[0].territory);
let settled = applyAction(game, 'r', candidates[0]);
if (settled.decision?.kind === 'guildShipment')
settled = applyAction(settled, settled.decision.player, {
type: 'decision',
allow: true,
});
assert.equal(settled.players[0].reserves, 19);
assert.equal(presenceAt(settled.players[0], destination), 2);
}
});
void test('all profiles reveal a zero No-Field before moving when its real companion is alongside it', () => {
for (const difficulty of DIFFICULTIES) {
let game = smugglerNoFieldGame();
game = applyAction(game, 'r', smugglerNoFieldAction(game, 0, true));
game.active = 'r';
const view = viewGame(game, 'r');
view.players.find((player) => player.id === 'r')!.bot = difficulty;
const reveal = botActions(view)[0];
assert.equal(reveal.type, 'revealNoField', difficulty);
game = applyAction(game, 'r', reveal);
assert.equal(game.players[0].noField!.deployed, null);
assert.equal(game.players[0].reserves, 19);
assert.equal(presenceAt(game.players[0], 'arrakeen'), 1);
}
});