把代入名片从开场白拆到主角设定短码槽,并收紧对话落盘写法。
多开场可各自绑定名片;开玩挂载跳过开场产物并认用户需求类标签。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -18,6 +18,8 @@ describe("dictate play-bind", () => {
|
||||
expect(isDictateStyleBindTag("设计.模仿要点")).toBe(true);
|
||||
expect(isDictateStyleBindTag("设计.叙事指南")).toBe(true);
|
||||
expect(isDictateStyleBindTag("设计.故事推进")).toBe(true);
|
||||
expect(isDictateStyleBindTag("设计.用户需求")).toBe(true);
|
||||
expect(isDictateStyleBindTag("设计.用户约束")).toBe(true);
|
||||
expect(isDictateStyleBindTag("用户.需求")).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
@@ -259,6 +259,36 @@ describe("dictate context", () => {
|
||||
});
|
||||
expect("error" in overwriteBare).toBe(true);
|
||||
|
||||
const openingFamilies = new Set(["设计.开场白", "设计.主角设定"]);
|
||||
const firstOpen = resolveRepeatableInsertTag({
|
||||
tag: "设计.开场白",
|
||||
content: "@玩家推开门。",
|
||||
existingTags: [],
|
||||
repeatableFamilies: openingFamilies,
|
||||
});
|
||||
expect("tag" in firstOpen && firstOpen.tag).toBe("设计.开场白");
|
||||
const overwriteFirst = resolveRepeatableInsertTag({
|
||||
tag: "设计.开场白",
|
||||
content: "@玩家改写第一幕。",
|
||||
existingTags: ["设计.开场白"],
|
||||
repeatableFamilies: openingFamilies,
|
||||
});
|
||||
expect("tag" in overwriteFirst && overwriteFirst.tag).toBe("设计.开场白");
|
||||
const secondOpen = resolveRepeatableInsertTag({
|
||||
tag: "设计.开场白#gate",
|
||||
content: "@玩家在闸机前。",
|
||||
existingTags: ["设计.开场白"],
|
||||
repeatableFamilies: openingFamilies,
|
||||
});
|
||||
expect("tag" in secondOpen && secondOpen.tag).toBe("设计.开场白#gate");
|
||||
const bareOpenConflict = resolveRepeatableInsertTag({
|
||||
tag: "设计.开场白",
|
||||
content: "又写一条",
|
||||
existingTags: ["设计.开场白", "设计.开场白#gate"],
|
||||
repeatableFamilies: openingFamilies,
|
||||
});
|
||||
expect("error" in bareOpenConflict).toBe(true);
|
||||
|
||||
const catalog = await loadModuleCatalog("dialogue/world-simulator");
|
||||
const index = await buildDictateInsertFeedbackIndex({
|
||||
catalog: catalog!,
|
||||
|
||||
@@ -175,6 +175,24 @@ describe("opening-seal", () => {
|
||||
expect(dotted.text).toBe("门外有人喊 @玩家。");
|
||||
});
|
||||
|
||||
it("strips bare 用户角色 meta lines without YAML fence", () => {
|
||||
const bare = splitOpeningDocument(`用户角色.名字: 李宗
|
||||
用户角色.简介: 18cm 男大学生,家境贫穷
|
||||
……@玩家推开宿舍门。
|
||||
`);
|
||||
expect(bare.persona).toEqual({
|
||||
name: "李宗",
|
||||
description: "18cm 男大学生,家境贫穷",
|
||||
});
|
||||
expect(bare.text).toBe("……@玩家推开宿舍门。");
|
||||
|
||||
const onlyMeta = splitOpeningDocument(`用户角色.名字: 李宗
|
||||
用户角色.简介: 男大学生
|
||||
`);
|
||||
expect(onlyMeta.persona?.name).toBe("李宗");
|
||||
expect(onlyMeta.text).toBe("");
|
||||
});
|
||||
|
||||
it("builds table rows from opening variables", () => {
|
||||
const payload = parseOpeningSealPayload(fragment, 0);
|
||||
const doc = openingVariablesToTableDoc(payload!.variables);
|
||||
|
||||
@@ -73,4 +73,39 @@ describe("play-aux view", () => {
|
||||
expect(view.openings[0]?.text).toBe("@玩家站在债契前,堂屋很静。");
|
||||
expect(view.openings[0]?.text).not.toContain("present.v1");
|
||||
});
|
||||
|
||||
it("lists dictate 设计.开场白#slot openings with same-slot 主角设定", () => {
|
||||
const board = new Blackboard();
|
||||
board.write({
|
||||
tag: "设计.开场白#dorm",
|
||||
content: "@玩家推开宿舍门。",
|
||||
source: "agent",
|
||||
});
|
||||
board.write({
|
||||
tag: "设计.开场白#gate",
|
||||
content: "@玩家停在闸机前。",
|
||||
source: "agent",
|
||||
});
|
||||
board.write({
|
||||
tag: "设计.主角设定#dorm",
|
||||
content: JSON.stringify({
|
||||
正文: { 名字: "李宗", 背景: "男大学生", 特殊设定: { 身份: "无", 金手指: "无", 其它: "无" } },
|
||||
}),
|
||||
source: "agent",
|
||||
});
|
||||
board.write({
|
||||
tag: "设计.主角设定#gate",
|
||||
content: JSON.stringify({
|
||||
正文: { 名字: "阿泽", 背景: "社畜", 特殊设定: { 身份: "无", 金手指: "无", 其它: "无" } },
|
||||
}),
|
||||
source: "agent",
|
||||
});
|
||||
const view = buildPlayAuxView(board, { [SLOT_OPENING_SELECTED_INDEX]: 0 });
|
||||
expect(view.openings).toHaveLength(2);
|
||||
expect(view.openings.map((o) => o.slot).sort()).toEqual(["dorm", "gate"]);
|
||||
const dorm = view.openings.find((o) => o.slot === "dorm");
|
||||
const gate = view.openings.find((o) => o.slot === "gate");
|
||||
expect(dorm?.persona?.name).toBe("李宗");
|
||||
expect(gate?.persona?.name).toBe("阿泽");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -155,6 +155,54 @@ describe("protagonist overlay", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers 设计.主角设定 over legacy opening meta", async () => {
|
||||
const {
|
||||
PROTAGONIST_TAG,
|
||||
readSessionProtagonist,
|
||||
ensureProtagonistFromPersona,
|
||||
protagonistFragmentFromPersona,
|
||||
protagonistTagForOpeningSlot,
|
||||
} = await import("../src/persona/protagonist-bind.js");
|
||||
|
||||
const board = new Blackboard();
|
||||
board.write({
|
||||
tag: "设计.开场白",
|
||||
content: `用户角色.名字: 旧名
|
||||
用户角色.简介: 旧简介
|
||||
@玩家推开门。`,
|
||||
source: "agent",
|
||||
});
|
||||
board.write({
|
||||
tag: PROTAGONIST_TAG,
|
||||
content: protagonistFragmentFromPersona({
|
||||
name: "李宗",
|
||||
description: "男大学生",
|
||||
}),
|
||||
source: "agent",
|
||||
});
|
||||
expect(readSessionProtagonist(board)?.name).toBe("李宗");
|
||||
|
||||
const empty = new Blackboard();
|
||||
expect(
|
||||
ensureProtagonistFromPersona(empty, {
|
||||
name: "阿泽",
|
||||
description: "社畜",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(readSessionProtagonist(empty)?.name).toBe("阿泽");
|
||||
expect(
|
||||
ensureProtagonistFromPersona(empty, {
|
||||
name: "另一个",
|
||||
description: "不覆盖",
|
||||
}),
|
||||
).toBe(false);
|
||||
|
||||
expect(protagonistTagForOpeningSlot("设计.开场白#dorm")).toBe(
|
||||
"设计.主角设定#dorm",
|
||||
);
|
||||
expect(protagonistTagForOpeningSlot("设计.开场白")).toBe(PROTAGONIST_TAG);
|
||||
});
|
||||
|
||||
it("overlays opening meta 用户角色 when the user switches to that preset", async () => {
|
||||
const { readSessionProtagonist, overlayPlayPersona } = await import(
|
||||
"../src/persona/protagonist-bind.js"
|
||||
|
||||
Reference in New Issue
Block a user