feat: #80 + #81 alliances & space program — 13th & 14th abilities (v0.45.0)
CI / build + unit + e2e (pull_request) Successful in 2m20s
CI / build + unit + e2e (pull_request) Successful in 2m20s
#80: TurnResult.alliances — form/break durable mutual-defense pacts (GameState.alliances; pair relation → ally/neutral, an active war overrides). #81: TurnResult.space — launch/deorbit satellites (GameState.satellites; +1 stability per active satellite per turn). Both appended last to the ABILITIES registry (entries 13 & 14, after contested); durable in SQLite via the stateFields loop, mirrored to regionStats, stats re-derived. System + user prompt goldens updated; 738 unit tests passing.
This commit is contained in:
@@ -173,6 +173,10 @@ const state: GameState = {
|
||||
seaZones: [],
|
||||
// (v0.44.0, #66) no contested regions on a fresh state.
|
||||
contested: [],
|
||||
// (v0.45.0, #80) no alliances on a fresh state.
|
||||
alliances: [],
|
||||
// (v0.45.0, #81) no satellites on a fresh state.
|
||||
satellites: [],
|
||||
};
|
||||
|
||||
// --- buildUserPrompt arguments: every optional branch exercised. ---
|
||||
@@ -254,6 +258,8 @@ function fullDefaultTurn(): TurnResult {
|
||||
migration: { ops: [] },
|
||||
sea: { control: [] },
|
||||
contested: { open: [], resolve: [] },
|
||||
alliances: { form: [], break: [] },
|
||||
space: { launch: [], deorbit: [] },
|
||||
markers: { add: [], remove: [] },
|
||||
};
|
||||
}
|
||||
@@ -285,6 +291,10 @@ function meaningfulFor(field: AbilityField): TurnResult[AbilityField] {
|
||||
return { control: [{ zone: 'pacific', by: 'a1' }] };
|
||||
case 'contested':
|
||||
return { open: [{ regionKey: 'b-r1', parties: ['a1', 'b2'] }], resolve: [] };
|
||||
case 'alliances':
|
||||
return { form: [{ a: 'a1', b: 'b2' }], break: [] };
|
||||
case 'space':
|
||||
return { launch: [{ by: 'a1', regionKey: 'a-r1', type: 'recon' }], deorbit: [] };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,13 +313,15 @@ describe('ABILITIES registry (v0.40.0, #67)', () => {
|
||||
'migration',
|
||||
'sea',
|
||||
'contested',
|
||||
'alliances',
|
||||
'space',
|
||||
]);
|
||||
});
|
||||
|
||||
it('lists 12 distinct fields, each a key of TurnResult', () => {
|
||||
it('lists 14 distinct fields, each a key of TurnResult', () => {
|
||||
const fields = ABILITIES.map((a) => a.field);
|
||||
expect(fields).toHaveLength(12);
|
||||
expect(new Set(fields).size).toBe(12);
|
||||
expect(fields).toHaveLength(14);
|
||||
expect(new Set(fields).size).toBe(14);
|
||||
const all = fullDefaultTurn();
|
||||
for (const f of fields) expect(f in all).toBe(true);
|
||||
});
|
||||
@@ -370,7 +382,7 @@ describe('ABILITIES registry (v0.40.0, #67)', () => {
|
||||
it('declares exactly the durable GameState fields the db migration must default', () => {
|
||||
const owned: string[] = [];
|
||||
for (const a of ABILITIES) if (a.stateFields) owned.push(...a.stateFields);
|
||||
expect([...owned].sort()).toEqual(['contested', 'embargoes', 'industry', 'seaZones', 'trades', 'wars']);
|
||||
expect([...owned].sort()).toEqual(['alliances', 'contested', 'embargoes', 'industry', 'satellites', 'seaZones', 'trades', 'wars']);
|
||||
});
|
||||
|
||||
it('composes: a hypothetical extra ability can be appended and every uniform loop still runs', () => {
|
||||
@@ -395,9 +407,9 @@ describe('ABILITIES registry (v0.40.0, #67)', () => {
|
||||
'custom',
|
||||
]);
|
||||
// every uniform loop runs over all N abilities, the appended one included:
|
||||
expect(extended.map((a) => a.schemaLine())).toHaveLength(13);
|
||||
expect(extended.map((a) => a.constraint())).toHaveLength(13);
|
||||
expect(extended[12].schemaLine()).toBe(' "custom": [],');
|
||||
expect(extended.map((a) => a.schemaLine())).toHaveLength(15);
|
||||
expect(extended.map((a) => a.constraint())).toHaveLength(15);
|
||||
expect(extended[14].schemaLine()).toBe(' "custom": [],');
|
||||
});
|
||||
|
||||
it('the engine still re-exports every name its callers import from it', () => {
|
||||
|
||||
Reference in New Issue
Block a user