feat: #65 sea control — 11th ability (v0.43.0)
CI / build + unit + e2e (pull_request) Successful in 2m33s
CI / build + unit + e2e (pull_request) Successful in 2m33s
Adds the sea ability: a fixed set of 12 named sea zones with sole-control semantics, contest-by-higher-military, and a deterministic per-turn +1 military/+1 economy bonus for each claimed zone. Durable (owns GameState.seaZones), additive migration, stub-inert (e2e unaffected). Fixes #65 Design note: Gitea #65 sketched a free-form 'naval' layer; the implemented approved design is this richer 'sea' layer with a fixed zone set.
This commit is contained in:
+27
-1
@@ -16,6 +16,7 @@ import type {
|
||||
RegionResources,
|
||||
RegionStats,
|
||||
RoundItem,
|
||||
SeaZoneClaim,
|
||||
StatKey,
|
||||
TradeAgreement,
|
||||
} from '../shared/types';
|
||||
@@ -104,6 +105,14 @@ export {
|
||||
MIGRATION_SOURCE_EFFECTS,
|
||||
MIGRATION_DESTINATION_EFFECTS,
|
||||
LARGE_INFLUX_STABILITY_DIP,
|
||||
validateSeaOps,
|
||||
applySeaControl,
|
||||
applySeaZoneBonuses,
|
||||
SEA_ZONE_BONUS,
|
||||
SEA_ZONES,
|
||||
SEA_ZONE_IDS,
|
||||
isSeaZoneId,
|
||||
seaZoneName,
|
||||
} from './abilities';
|
||||
|
||||
// --- The LLM's getNeighbours tool (v0.21, #16 P2: contiguous region transfers) ---
|
||||
@@ -376,6 +385,14 @@ export function buildUserPrompt(
|
||||
* key on any nation) when undefined.
|
||||
*/
|
||||
nuclear?: Record<string, boolean>,
|
||||
/**
|
||||
* (v0.43.0, #65) The game's current sea-zone control (each a `{ zone, by }`
|
||||
* pair of the fixed sea zones and their controlling nation id). Emitted
|
||||
* verbatim (even when empty) so the model knows which of the 12 fixed sea
|
||||
* zones each nation currently controls and can re-claim or contest them via
|
||||
* `TurnResult.sea`. Omitted entirely when undefined.
|
||||
*/
|
||||
seaZones?: SeaZoneClaim[],
|
||||
): string {
|
||||
const player = state.nations[state.playerNationId];
|
||||
const playerName = player?.name ?? state.playerNationId;
|
||||
@@ -485,6 +502,7 @@ export function buildUserPrompt(
|
||||
embargoes,
|
||||
nationResources,
|
||||
industryProjects,
|
||||
seaZones,
|
||||
};
|
||||
Object.assign(payload, ABILITIES[0].payload(upCtx)); // takeovers → takeoverRisks
|
||||
if (noMansLand !== undefined) payload.unowned = noMansLand;
|
||||
@@ -493,6 +511,7 @@ export function buildUserPrompt(
|
||||
Object.assign(payload, ABILITIES[2].payload(upCtx)); // hybrid → relations
|
||||
Object.assign(payload, ABILITIES[3].payload(upCtx)); // economy → trades, embargoes
|
||||
Object.assign(payload, ABILITIES[4].payload(upCtx)); // industry → resources, industry
|
||||
Object.assign(payload, ABILITIES[10].payload(upCtx)); // sea → seaZones (always emitted)
|
||||
return JSON.stringify(payload, null, 2);
|
||||
}
|
||||
|
||||
@@ -633,6 +652,7 @@ export function validateTurnResult(
|
||||
ideology: { changes: [] },
|
||||
disasters: { events: [] },
|
||||
migration: { ops: [] },
|
||||
sea: { control: [] },
|
||||
markers: { add: [], remove: [] },
|
||||
};
|
||||
|
||||
@@ -723,7 +743,7 @@ export function validateTurnResult(
|
||||
// list for the player to confirm/reject.
|
||||
result.nationOps = validateNationOps(o.nationOps);
|
||||
|
||||
// The 9 per-feature LLM abilities (v0.40.0, #67): each one's validate call
|
||||
// The 11 per-feature LLM abilities (v0.40.0, #67): each one's validate call
|
||||
// is owned by its Ability module in the ABILITIES registry. The field's raw
|
||||
// value is passed through a.validate() which returns the validated field shape.
|
||||
for (const a of ABILITIES) {
|
||||
@@ -917,6 +937,9 @@ export function applyTurnResult(
|
||||
// (v0.36, #59) active industry projects; a legacy/corrupt clone without the
|
||||
// field starts empty (the turn then only ever adds to it).
|
||||
next.industry = next.industry ?? [];
|
||||
// (v0.43.0, #65) sea-zone control; a legacy/corrupt clone without the field
|
||||
// starts empty (the turn then upserts this turn's claims into it).
|
||||
next.seaZones = next.seaZones ?? [];
|
||||
const actingTurn = state.turn; // this action resolves the current turn
|
||||
next.turn = state.turn + 1; // then we advance to the next turn to play
|
||||
next.date = isDateLike(result.date) ? result.date : addDays(state.date, 2);
|
||||
@@ -1594,6 +1617,9 @@ export async function runTurn(opts: {
|
||||
state.industry ?? [],
|
||||
// (v0.37.0, #60) per-nation nuclear capability (flag or rocket marker).
|
||||
nuclearCapability,
|
||||
// (v0.43.0, #65) the current sea-zone control (always emitted — even when
|
||||
// empty — so the model knows who controls which of the fixed sea zones).
|
||||
state.seaZones ?? [],
|
||||
);
|
||||
const nudge =
|
||||
'Your previous output was invalid. Respond with ONLY the JSON object described above for this batch — no prose, no fences.';
|
||||
|
||||
Reference in New Issue
Block a user