170 lines
5.0 KiB
TypeScript
170 lines
5.0 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { createGame, newPlayer, type Game } from '../game/engine';
|
|
import {
|
|
validAmbassadorResume,
|
|
type AmbassadorResumeEntry,
|
|
} from '../game/ambassador-resume';
|
|
|
|
function fixture() {
|
|
const g = createGame('AMB-RESUME', newPlayer('e', 'Ecaz', 'ecaz'), true);
|
|
g.players.push(
|
|
newPlayer('f', 'Fremen', 'fremen'),
|
|
newPlayer('b', 'BG', 'beneGesserit'),
|
|
newPlayer('p', 'Guild shipper', 'ixians'),
|
|
);
|
|
g.phase = 1;
|
|
const entry: AmbassadorResumeEntry = {
|
|
event: 'child-entry',
|
|
entrant: 'b',
|
|
territory: 'carthag',
|
|
sector: 11,
|
|
resume: 'wormRide',
|
|
wormRider: 'f',
|
|
guildAdvisorOrigin: {
|
|
event: 'completed-guild-entry',
|
|
player: 'p',
|
|
territory: 'carthag',
|
|
sector: 11,
|
|
},
|
|
};
|
|
return { g, entry };
|
|
}
|
|
void test('legacy direct worm entries and newly stamped direct entries retain their original rider', () => {
|
|
const { g } = fixture();
|
|
const legacy: AmbassadorResumeEntry = {
|
|
event: 'direct',
|
|
entrant: 'f',
|
|
territory: 'arrakeen',
|
|
sector: 10,
|
|
resume: 'wormRide',
|
|
};
|
|
assert.equal(validAmbassadorResume(g, legacy), true);
|
|
assert.equal(validAmbassadorResume(g, { ...legacy, wormRider: 'f' }), true);
|
|
assert.equal(validAmbassadorResume(g, { ...legacy, wormRider: 'p' }), false);
|
|
assert.equal(validAmbassadorResume(g, { ...legacy, entrant: 'b' }), false);
|
|
g.phase = 5;
|
|
assert.equal(validAmbassadorResume(g, legacy), false);
|
|
});
|
|
void test('BG tail entry inherits the explicit Fremen rider across JSON without current-board or alliance requirements', () => {
|
|
const { g, entry } = fixture();
|
|
for (const p of g.players) {
|
|
p.ally = null;
|
|
p.forces = {};
|
|
p.reserves = 0;
|
|
p.tanks = 20;
|
|
p.hand = [];
|
|
}
|
|
g.storm = 11;
|
|
const before = JSON.stringify({ g, entry });
|
|
for (let i = 0; i < 3; i++)
|
|
assert.equal(
|
|
validAmbassadorResume(
|
|
JSON.parse(JSON.stringify(g)),
|
|
JSON.parse(JSON.stringify(entry)),
|
|
),
|
|
true,
|
|
);
|
|
assert.equal(JSON.stringify({ g, entry }), before);
|
|
delete entry.wormRider;
|
|
assert.equal(validAmbassadorResume(g, entry), false);
|
|
});
|
|
void test('non-worm tail entries require no rider and accept only granted arrival sectors', () => {
|
|
const { g, entry } = fixture();
|
|
g.phase = 5;
|
|
entry.resume = 'none';
|
|
delete entry.wormRider;
|
|
assert.equal(validAmbassadorResume(g, entry), true);
|
|
const polar = { ...entry, territory: 'polar_sink', sector: 0 };
|
|
assert.equal(validAmbassadorResume(g, polar), true);
|
|
assert.equal(validAmbassadorResume(g, { ...polar, sector: 1 }), false);
|
|
assert.equal(
|
|
validAmbassadorResume(g, { ...entry, territory: 'arrakeen', sector: 10 }),
|
|
false,
|
|
);
|
|
assert.equal(validAmbassadorResume(g, { ...entry, wormRider: 'f' }), false);
|
|
const direct = { ...entry };
|
|
delete direct.guildAdvisorOrigin;
|
|
assert.equal(validAmbassadorResume(g, direct), true);
|
|
});
|
|
void test('tail witness rejects changed identity, source event, malformed locations and unsupported original shippers', () => {
|
|
const changes: ((g: Game, entry: AmbassadorResumeEntry) => void)[] = [
|
|
(_, e) => {
|
|
e.guildAdvisorOrigin!.event = e.event;
|
|
},
|
|
(_, e) => {
|
|
e.guildAdvisorOrigin!.event = '';
|
|
},
|
|
(_, e) => {
|
|
e.event = '';
|
|
},
|
|
(_, e) => {
|
|
e.entrant = 'p';
|
|
},
|
|
(_, e) => {
|
|
e.guildAdvisorOrigin!.player = 'b';
|
|
},
|
|
(_, e) => {
|
|
e.guildAdvisorOrigin!.player = 'f';
|
|
},
|
|
(_, e) => {
|
|
e.guildAdvisorOrigin!.player = 'missing';
|
|
},
|
|
(_, e) => {
|
|
e.guildAdvisorOrigin!.sector = 0;
|
|
},
|
|
(_, e) => {
|
|
e.guildAdvisorOrigin!.territory = 'reserves';
|
|
},
|
|
(_, e) => {
|
|
e.sector = 12;
|
|
},
|
|
(_, e) => {
|
|
e.wormRider = 'missing';
|
|
},
|
|
(_, e) => {
|
|
e.wormRider = '';
|
|
},
|
|
(_, e) => {
|
|
delete e.guildAdvisorOrigin;
|
|
},
|
|
(g) => {
|
|
g.phase = 5;
|
|
},
|
|
(g) => {
|
|
g.players.push({ ...g.players[1] });
|
|
},
|
|
(g) => {
|
|
g.players.push({ ...g.players[2] });
|
|
},
|
|
(g) => {
|
|
g.players.push({ ...g.players[3] });
|
|
},
|
|
(_, e) => {
|
|
Object.assign(e, { resume: 'forged' });
|
|
},
|
|
(_, e) => {
|
|
Object.assign(e, { guildAdvisorOrigin: null });
|
|
},
|
|
];
|
|
for (const change of changes) {
|
|
const { g, entry } = fixture();
|
|
change(g, entry);
|
|
const before = JSON.stringify({ g, entry });
|
|
assert.equal(validAmbassadorResume(g, entry), false);
|
|
assert.equal(JSON.stringify({ g, entry }), before);
|
|
}
|
|
});
|
|
void test('historical HMS source does not re-read mobile pointer or concealed custody', () => {
|
|
const { g, entry } = fixture();
|
|
Object.assign(entry, { territory: 'hidden_mobile_stronghold', sector: 0 });
|
|
Object.assign(entry.guildAdvisorOrigin!, {
|
|
territory: 'hidden_mobile_stronghold',
|
|
sector: 0,
|
|
});
|
|
g.mobileStronghold = { location: null };
|
|
assert.equal(validAmbassadorResume(g, entry), true);
|
|
g.mobileStronghold.location = 'hagga_basin:12';
|
|
assert.equal(validAmbassadorResume(g, entry), true);
|
|
});
|