This commit is contained in:
+186
-3
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user