batched round: queue actions/diplomacy, jump forward commits one turn (v0.2.0)
CI / build + unit + e2e (pull_request) Successful in 1m51s

This commit is contained in:
2026-10-11 14:36:35 +00:00
parent 3297658c81
commit 9bce14254d
16 changed files with 563 additions and 246 deletions
+35 -30
View File
@@ -2,11 +2,12 @@
// sets LLM_STUB=1) so the whole loop is reproducible with no live model. It
// speaks the OpenAI chat-completions wire shape, so it plugs in as a Transport.
import { createLlmClient, type ChatMessage, type Transport } from './llm';
import { addDays, clampStat, type TurnResult } from '../shared/protocol';
import { addDays, clampStat, type TurnEventSpec, type TurnResult } from '../shared/protocol';
import type { RoundItem } from '../shared/types';
export const ADVISOR_MARKER = '<<ADVISOR>>';
/** The parsed body of the user message the engine sends (see engine.buildPrompt). */
/** The parsed body of the user message the engine sends (see engine.buildUserPrompt). */
export interface StubPayload {
state?: {
turn: number;
@@ -14,8 +15,8 @@ export interface StubPayload {
playerNationId: string;
nations: Record<string, { stats: Record<string, number> }>;
};
action?: string | null;
chat?: { to: string; message: string } | null;
/** The raw pending round the engine emitted (one entry per queued item). */
pending?: RoundItem[];
}
function truncate(s: string, n: number): string {
@@ -24,8 +25,12 @@ function truncate(s: string, n: number): string {
/**
* Pure deterministic TurnResult for a parsed payload. Bumps the player nation's
* military by 5 each turn (clamped to 0–100) and emits one event, so a caller can
* predict the exact next state for assertions.
* military by 5 each turn (clamped to 0–100) and emits one event per queued
* round item (action → military/success, diplomacy → diplomatic/partial), or a
* single political "The world moves on" event when the round is empty — so a
* caller can predict the exact next state for assertions. It always sets the
* player's military to (current + 5) and allies the player with the first other
* nation, advancing the date by 2 days.
*/
export function deterministicTurn(p: StubPayload): TurnResult {
const state = p.state;
@@ -35,36 +40,36 @@ export function deterministicTurn(p: StubPayload): TurnResult {
const otherId = state
? (Object.keys(state.nations).find((id) => id !== pid) ?? '')
: 'us';
const items = p.pending ?? [];
const events = p.action
? [
{
nation: pid,
type: 'military' as const,
title: `Action: ${truncate(p.action, 60)}`,
text: 'Your move takes effect across the region.',
outcome: 'success' as const,
},
]
: p.chat
const events: TurnEventSpec[] =
items.length === 0
? [
{
nation: pid,
type: 'diplomatic' as const,
title: 'Diplomatic outreach',
text: 'Your message is received by the target nation.',
outcome: 'partial' as const,
type: 'political',
title: 'The world moves on',
text: 'Weeks pass without a decisive event.',
outcome: 'neutral',
},
]
: [
{
nation: pid,
type: 'political' as const,
title: 'The world moves on',
text: 'Weeks pass; the great powers posture and rearm quietly.',
outcome: 'neutral' as const,
},
];
: items.map((it) =>
it.kind === 'action'
? {
nation: pid,
type: 'military' as const,
title: `Action: ${truncate(it.text, 60)}`,
text: 'Your move takes effect across the region.',
outcome: 'success' as const,
}
: {
nation: pid,
type: 'diplomatic' as const,
title: 'Diplomatic outreach',
text: 'Your message is received.',
outcome: 'partial' as const,
},
);
return {
date: addDays(state?.date ?? '1939-09-01', 2),