Files
Dune/tests/discovery-battle-recovery.test.ts
T
Quintine f0d3d9c464 Prototype Jacurutu income, Testing Station and Shrine effects
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.
2026-09-13 14:50:09 +10:00

113 lines
3.3 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import type { DatabaseSync } from 'node:sqlite';
import type { RoomsClock } from '../db/rooms';
import { unitStore } from './fixture-nexus-room-store';
import { discoveryBattleBeforeFinalVote } from './fixture-discovery-battle';
const clock: RoomsClock = { now: () => 10000, sleep: async () => {} };
void test('SQLite restoration and competing final votes commit Jacurutu income exactly once', async () => {
const store = unitStore();
const sqlite: DatabaseSync = store.sqlite;
try {
const created = await store.rooms.createRoom(
'Jacurutu recovery',
'atreides',
false,
[],
);
const joined = await store.rooms.joinRoom(
created.view.code,
'Guild',
'guild',
);
const third = await store.rooms.joinRoom(
created.view.code,
'Fremen',
'fremen',
);
const tokens = [created.token, joined.token!, third.token!];
const auths = await Promise.all(
tokens.map((token) => store.rooms.authenticate(created.view.code, token)),
);
const ids = auths.map((auth) => auth.playerId) as [string, string, string];
const stored = await store.rooms.readRoom(created.view.code);
const { game, attacker, defender } = discoveryBattleBeforeFinalVote(
false,
ids,
);
game.code = created.view.code;
game.version = stored.version;
sqlite
.prepare('UPDATE rooms SET state = ?, version = ? WHERE code = ?')
.run(JSON.stringify(game), game.version, game.code);
const seats = sqlite
.prepare('SELECT * FROM seats ORDER BY player_id')
.all();
let arrivals = 0;
let release!: () => void;
const gate = new Promise<void>((resolve) => {
release = resolve;
});
store.hooks.beforeWrite = async () => {
if (++arrivals === 2) release();
await gate;
};
const timer = setTimeout(release, 2000);
try {
const results = await Promise.allSettled(
[0, 1].map(() =>
store
.restart()
.act(
game.code,
auths[1],
game.version,
{ type: 'traitorCall', call: false },
clock,
),
),
);
assert.equal(arrivals, 2);
assert.equal(
results.filter((result) => result.status === 'fulfilled').length,
1,
);
} finally {
clearTimeout(timer);
delete store.hooks.beforeWrite;
}
const done = await store.restart().readRoom(game.code);
assert.equal(done.version, game.version + 1);
assert.equal(
done.players.find((player) => player.id === attacker)!.spice,
23,
);
assert.equal(
done.players.find((player) => player.id === defender)!.tanks,
5,
);
assert.equal(
done.log.filter(
(entry) => entry.automatic?.name === 'Jacurutu Sietch income',
).length,
1,
);
const stable = JSON.stringify(done);
await store.restart().continueRoomAutomatic(game.code, clock);
assert.equal(
JSON.stringify(await store.restart().readRoom(game.code)),
stable,
);
assert.deepEqual(
sqlite.prepare('SELECT * FROM seats ORDER BY player_id').all(),
seats,
);
} finally {
sqlite.close();
}
});