Connect automatic unambiguous Jacurutu physical-force income, a sole-occupant Testing Station storm choice and Shrine reciprocal physical card roles to engine actions, player controls, legal AI and saved continuation. Preserve Weather Control exclusion, exact card/force custody, private hand boundaries and committed effects after restart. Shared role checks cover existing ordinary and special Karama paths; reserved cards cannot enter another pending use. Keep Orgiz, mixed-force Jacurutu allocation and contested Cistern/Testing Station benefits explicitly pending. These are bounded prototypes; complete Discovery/Advanced/expansion and publication gates remain closed. Validation: types, lint and 4,345 offline tests passed (32 added), production build passed, and 40 HTTP tests passed. Independent cross-reviews repaired reserved-card deadlock, physical-card selection, alternate-role permission, storm source/token binding and public-entitlement bot ordering. The first broad run found five failures; focused repairs preceded the stable repeated check. Browser A5EJQ2W4 used genuine Discovery setup/reveal/free entry with targeted QA positioning. A human increased storm movement from two to three sectors, moving sector 6 to 9. Refresh restored station forces and the private Snooper. This was targeted control/recovery evidence, not an unmodified full game. All 187 opening saved rooms remained unchanged; 31 QA/HTTP rooms were added (218 total). Reused the running server; no restart or save reset. Source-bound report: /home/quintine/.local/share/dune/checkpoints/20260913-discovery-location-effects/verification-final/report.json Source fingerprint: beecb85ab7ec39319115389c115434d161591e8549802c542a6343a0fc19bea6 Durations: check 140540ms; build 6741ms; integration 24956ms. Aggregate goal counter 63,116,444 to 64,295,091 at final verification: 1,178,647 tokens before final Git/push overhead for three bounded functions. Standing authorization: Push all from now on.
73 lines
2.1 KiB
TypeScript
73 lines
2.1 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import { applyAction, viewGame, type Game } from '../game/engine';
|
|
import {
|
|
discoveryFixture,
|
|
enterDiscoveryCollection,
|
|
} from './fixture-discovery';
|
|
|
|
/** Genuine module setup, physical reveal and next-turn entry; positions are
|
|
* arranged for a focused storm rather than an unmodified complete game. */
|
|
export function discoveryStormFixture(
|
|
advanced = false,
|
|
ids: [string, string, string] = ['a', 'g', 'f'],
|
|
) {
|
|
let g = discoveryFixture(advanced, ids);
|
|
const token = enterDiscoveryCollection(
|
|
g,
|
|
'ecological-testing-station',
|
|
ids[0],
|
|
);
|
|
if (viewGame(g, ids[0]).discoveries!.canInspect.includes(token.id))
|
|
g = applyAction(g, ids[0], {
|
|
type: 'discovery',
|
|
token: token.id,
|
|
reveal: false,
|
|
});
|
|
g = applyAction(g, ids[0], {
|
|
type: 'discovery',
|
|
token: token.id,
|
|
reveal: true,
|
|
});
|
|
Object.assign(g, {
|
|
phase: 8,
|
|
ready: [],
|
|
decision: null,
|
|
response: null,
|
|
phaseOpening: null,
|
|
stormCard: 2,
|
|
});
|
|
for (const p of g.players) g = applyAction(g, p.id, { type: 'ready' });
|
|
assert.equal(g.decision?.kind, 'discoveryEntry');
|
|
const offer = viewGame(g, ids[0]).discoveryEntry!;
|
|
g = applyAction(g, ids[0], {
|
|
type: 'decision',
|
|
event: offer.event,
|
|
accept: true,
|
|
groups: offer.sources,
|
|
});
|
|
assert.equal(g.discoveryEntry, undefined);
|
|
g.storm = 6;
|
|
if (!advanced)
|
|
for (const id of g.stormDialers)
|
|
g = applyAction(g, id, { type: 'stormDial', amount: 1 });
|
|
assert.equal(g.stormPending, 2);
|
|
return g;
|
|
}
|
|
export function confirmDiscoveryStorm(g: Game) {
|
|
for (const p of g.players) g = applyAction(g, p.id, { type: 'ready' });
|
|
return g;
|
|
}
|
|
export function holdStormCard(g: Game, owner: string, effect: string) {
|
|
const card = [
|
|
...g.deck,
|
|
...g.discard,
|
|
...g.players.flatMap((p) => p.hand),
|
|
].find((c) => c.effect === effect)!;
|
|
assert.ok(card);
|
|
g.deck = g.deck.filter((c) => c.id !== card.id);
|
|
g.discard = g.discard.filter((c) => c.id !== card.id);
|
|
for (const p of g.players) p.hand = p.hand.filter((c) => c.id !== card.id);
|
|
g.players.find((p) => p.id === owner)!.hand.push(card);
|
|
return card;
|
|
}
|