主世界层直接写正文,转述与旁观改读用户展示输出。

去掉裁决包主路径,让无转述时可直出终稿、有转述时再改写镶壳。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-10 02:18:14 +08:00
co-authored by Cursor
parent d65ebbce8b
commit 3d720f23fe
27 changed files with 290 additions and 216 deletions
+3 -3
View File
@@ -498,7 +498,7 @@ const SYNTH_FALLBACK_CONTEXT: Record<
"设计.变量控制上下文",
],
dynamic: [
"运行.本轮.裁决",
"输出.用户展示",
"用户.最新输入",
"变量.当前",
"上下文.旁观.状态摘要",
@@ -528,12 +528,12 @@ const SYNTH_FALLBACK_CONTEXT: Record<
"设计.正文组成",
"设计.监控栏",
],
dynamic: ["运行.本轮.裁决"],
dynamic: ["输出.用户展示"],
skipHistory: true,
},
"role-decide": {
static: [],
dynamic: ["用户.最新输入", "运行.本轮.裁决"],
dynamic: ["用户.最新输入", "输出.用户展示"],
},
};
+1 -2
View File
@@ -32,7 +32,6 @@ import {
} from "./context-order.js";
import type { ContextSegmentDef } from "./context-segments.js";
import { PLAY_VISIBLE_BODY_INSTRUCTION } from "./present-packet.js";
import { isNarratorEnabled } from "./play-slots.js";
type WorkerTemplateDoc = {
id?: string;
@@ -223,7 +222,7 @@ export function buildDeclaredWorkerSkill(params: {
const visibleBodyRule =
id === "narrator" ||
id === "round-present" ||
(id === "world-simulator" && !isNarratorEnabled(params.workerSet?.play_slots))
id === "world-simulator"
? PLAY_VISIBLE_BODY_INSTRUCTION
: "";
const excerptWithRule = [excerpt, visibleBodyRule].filter(Boolean).join("\n\n");
+2 -2
View File
@@ -114,6 +114,6 @@ export const GM_CHANCE_HARNESS_INSTRUCTION = `
## 机遇工具(游玩期)
需要真随机(掷骰/比点/抽签/加权抽取)时:调用工具 \`chance\`,在 \`requests\` 数组中**一次性**列出本步全部随机需求(每项必须有唯一 \`id\`)。
禁止口算或编造随机结果。收到工具结果后,再在 settlement 的 \`resolved\` 等处引用对应 \`id\` 写出后果。
无随机需求时:直接输出 settlement.v1 JSON,不要调用工具。
禁止口算或编造随机结果。收到工具结果后,在本轮 **Markdown 正文**里演绎对应 \`id\` 的后果(写进场面,不要交 JSON 裁决包)。
无随机需求时:直接输出故事正文,不要调用工具。
`;
+18 -15
View File
@@ -54,12 +54,13 @@ export const PLAY_SLOT_META: Record<
gm: {
label: "主世界层",
purpose:
"读真值与 Progressive 投影并推进世界;无独立转述时直接写用户可见正文,有转述时出裁决包。",
"读真值与 Progressive 投影并推进世界;直接写本轮故事正文(Markdown);无转述时原文即终稿。",
defaultAcceptance: "review",
},
narrator: {
label: "叙事转述",
purpose: "只读裁决包 + 文风常驻,输出用户可见正文(可选;默认关)。",
purpose:
"读主世界正文,提取/改写/镶壳为用户终稿;可补残稿,不改剧情(可选;默认关)。",
defaultAcceptance: "review",
},
perspective: {
@@ -191,37 +192,39 @@ export function workerEntryForSlot(
): WorkerSetEntry {
const meta = PLAY_SLOT_META[id];
const ref = refForSlot(slots, id);
const soloGm = id === "gm" && !isNarratorEnabled(slots);
const withNarrator = isNarratorEnabled(slots);
const soloGm = id === "gm" && !withNarrator;
return {
ref,
name: meta.label,
role: id,
duty: soloGm
? "读真值与投影,推进世界,并直接写出用户可见正文(无独立转述层,原文即终稿)。"
: meta.purpose,
when:
duty:
id === "gm"
? soloGm
? "每轮用户输入后上场;直接交用户可见正文"
: "每轮用户输入后首先上场(主 LLM / 裁决权威)"
? "读真值与投影,推进世界,并直接写出用户可见正文(无独立转述层,原文即终稿)。"
: "读真值与投影,推进世界,写出本轮故事正文(Markdown);文风润色与壳交给转述。"
: meta.purpose,
when:
id === "gm"
? "每轮用户输入后上场;直接交故事正文"
: id === "perspective"
? "强信息隔离且本轮需要该角色独立反应时(主世界层之后)"
: id === "narrator"
? "主世界层裁决包就绪后"
: "转述完成后;表维护合并进变量.当前,供下一轮主世界层读取",
? "主世界层本轮正文就绪后"
: "终稿写入后;表维护合并进变量.当前,供下一轮主世界层读取",
rationale:
id === "gm"
? soloGm
? "默认少槽:主世界层兼呈现,避免转述再改写一遍"
: "有独立转述时:主世界层出裁决,正文交给转述"
: "有独立转述时:主世界专注剧情与扮演;转述再提取/改写/镶壳"
: id === "perspective"
? "知密内容不能进主世界层上下文"
: id === "narrator"
? "用户可见正文与裁决分离,避免文风与规则互相挤压"
? "文风与前端壳与世界推演分离,避免互相挤压"
: "表与规则补充延后执行,改表结果作用于下一轮而非本轮重裁",
acceptance:
id === "gm" && isNarratorEnabled(slots) ? "continue" : meta.defaultAcceptance,
outputs: soloGm ? ["输出.用户展示"] : undefined,
id === "gm" && withNarrator ? "continue" : meta.defaultAcceptance,
outputs: id === "gm" ? ["输出.用户展示"] : undefined,
invocation: "turn",
};
}
+38 -7
View File
@@ -192,23 +192,40 @@ export function stripPresentSourceFences(raw: string): string {
}
export const PLAY_VISIBLE_BODY_INSTRUCTION = [
"用户可见正文(present.v1 的 blocks.body,或纯 Markdown 主读)默认 **1000~2000 字**(按汉字计);以「设计.监控栏/回复呈现」已钉字数为准。",
"用户可见正文(主世界层:纯 Markdown;转述:present.v1 的 blocks.body,或纯 Markdown 主读)默认 **1000~2000 字**(按汉字计);以「设计.监控栏/回复呈现」已钉字数为准。",
"采用完整长自然段叙述,不要单句成段。",
"不要写成几句气泡短信就结束,除非用户明确要求极短。",
"禁止把 tag 名(如「输出.用户展示」)、压缩摘要、过程日志写进正文。",
].join("\n");
/** 游玩期把用户可见终稿写出的执行单元(有转述用转述;无则主世界层直接交原文) */
export function isPlayPresentWorker(
/**
* 游玩期走 present.v1 结构化投递的执行单元(转述 / 回合陈述)。
* 主世界层不在此列:始终自由写 Markdown 正文。
*/
export function isPlayPresentWorker(workerId: string): boolean {
const id = workerId.trim();
return id === "narrator" || id === "round-present";
}
/**
* 本轮最终用户可见正文的执行单元(写入对话.历史 / 空产物兜底)。
* 无转述:主世界层;有转述:转述(主世界草稿可被覆盖,不进历史)。
*/
export function isPlayFinalVisibleWorker(
workerId: string,
opts?: { narratorEnabled?: boolean },
): boolean {
const id = workerId.trim();
if (id === "narrator" || id === "round-present") return true;
if (id === "world-simulator" && opts?.narratorEnabled === false) return true;
if (id === "world-simulator" && opts?.narratorEnabled !== true) return true;
return false;
}
/** 游玩主世界层:自由 Markdown 正文(程序可事后落入 present 壳,模型不交 JSON) */
export function isPlayGmBodyWorker(workerId: string): boolean {
return workerId.trim() === "world-simulator";
}
/** 按能力探测走 json_schema / forced_tool 时用的 present.v1 形状(全 required,可 strict) */
export const PRESENT_JSON_SCHEMA: Record<string, unknown> = {
type: "object",
@@ -243,21 +260,35 @@ export const PRESENT_JSON_SCHEMA: Record<string, unknown> = {
},
};
/** 游玩主世界层:直接写 Markdown,禁止 JSON 裁决包 */
export function playGmBodyOutputInstruction(): string {
return `
---
## 运行时输出协议(主世界正文)
直接输出本轮 **Markdown / 自然语言故事正文**(写入「输出.用户展示」)。
不要输出 JSON、不要交 settlement/裁决包、不要套 present.v1 外壳。
重心在场面推进与人物扮演;残稿也可以交,有转述时会再处理。
不要写追问、askUser、自评或「可验收产物」。`;
}
/** 游玩转述:按探测到的 structured 投递写出 present.v1,不要创作追问 */
export function playPresentOutputInstruction(shell: PresentShellId): string {
return `
---
## 运行时输出协议(游玩正文)
## 运行时输出协议(游玩终稿)
本步产物是给用户看的终稿。程序按已探测的模型能力用 schema / tool / json_object 投递,你输出 **一个 present.v1 对象**:
本步读主世界层已写正文,产出给用户看的终稿。程序按已探测的模型能力用 schema / tool / json_object 投递,你输出 **一个 present.v1 对象**:
{"schema":"present.v1","shell":"${shell}","blocks":{"body":"用户可读正文","monitor":"","header":"","footer":"","aside":""},"meta":{"suggested_actions":[]}}
\`shell\` 必须是 \`${shell}\`。只填该壳已有区域,没有的键留空字符串。
prose 壳也可直接输出 Markdown,程序会落入 body。
材料不够也先写一版场面。不要写追问、askUser、自评或「可验收产物」。`;
可补全残缺叙述与版式,但不得改剧情事实。不要写追问、askUser、自评或「可验收产物」。`;
}
export function stringifyPresentPacket(packet: PresentPacket): string {
+3 -2
View File
@@ -1,6 +1,7 @@
/**
* 主世界层 → 叙事转述的裁决包(settlement / direction packet)。
* 存黑板 tag:运行.本轮.裁决
* 遗留:settlement.v1 裁决包(运行.本轮.裁决)。
* 现行游玩管线:主世界层直接写 Markdown 正文;转述读正文;旁观维护读正文出 maintain.v1。
* 仍保留解析/合并,兼容旧存档或手工写入的 variable_changes。
*/
import {
createTableFromValues,
+4 -3
View File
@@ -221,6 +221,7 @@ const DEFAULT_WORKER_CONTRACTS: Record<
"设计.变量控制上下文",
],
dynamic: [
"输出.用户展示",
"用户.最新输入",
"变量.当前",
"上下文.旁观.状态摘要",
@@ -249,12 +250,12 @@ const DEFAULT_WORKER_CONTRACTS: Record<
"运行.本轮.旁观",
],
},
outputs: ["输出.用户展示", "运行.本轮.裁决", "运行.事件流", "运行.本轮.变量变更"],
outputs: ["输出.用户展示", "运行.事件流"],
},
"variable-update": {
context: {
static: ["设计.变量设计与更新规则"],
dynamic: ["运行.本轮.裁决", "变量.当前"],
dynamic: ["输出.用户展示", "变量.当前"],
},
outputs: ["运行.本轮.变量变更", "变量.当前"],
},
@@ -267,7 +268,7 @@ const DEFAULT_WORKER_CONTRACTS: Record<
"设计.正文组成",
"设计.监控栏",
],
dynamic: ["运行.本轮.裁决", "用户.最新输入", "变量.当前"],
dynamic: ["输出.用户展示", "用户.最新输入", "变量.当前"],
},
outputs: ["输出.用户展示"],
},