feat(#57): hybrid war — TurnResult.hybrid ops (spy/sabotage/assassinate/cyber/economic/information/proxy) with exposure backfire
CI / build + unit + e2e (pull_request) Successful in 2m7s

This commit is contained in:
2026-10-15 16:42:27 +00:00
parent f45c01b8d6
commit 8eeb6df284
7 changed files with 868 additions and 22 deletions
+72
View File
@@ -305,6 +305,64 @@ export function isMarkerType(t: unknown): t is MarkerType {
return typeof t === 'string' && (MARKER_TYPES as string[]).includes(t);
}
// --- Hybrid war (v0.34.0, #57): non-kinetic operations ---------------------
//
// The LLM can run covert, non-kinetic operations each turn: spies, sabotage,
// assassination attempts, cyberattacks, economic pressure, disinformation, and
// proxy support. Unlike kinetic conquest (territory), these never move borders
// — they dip a target's stats or sour a relation. The LLM declares an intended
// outcome (`success` or `exposed`); the engine is fully deterministic (no
// hidden randomness) and acts only on that declaration: a successful op applies
// its small, per-type effect, and an "exposed" op backfires (no effect, the
// actor's relation with the target sours by one step, and an auto
// diplomatic/failure event is emitted).
/** The seven covert operation types a hybrid op can be (v0.34.0, #57). */
export type HybridOpType =
| 'spy'
| 'sabotage'
| 'assassinate'
| 'cyber'
| 'economic'
| 'information'
| 'proxy';
/** All hybrid op types, in canonical order (the LLM contract + validator). */
export const HYBRID_OP_TYPES: HybridOpType[] = [
'spy',
'sabotage',
'assassinate',
'cyber',
'economic',
'information',
'proxy',
];
/**
* (v0.34.0, #57) One covert (non-kinetic) operation the LLM may run in a turn
* (`TurnResult.hybrid.ops`). `from` is the acting nation (its id or a unique
* name — resolved by the engine's `resolveNationRef`). `target` is EITHER a
* nation (id or unique name) or a specific region key; a region target hits
* that region's sole owner (a region with zero or 2+ owners can't be
* targeted). `detail` is a short narrative (the LLM may also narrate the op in
* `events`). `outcome` is the LLM's *intended* result: `success` (the default)
* applies the op's deterministic effect, while `exposed` backfires it — the
* effect is NOT applied, the actor's relation with the target sours by one
* step, and an auto diplomatic/failure event is emitted.
*/
export interface HybridOp {
/** The operation type (see {@link HybridOpType}). */
type: HybridOpType;
/** The acting nation's id or current name (resolved by `resolveNationRef`). */
from: string;
/** The target: a nation's id/name, or a region key (hits that region's owner). */
target: string;
/** Short narrative detail (optional). */
detail?: string;
/** The LLM's intended result; `exposed` backfires the op. Defaults to success. */
outcome?: 'success' | 'exposed';
}
/** The exact JSON object the world-simulator LLM must return for a turn. */
export interface TurnResult {
date: string;
@@ -367,6 +425,20 @@ export interface TurnResult {
* aims) in the user payload's `wars` field, so it can `end` by id.
*/
wars: { declare: WarDeclareOp[]; end: WarEndOp[] };
/**
* (v0.34.0, #57) Covert, non-kinetic ("hybrid") operations this turn — spies,
* sabotage, assassination attempts, cyberattacks, economic pressure,
* disinformation, and proxy support. Unlike `nationOps` (player-confirmed
* proposals) these are applied directly by the turn, like territory / state
* changes: each op's deterministic per-type effect is applied by
* `applyTurnResult` (a small stat dip on the target's regions, or a
* relation nudge toward hostile for `information` / `proxy`); an `exposed`
* op backfires (no effect, the actor's relation with the target sours by
* one step, and an auto diplomatic/failure event is emitted). They never
* move borders — sole ownership is untouched. Required (default
* `{ ops: [] }`).
*/
hybrid: { ops: HybridOp[] };
/**
* (v0.27, #44) Map-marker ops this turn. `add` places new named markers
* (the engine positions each at its region's centroid + a small