feat(#52): dynamic scenario client (form → review → finalize) + e2e

Additive 'Create scenario' path (v0.31): the home screen's create-scenario
card opens a ScenarioForm (pick one of 3 light era templates + a free-text
description + an editable, era-prefilled start year) whose 'Generate world'
posts the draft request; the CheckpointReview screen shows the generated
world (per-nation stats/territory/relations + backstory) and the advisor's
checkpoint list as an approve/remove checklist, then the player picks a
nation to lead and finalizes into a saved, playable game. Both LLM steps ride
the scenario client (deterministic stub under LLM_STUB=1); a failure toasts
and stays on the current screen. The turn stub and static presets are
untouched. New e2e covers the full deterministic flow (near-future 2035,
remove checkpoint 3, lead Boreas, finalize, one empty jump) and the 3-template
manifest.
This commit is contained in:
2026-10-15 10:21:53 +00:00
parent 832957c635
commit 396702b9da
7 changed files with 971 additions and 3 deletions
+27
View File
@@ -14,6 +14,13 @@ import type {
RemoveQueueItemResponse,
TurnResponse,
} from '@shared/protocol';
import type {
ScenarioCommitRequest,
ScenarioCommitResponse,
ScenarioDraftRequest,
ScenarioDraftResponse,
TemplateMeta,
} from '@shared/scenario';
async function req<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(path, {
@@ -95,4 +102,24 @@ export const api = {
req<{ regions: Record<string, { color: string; owner: string; ownerId: string }> }>(
`/api/games/${id}/map`,
),
// --- Dynamic scenario generation (v0.31, #52) ---------------------------
// The additive "Create scenario" path: a two-step LLM flow (design a draft
// world + checkpoint list, then finalize it into a saved, playable game).
// The era-template manifest for the form's cards (id / name / year / blurb).
listScenarioTemplates: () => req<{ templates: TemplateMeta[] }>('/api/scenarios/templates'),
// Step 1: design a draft world + an advisor checkpoint list from an era
// template + a free-text description. An LLM call — can be slow; a 502
// means the model is down or returned nothing usable (the caller toasts).
draftScenario: (body: ScenarioDraftRequest) =>
req<ScenarioDraftResponse>('/api/scenarios/draft', {
method: 'POST',
body: JSON.stringify(body),
}),
// Step 2: finalize the approved draft (constrained to the approved
// checkpoints) into a new, playable game and save it. Returns the id.
commitScenario: (body: ScenarioCommitRequest) =>
req<ScenarioCommitResponse>('/api/scenarios/commit', {
method: 'POST',
body: JSON.stringify(body),
}),
};