feat(app): complete playable branch sessions
verify / verify (push) Has been cancelled

This commit is contained in:
Codex
2026-07-28 04:42:40 -04:00
parent 449515fcfe
commit 1f935a3206
26 changed files with 3472 additions and 67 deletions
+186 -3
View File
@@ -8,6 +8,7 @@ import type {
} from "@contracts";
const DEMO_LATENCY_MS = 120;
type DemoPhase = "promise" | "investigate" | "enter" | "return" | "regular";
function nextNodeId(currentNodeId: string): string {
const match = currentNodeId.match(/^(.*?)(\d+)$/);
@@ -17,7 +18,134 @@ function nextNodeId(currentNodeId: string): string {
return `${prefix}${String(Number(digits) + 1).padStart(digits.length, "0")}`;
}
function replyBeats(request: TurnRequest): PresentationBeat[] {
function demoPhase(request: TurnRequest, currentView: PlayerView): DemoPhase {
if (
request.input.includes("天亮前") &&
request.input.includes("回来") &&
!currentView.promises.some((promise) => promise.status === "accepted")
) {
return "promise";
}
if (currentView.history.some((node) => node.label === "进入封锁隧道")) return "return";
if (currentView.knowledge.some((record) => record.id.startsWith("knowledge_maintenance_door"))) {
return "enter";
}
if (currentView.promises.some((promise) => promise.status === "accepted")) return "investigate";
return "regular";
}
function replyBeats(request: TurnRequest, phase: DemoPhase): PresentationBeat[] {
if (phase === "promise") {
return [
{
id: `${request.actionId}_player`,
kind: "action",
speaker: "你",
text: request.input,
visual: null
},
{
id: `${request.actionId}_nana`,
kind: "dialogue",
speaker: "娜娜",
text: "娜娜看了你一会儿,终于松开攥紧外套的手。“好。我等你到天亮。”",
visual: {
character: "nana",
expression: "relieved",
pose: "holding_coat",
scene: null
}
}
];
}
if (phase === "investigate") {
return [
{
id: `${request.actionId}_search`,
kind: "narration",
speaker: null,
text: "你打开旧手电,沿着站台边缘寻找。斜光扫过积水,一道检修门和半张旧车票显了出来。",
visual: {
character: null,
expression: null,
pose: null,
scene: "station_maintenance_door"
}
},
{
id: `${request.actionId}_nana`,
kind: "dialogue",
speaker: "娜娜",
text: "“这是我妹妹的字。门后通向封锁隧道。”",
visual: {
character: "nana",
expression: "startled",
pose: "reaching_out",
scene: null
}
}
];
}
if (phase === "enter") {
return [
{
id: `${request.actionId}_door`,
kind: "narration",
speaker: null,
text: "检修门在肩膀的撞击下松开。手电光照见没过鞋面的水和向深处延伸的脚印。",
visual: {
character: null,
expression: null,
pose: null,
scene: "sealed_tunnel"
}
},
{
id: `${request.actionId}_nana`,
kind: "dialogue",
speaker: "娜娜",
text: "“我留在这里。你答应过会回来,所以我等。”",
visual: {
character: "nana",
expression: "determined",
pose: "at_door",
scene: null
}
}
];
}
if (phase === "return") {
return [
{
id: `${request.actionId}_return`,
kind: "narration",
speaker: null,
text: "天色发白前,你重新推开检修门。娜娜仍坐在原处。",
visual: {
character: "nana",
expression: "disbelieving",
pose: "waiting",
scene: "station_before_dawn"
}
},
{
id: `${request.actionId}_nana`,
kind: "dialogue",
speaker: "娜娜",
text: "“你回来了。那我也会把剩下的事告诉你。”",
visual: {
character: "nana",
expression: "relieved",
pose: "lowered_guard",
scene: null
}
}
];
}
if (request.intent === "continue") {
return [
{
@@ -78,8 +206,58 @@ export async function submitDemoTurn(
const committedNodeId = nextNodeId(currentView.nodeId);
const previousHistory = currentView.history.map((node) => ({ ...node, isCurrent: false }));
const beats = replyBeats(request);
const phase = demoPhase(request, currentView);
const beats = replyBeats(request, phase);
const finalVisual = [...beats].reverse().find((beat) => beat.visual)?.visual;
const historyLabel = {
promise: "天亮前的许诺",
investigate: "检修门的线索",
enter: "进入封锁隧道",
return: "天亮前归来",
regular: request.intent === "continue" ? "雨声中的停顿" : "回应娜娜"
}[phase];
const promises =
phase === "promise"
? [
...currentView.promises,
{
id: `promise_return_before_dawn_${request.actionId}`,
content: "天亮前一定回来",
status: "accepted" as const,
weight: "major" as const
}
]
: phase === "return"
? currentView.promises.map((promise) =>
promise.status === "accepted" ? { ...promise, status: "fulfilled" as const } : promise
)
: currentView.promises;
const knowledge =
phase === "investigate"
? [
...currentView.knowledge,
{
id: `knowledge_maintenance_door_${request.actionId}`,
title: "封锁隧道的检修门",
summary: "旧站台下方的检修门通向封锁隧道,妹妹留下的车票指向四点十七分。",
certainty: "confirmed" as const
}
]
: currentView.knowledge;
const inventory =
phase === "investigate"
? [
...currentView.inventory,
{
instanceId: `item_half_ticket_${request.actionId}`,
name: "半张旧车票",
description: "受潮的车票背面写着“四点十七分,检修线”。",
quantity: 1,
placement: "bag" as const,
condition: "damp"
}
]
: currentView.inventory;
return {
committedNodeId,
@@ -89,13 +267,18 @@ export async function submitDemoTurn(
characterExpression: finalVisual?.expression ?? currentView.characterExpression,
characterPose: finalVisual?.pose ?? currentView.characterPose,
beats: [...currentView.beats, ...beats].slice(-8),
promises,
knowledge,
inventory,
suggestions: phase === "return" ? [] : currentView.suggestions,
canContinue: phase !== "return",
history: [
...previousHistory,
{
id: committedNodeId,
parentId: currentView.nodeId,
branchId: currentView.branchId,
label: request.intent === "continue" ? "雨声中的停顿" : "回应娜娜",
label: historyLabel,
isCurrent: true
}
]