Add a local faction-only prototype profile for all seven nonempty expansion selections, genuine Basic/Advanced initialization and existing private setup, controls, AI and saved continuation. Ecaz allocates six forces across Imperial Basin sectors and retains fourteen reserves before later setup; preserve Fremen, Advanced BG and Moritani ordering with printed and inferred boundaries explicit. Keep optional Ecaz Treachery cards separate, ordinary deck counts 33/35/47, and Richese cache separate. Public start and publication gates remain closed. Replace the Advanced Ixian/Richese pre-offer stop with an explicit per-lot Ixian decline. Bind the saved original offer to its round, turn, owner, independent event, exact card custody and terms before view/action/normalization. Decline resumes that unchanged lot once without using Technology; later normal lots still offer the exchange. Protect reserved cards across transfers, spending and random Harkonnen exchange, and preserve legitimate Gift/Truthtrance interruptions. The actual special-lot exchange still awaits replacement-custody interpretation. Validation: focused setup, rules, UI/bot, private JSON/SQLite reload and concurrent CAS tests and independent custody/persistence review passed. Typecheck, lint, 4573 offline tests, production build and 43 HTTP tests passed. Browser Ecaz setup rejected an incomplete total, saved a 2/2/2 split, and refreshed to fourteen reserves and unchanged private cards. A separate genuine Advanced QA game restored the Ixian choice; explicit decline opened the unchanged cache lot with legal bidding and retained private hand, spice, pool count and unused Technology after refresh. Five of six genuine four-profile samples finish: CHOAM Basic 1054 actions/28 JSON restores, CHOAM Advanced 225/6, Ecaz Basic 91/2, Ecaz Advanced 523/14, all-three Basic 476/12. Combined Advanced passes three former auction stops and reaches turn three/action614 with 16 restores, then hits an existing CHOAM movement/Ambassador arrival guard. Ecaz samples also include guarded arrival candidates with legal alternatives. Full combined play is not certified; arrival integration is the next gap. Retain initial failed logs, including a corrected private harness sold-card receipt alias. After the outage, all 597 opening rooms and original seat/recovery records were preserved (632 after QA/HTTP). A stale development module cache required restart after a fresh 598-room backup; all 598 matched afterward and the QA seat restored. Final integrity and foreign keys passed. No database reset, recurring automation or publication. Verified source tree: 5e2ff244a1167f2834bb46d609b8de914a34f7049ff7b06046243068c0935131
77 lines
2.6 KiB
TypeScript
77 lines
2.6 KiB
TypeScript
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 { isPrototypeProfile, startPrototypeRoom } from './prototype-room';
|
|
import { sourceSnapshot } from './verification';
|
|
|
|
async function main() {
|
|
const { values } = parseArgs({
|
|
options: {
|
|
db: { type: 'string' },
|
|
profile: { type: 'string', default: 'ix' },
|
|
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 --profile ix|discovery|leader-skills|factions --db PATH --room CODE --version NUMBER --out /private/new-directory\nStarts only a fresh ready lobby for the selected development prototype. Factions supports selected expansion factions without optional modules; the separate Ecaz Treachery variant stays disabled. Backs up all rooms first, preserves sessions and existing games, and rejects stale versions. Normal game-start and publication gates remain closed.',
|
|
);
|
|
return;
|
|
}
|
|
if (
|
|
!isPrototypeProfile(values.profile) ||
|
|
!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 = startPrototypeRoom(db, values.room, version, values.profile);
|
|
writeFileSync(
|
|
resolve(values.out, 'prototype.json'),
|
|
JSON.stringify(
|
|
{
|
|
format: 1,
|
|
startedAt: new Date().toISOString(),
|
|
profile: values.profile,
|
|
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;
|
|
});
|