Connect all fourteen physical skills and private setup to five card-role battle modifiers, public posture, Harkonnen skilled capture, death return and optional own-leader revival through controls, legal AI and persisted room actions. Nine skill effects and combined-mode/publication acceptance remain unfinished. Captured replacement entitlement and foreign ghola integration stay guarded. Fresh publisher/designer research supports skilled-capture publicity through its unique known physical card; this is recorded as a component inference. Independent review found and fixed Voice/must-use-leader and invalid saved assignment/offer cases. A pre-existing Discovery privacy assertion confused card ID prefixes; the corrected deterministic physical-pair scenario was independently reviewed. The first broad run was 4410/4411; corrected final verification passes types/lint, all 4411 offline tests, build and 43 HTTP tests. Final durations: check 149741ms, build 6659ms, integration 28274ms. Source fingerprint: aef655ac1ebc0eb2f64294e13d3d27337167312cf8ab17568a166ee9c39f6a85. Private final report: 20260913-leader-skills/checks-final/report.json. Three seeded prototype games completed with no rejected bot candidates. Browser setup/card inspection/refresh passed. Another user-reported power outage stopped the server; backup and necessary restart restored the same private seat. All 292 games at restart and their original application records remain unchanged; 33 isolated HTTP-test rooms bring the current total to 325. No database reset. Efficiency: one shared setup/lifecycle implementation supports five connected role effects; selective existing agents handled catalog/UI/tests/research and independent reviews. One repeated broad check was required by the reproduced ID-prefix assertion failure. Aggregate goal counter rose from 65529766 to 67313673 through this checkpoint (1783907), including inherited context and coordination; this is not a billing or savings estimate. Full goal remains active.
149 lines
4.7 KiB
TypeScript
149 lines
4.7 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { DatabaseSync } from 'node:sqlite';
|
|
import { createGame, joinGame, newPlayer } from '../game/engine';
|
|
import {
|
|
startIxPrototypeRoom,
|
|
startPrototypeRoom,
|
|
} from '../tools/prototype-room';
|
|
|
|
function fixture(t: test.TestContext) {
|
|
const db = new DatabaseSync(':memory:');
|
|
t.after(() => db.close());
|
|
db.exec(
|
|
'CREATE TABLE rooms(code TEXT PRIMARY KEY, state TEXT, version INTEGER, updated_at INTEGER); CREATE TABLE seats(room_code TEXT, player_id TEXT, token_hash TEXT);',
|
|
);
|
|
const game = createGame(
|
|
'PROTOTYP',
|
|
newPlayer('i', 'Ix player', 'ixians'),
|
|
true,
|
|
);
|
|
game.expansions = ['ix'];
|
|
joinGame(game, newPlayer('t', 'Tleilaxu player', 'tleilaxu'));
|
|
game.players.forEach((player) => {
|
|
player.ready = true;
|
|
});
|
|
db.prepare('INSERT INTO rooms VALUES(?,?,?,?)').run(
|
|
game.code,
|
|
JSON.stringify(game),
|
|
7,
|
|
100,
|
|
);
|
|
db.prepare('INSERT INTO rooms VALUES(?,?,?,?)').run(
|
|
'KEEPME00',
|
|
'{"unchanged":true}',
|
|
3,
|
|
50,
|
|
);
|
|
db.prepare('INSERT INTO seats VALUES(?,?,?)').run(
|
|
game.code,
|
|
'i',
|
|
'unchanged-private-session',
|
|
);
|
|
return { db, game };
|
|
}
|
|
|
|
void test('local prototype starts the named ready lobby once, retaining every seat and other room', (t) => {
|
|
const { db } = fixture(t);
|
|
const seats = db.prepare('SELECT * FROM seats').all();
|
|
const other = db
|
|
.prepare('SELECT * FROM rooms WHERE code = ?')
|
|
.get('KEEPME00');
|
|
const result = startIxPrototypeRoom(db, 'PROTOTYP', 7);
|
|
assert.equal(result.version, 8);
|
|
assert.equal(result.status, 'setup');
|
|
const saved = db
|
|
.prepare('SELECT state, version FROM rooms WHERE code = ?')
|
|
.get('PROTOTYP')!;
|
|
assert.equal(JSON.parse(saved.state as string).version, saved.version);
|
|
assert.deepEqual(db.prepare('SELECT * FROM seats').all(), seats);
|
|
assert.deepEqual(
|
|
db.prepare('SELECT * FROM rooms WHERE code = ?').get('KEEPME00'),
|
|
other,
|
|
);
|
|
const rows = db.prepare('SELECT * FROM rooms ORDER BY code').all();
|
|
assert.throws(() => startIxPrototypeRoom(db, 'PROTOTYP', 7), /changed/);
|
|
assert.throws(() => startIxPrototypeRoom(db, 'PROTOTYP', 8), /fresh lobby/);
|
|
assert.deepEqual(db.prepare('SELECT * FROM rooms ORDER BY code').all(), rows);
|
|
});
|
|
|
|
void test('unready, wrong identity and incompatible lobbies fail without writing any room or seat', (t) => {
|
|
const { db, game } = fixture(t);
|
|
for (const mutate of [
|
|
() => {
|
|
game.players[0].ready = false;
|
|
},
|
|
() => {
|
|
game.players[0].ready = true;
|
|
game.code = 'NOTMATCH';
|
|
},
|
|
() => {
|
|
game.code = 'PROTOTYP';
|
|
game.expansions = [];
|
|
},
|
|
]) {
|
|
mutate();
|
|
db.prepare('UPDATE rooms SET state = ? WHERE code = ?').run(
|
|
JSON.stringify(game),
|
|
'PROTOTYP',
|
|
);
|
|
const before = db.prepare('SELECT * FROM rooms ORDER BY code').all();
|
|
assert.throws(() => startIxPrototypeRoom(db, 'PROTOTYP', 7));
|
|
assert.deepEqual(
|
|
db.prepare('SELECT * FROM rooms ORDER BY code').all(),
|
|
before,
|
|
);
|
|
assert.equal(
|
|
db.prepare('SELECT count(*) AS count FROM seats').get()!.count,
|
|
1,
|
|
);
|
|
}
|
|
});
|
|
|
|
void test('Leader Skills prototype saves the real private setup once without changing other rooms or seats', (t) => {
|
|
const { db } = fixture(t);
|
|
const game = createGame(
|
|
'PROTOTYP',
|
|
newPlayer('i', 'Atreides', 'atreides'),
|
|
true,
|
|
);
|
|
joinGame(game, newPlayer('h', 'Harkonnen', 'harkonnen'));
|
|
game.players.forEach((p) => {
|
|
p.ready = true;
|
|
});
|
|
db.prepare('UPDATE rooms SET state = ? WHERE code = ?').run(
|
|
JSON.stringify(game),
|
|
game.code,
|
|
);
|
|
const seats = db.prepare('SELECT * FROM seats').all();
|
|
const other = db
|
|
.prepare('SELECT * FROM rooms WHERE code = ?')
|
|
.get('KEEPME00');
|
|
const result = startPrototypeRoom(db, game.code, 7, 'leader-skills');
|
|
assert.equal(result.version, 8);
|
|
const saved = JSON.parse(
|
|
db.prepare('SELECT state FROM rooms WHERE code = ?').get(game.code)!
|
|
.state as string,
|
|
);
|
|
assert.equal(saved.version, 8);
|
|
assert.equal(saved.setupStage, 'leaderSkills');
|
|
assert.equal(saved.leaderSkills.offers.i.cards.length, 2);
|
|
assert.equal(saved.leaderSkills.offers.h.cards.length, 2);
|
|
assert.equal(saved.players[0].hand.length, 1);
|
|
assert.equal(saved.players[1].hand.length, 2);
|
|
const rooms = db.prepare('SELECT * FROM rooms ORDER BY code').all();
|
|
assert.throws(
|
|
() => startPrototypeRoom(db, game.code, 8, 'leader-skills'),
|
|
/redeal/,
|
|
);
|
|
assert.deepEqual(
|
|
db.prepare('SELECT * FROM rooms ORDER BY code').all(),
|
|
rooms,
|
|
);
|
|
assert.deepEqual(db.prepare('SELECT * FROM seats').all(), seats);
|
|
assert.deepEqual(
|
|
db.prepare('SELECT * FROM rooms WHERE code = ?').get('KEEPME00'),
|
|
other,
|
|
);
|
|
});
|