Accelerate rules-first feedback and integrate native Strongholds
Apply user priority: save/recovery/privacy/custody assurance after all rules. Add quick types/lint, concise failure-preserving reporter, verbose mode, actual empty-execution guard, signal handling and focused verify name selection; compact canonical context/handoffs without mass moves. Tooling15, six negative CLI, named/empty source-bound smokes,206 doc links; same15-case278B vs2455B,2.35s vs2.36s no compute speedclaim. Native Advanced Ix/CHOAM47-card Stronghold profile: actual firstMentat/HMS/sixeffects,18native/four legalpolicies,five genuine2..6games3129actions/no rejection, human390px LAUDR2WNv14 bank2/personal1 plan and real battle. Earlier162/6513 checks before assurance amendment; finalquick25/build/guide pass. No new preservation/privacy audit or public/deployed/full-mode claim.
This commit is contained in:
1 parent
af6d27095c
commit
e9830b6ced
27 files changed
+2339
-194
No files matched your search
@@ -13,6 +13,11 @@ import {
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import type { Game } from '../game/engine';
|
||||
import { isAuditorLeader, treacheryDeck } from '../game/cards';
|
||||
import { STRONGHOLD_CARDS } from '../game/stronghold-cards';
|
||||
import { sampleInventory, verifySampleCustody } from '../tools/sample-custody';
|
||||
import { createTechTokens } from '../game/tech-tokens';
|
||||
const root = new URL('../', import.meta.url).pathname;
|
||||
const cli = new URL('../tools/faction-games.ts', import.meta.url).pathname;
|
||||
type CliResult = {
|
||||
@@ -94,6 +99,83 @@ function assertFailedEvidence(
|
||||
return report;
|
||||
}
|
||||
|
||||
void test('native Stronghold samples admit every two-to-six-seat roster and preserve canonical physical inventories', (t) => {
|
||||
const area = temporary(t);
|
||||
const out = join(area, 'native');
|
||||
assert.equal(run(out, '--profile', 'stronghold-factions',
|
||||
'--players', 'all', '--seed', '1000', '--max-actions', '1').status, 1);
|
||||
const results = json<{ results: CliResult[] }>(join(out, 'results.json')).results;
|
||||
assert.deepEqual(results.map(result => result.name),
|
||||
[2, 3, 4, 5, 6].map(seats => `stronghold-factions-${seats}-advanced`));
|
||||
const roster = ['ixians', 'choam', 'emperor', 'fremen', 'harkonnen', 'beneGesserit'];
|
||||
const canonical = treacheryDeck(['ix', 'choam']).map(card => card.id).sort();
|
||||
assert.equal(canonical.length, 47);
|
||||
assert.equal(new Set(canonical).size, 47);
|
||||
for (const [index, result] of results.entries()) {
|
||||
assert.equal(result.error, 'Error: Action limit');
|
||||
assert.equal(result.actions, 1);
|
||||
const game = json<Game>(join(out, `failed-${result.name}.json`));
|
||||
assert.equal(game.advanced, true);
|
||||
assert.deepEqual(game.expansions, ['ix', 'choam']);
|
||||
assert.deepEqual(game.players.map(player => player.faction), roster.slice(0, index + 2));
|
||||
assert.deepEqual(game.players.map(player => player.bot),
|
||||
['Easy', 'Medium', 'Hard', 'Brutal', 'Easy', 'Medium'].slice(0, index + 2));
|
||||
const inventory = sampleInventory(game);
|
||||
assert.deepEqual(inventory.cards, canonical);
|
||||
verifySampleCustody(game, inventory);
|
||||
assert.deepEqual(Object.keys(game.strongholdCards!.owners).sort(),
|
||||
STRONGHOLD_CARDS.map(card => card.id).sort());
|
||||
assert.ok(Object.values(game.strongholdCards!.owners).every(owner => owner === null));
|
||||
assert.equal(game.strongholdCards!.claimedTurn, 0);
|
||||
assert.equal(game.players.find(player => player.faction === 'choam')!.leaders
|
||||
.filter(isAuditorLeader).length, 1);
|
||||
}
|
||||
const snapshot = join(out, 'failed-stronghold-factions-6-advanced.json');
|
||||
const original = readFileSync(snapshot, 'utf8');
|
||||
const continued = join(area, 'continued');
|
||||
assert.equal(run(continued, '--resume', snapshot, '--seed', '1000',
|
||||
'--max-actions', '1').status, 1);
|
||||
const report = assertFailedEvidence(continued, 'stronghold-factions-6-advanced', 1155, true);
|
||||
assert.equal(report.options.resume?.path, snapshot);
|
||||
verifySampleCustody(json<Game>(join(continued, 'failed-stronghold-factions-6-advanced.json')),
|
||||
sampleInventory(json<Game>(snapshot)), json<Game>(snapshot));
|
||||
assert.equal(readFileSync(snapshot, 'utf8'), original);
|
||||
});
|
||||
|
||||
void test('native Stronghold admission and resume reject foreign profiles and corrupted custody before gameplay', (t) => {
|
||||
const area = temporary(t);
|
||||
const basic = join(area, 'basic');
|
||||
assert.equal(run(basic, '--profile', 'stronghold-factions', '--rules', 'basic').status, 1);
|
||||
assert.equal(existsSync(basic), false);
|
||||
const out = join(area, 'native');
|
||||
assert.equal(run(out, '--profile', 'stronghold-factions', '--players', '2',
|
||||
'--max-actions', '1').status, 1);
|
||||
const source = json<Game>(join(out, 'failed-stronghold-factions-2-advanced.json'));
|
||||
const corruptions: [string, (game: Game) => void][] = [
|
||||
['basic', game => { game.advanced = false; }],
|
||||
['wrong-deck', game => { game.expansions = ['ix']; }],
|
||||
['foreign-faction', game => { game.players[1].faction = 'richese'; }],
|
||||
['missing-module', game => { delete game.strongholdCards; }],
|
||||
['nexus', game => { game.nexusCards = { cards: null, phase: null }; }],
|
||||
['tech', game => { game.techTokens = createTechTokens(); }],
|
||||
['missing-stronghold', game => { Reflect.deleteProperty(game.strongholdCards!.owners, 'arrakeen'); }],
|
||||
['foreign-owner', game => { game.strongholdCards!.owners.arrakeen = 'foreign-seat'; }],
|
||||
['future-custody', game => { game.strongholdCards!.claimedTurn = game.turn + 1; }],
|
||||
['missing-treachery', game => { game.deck.pop(); }],
|
||||
];
|
||||
for (const [label, corrupt] of corruptions) {
|
||||
const game = structuredClone(source);
|
||||
corrupt(game);
|
||||
const snapshot = join(area, `${label}.json`);
|
||||
writeFileSync(snapshot, JSON.stringify(game), { mode: 0o600 });
|
||||
const before = readFileSync(snapshot, 'utf8');
|
||||
const rejected = join(area, `${label}-resume`);
|
||||
assert.equal(run(rejected, '--resume', snapshot, '--max-actions', '1').status, 1, label);
|
||||
assert.equal(existsSync(rejected), false, label);
|
||||
assert.equal(readFileSync(snapshot, 'utf8'), before, label);
|
||||
}
|
||||
});
|
||||
|
||||
void test('Ecaz card samples keep all three physical identities through saved resume', (t) => {
|
||||
const area = temporary(t);
|
||||
const out = join(area, 'ecaz-cards');
|
||||
|
||||
Reference in new issue
Block a user