Connect genuine Ix expansion setup to playable prototypes
Add an offline Ix initializer through the real setup pipeline, supporting Basic and Advanced play, base factions, Ixians and Tleilaxu with the full 47-card deck and 22-card spice deck. Preserve all normal start and publication gates. Add a reusable local prototype starter: back up the named fresh ready lobby, initialize it once by exact version/state comparison and preserve seat/recovery tables and other rooms. Update the existing checklist with a separate prototype stage and document functional scope, source interpretations and later refinement. Six new tests cover all-profile private setup, exact typed starting forces, Face Dancer/treachery custody, JSON continuation, two fixed-seed complete games and local SQLite write/rejection boundaries. Independent review caught a missing JSON version update, now fixed and covered; focused checking fixed one test typing error before the stable broad check. No new rule interpretation was introduced. Validation: types, lint and 4,211 offline tests passed (123.237s); production build passed (6.543s); all 40 HTTP tests passed (23.578s). Source-bound report: 20260913-ix-prototype-final/report.json, fingerprint 3b8f295593bcb314bb72308d81739006b11d949d1e5906dbf6ce0704b15ebd72. Browser W5LE6VFB: human Ixian + Medium Tleilaxu + Hard Atreides from genuine setup, private starting-card refresh, Weather Control, mobile stronghold placement, then owned Brutal autopilot. Atreides won on turn 1; this short complete journey is not proof of every card or faction interaction. Finished private hand restored at phone width; expanded Poison Tooth was readable. Fixed-seed offline games 2026091301 (Basic) and 2026091302 (Advanced) both completed with both Ix factions. All 64 opening room versions/hashes are unchanged. One browser room and 30 HTTP rooms were added, totaling 95. Reused the running server; no restart or automation. Aggregate goal counters: 59,524,858 to 59,976,567 at completed verification, 451,709 tokens before final Git overhead. Two bounded workhorse helpers handled inventory/tests and independent review; one stable full check/build/HTTP run. Full game scope remains active and unfinished; future work prioritizes missing functional prototypes before exhaustive refinement and final acceptance.
This commit is contained in:
1 parent
194471e906
commit
5a76ae0410
12 files changed
+736
-9
No files matched your search
@@ -0,0 +1,53 @@
|
||||
import type { DatabaseSync } from 'node:sqlite';
|
||||
import { initializeIxGameForAudit, viewGame, type Game } from '../game/engine';
|
||||
|
||||
/** Local development only. This cannot create a seat, change its owner, or
|
||||
* redeal a started game. The caller backs up the database before invoking it. */
|
||||
export function startIxPrototypeRoom(
|
||||
db: DatabaseSync,
|
||||
code: string,
|
||||
expectedVersion: number,
|
||||
) {
|
||||
if (
|
||||
!/^[A-Z0-9]{8}$/.test(code) ||
|
||||
!Number.isSafeInteger(expectedVersion) ||
|
||||
expectedVersion < 0
|
||||
)
|
||||
throw new Error('Provide an eight-character room and its current version.');
|
||||
const row = db
|
||||
.prepare('SELECT state, version FROM rooms WHERE code = ?')
|
||||
.get(code);
|
||||
if (!row || typeof row.state !== 'string' || row.version !== expectedVersion)
|
||||
throw new Error(
|
||||
'The room is missing or changed. Refresh before starting the prototype.',
|
||||
);
|
||||
const initial = JSON.parse(row.state) as Game;
|
||||
if (initial.code !== code)
|
||||
throw new Error('The room identity is inconsistent.');
|
||||
const game = initializeIxGameForAudit(initial);
|
||||
// Exercise the same player projection before accepting the new saved state.
|
||||
for (const player of game.players) viewGame(game, player.id);
|
||||
const version = expectedVersion + 1;
|
||||
game.version = version;
|
||||
const result = db
|
||||
.prepare(
|
||||
'UPDATE rooms SET state = ?, version = ?, updated_at = ? WHERE code = ? AND version = ? AND state = ?',
|
||||
)
|
||||
.run(
|
||||
JSON.stringify(game),
|
||||
version,
|
||||
Date.now(),
|
||||
code,
|
||||
expectedVersion,
|
||||
row.state,
|
||||
);
|
||||
if (result.changes !== 1)
|
||||
throw new Error('The lobby changed during setup. No prototype was saved.');
|
||||
return {
|
||||
code,
|
||||
version,
|
||||
status: game.status,
|
||||
advanced: game.advanced,
|
||||
players: game.players.length,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { parseArgs } from 'node:util';
|
||||
import { captureSavedGames } from './saved-game-verification';
|
||||
import { startIxPrototypeRoom } from './prototype-room';
|
||||
import { sourceSnapshot } from './verification';
|
||||
|
||||
async function main() {
|
||||
const { values } = parseArgs({
|
||||
options: {
|
||||
db: { type: 'string' },
|
||||
room: { type: 'string' },
|
||||
version: { type: 'string' },
|
||||
out: { type: 'string' },
|
||||
help: { type: 'boolean' },
|
||||
},
|
||||
});
|
||||
if (values.help) {
|
||||
console.log(
|
||||
'Usage: node --import tsx tools/start-prototype.ts --db PATH --room CODE --version NUMBER --out /private/new-directory\nStarts only a fresh ready Ix expansion lobby for local prototyping. Backs up all rooms first, preserves sessions and existing games, and rejects stale versions. Normal game-start and publication gates remain closed.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!values.db ||
|
||||
!values.room ||
|
||||
!values.out ||
|
||||
!/^\d+$/.test(values.version ?? '')
|
||||
)
|
||||
throw new Error(
|
||||
'Provide --db, --room, --version and a new private --out directory.',
|
||||
);
|
||||
const root = fileURLToPath(new URL('../', import.meta.url));
|
||||
const version = Number(values.version),
|
||||
source = sourceSnapshot(root);
|
||||
const snapshot = await captureSavedGames(root, values.db, values.out, true);
|
||||
if (
|
||||
!snapshot.rooms.some(
|
||||
(room) => room.code === values.room && room.version === version,
|
||||
)
|
||||
)
|
||||
throw new Error('The requested lobby version is absent from the backup.');
|
||||
const db = new DatabaseSync(values.db);
|
||||
try {
|
||||
const result = startIxPrototypeRoom(db, values.room, version);
|
||||
writeFileSync(
|
||||
resolve(values.out, 'prototype.json'),
|
||||
JSON.stringify(
|
||||
{
|
||||
format: 1,
|
||||
startedAt: new Date().toISOString(),
|
||||
profile: 'ix',
|
||||
source,
|
||||
beforeVersion: version,
|
||||
...result,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + '\n',
|
||||
{ flag: 'wx', mode: 0o600 },
|
||||
);
|
||||
console.log(JSON.stringify(result));
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
main().catch((error: unknown) => {
|
||||
console.error(
|
||||
error instanceof Error ? error.message : 'Prototype start failed.',
|
||||
);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
Reference in new issue
Block a user