Reorder play pipeline: gm first, auditor last.
Main world layer runs before narrator and deferred maintenance. Round-scoped settlement/maintain tags are archived at turn start so prior auditor table merges feed the next gm read. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+17
-16
@@ -1,7 +1,7 @@
|
|||||||
# 主世界层 与 旁观维护:怎么配合
|
# 主世界层 与 旁观维护:怎么配合
|
||||||
|
|
||||||
> 用白话说明「DM」和「副 LLM(旁观)」谁先谁后、谁干什么。
|
> 用白话说明「DM」和「副 LLM(旁观)」谁先谁后、谁干什么。
|
||||||
> 实现上当前管线是:`旁观 →(可选角色视角)→ 主世界层 → 转述`。
|
> 实现上当前管线是:`主世界层 →(可选角色视角)→ 转述 → 旁观维护`。
|
||||||
|
|
||||||
## 1. 先说清楚两个角色
|
## 1. 先说清楚两个角色
|
||||||
|
|
||||||
@@ -34,26 +34,27 @@ DM 出裁决包(含变量变更)→ 旁观只动表/触发生成 → 程序
|
|||||||
|
|
||||||
## 3. 本仓库采用的(推荐)
|
## 3. 本仓库采用的(推荐)
|
||||||
|
|
||||||
**既不是甲也不是乙的「整段返工」,而是:旁观轻扫 → DM 拍板 →(程序合并旁观的改表)→ 转述。**
|
**采用做法乙:主世界层先拍板 → 转述 → 旁观在回合末改表(合并结果供下一轮主世界层读取)。**
|
||||||
|
|
||||||
```text
|
```text
|
||||||
每轮:
|
每轮:
|
||||||
1. 旁观:看规则合同 + 当前表 + 玩家输入
|
1. 主世界层:读上一轮合并后的 变量.当前 + 设定 + 历史 + 用户输入
|
||||||
→ 多数轮:什么都不改(空 maintain)
|
→ 出 settlement 裁决包(可含 variable_changes;可经 harness 调机遇工具)
|
||||||
→ 少数轮:写 table_ops / need_generate(补字段或点名 rule_id)
|
2. 程序:合并裁决中的 variable_changes → 变量.当前(边沿副作用照常)
|
||||||
2. 主世界层:出 settlement 裁决包(可含变量变更)
|
3. 叙事转述(若启用):只读裁决包写用户可见正文
|
||||||
3. 程序:合并旁观改表 + DM 变量变更(边沿副作用照常)
|
4. 旁观维护:读本轮裁决 + 合同 + 表
|
||||||
4. 转述:只根据裁决包写正文
|
→ 多数轮:空 maintain
|
||||||
|
→ 少数轮:table_ops / need_generate
|
||||||
|
5. 程序:合并旁观 table_ops → 变量.当前(**下一轮**主世界层才读到)
|
||||||
```
|
```
|
||||||
|
|
||||||
要点:
|
要点:
|
||||||
|
|
||||||
1. **不要**默认「DM 写完再让旁观润色全文」(贵,且旁观会抢笔)。
|
1. **不要**默认「旁观先扫再让 DM 裁决」(旧顺序已废弃)。
|
||||||
2. **不要**默认「旁观先写一大段设定再交给 DM」(旁观变作者)。
|
2. **不要**默认「DM 写完再让旁观润色全文」(贵,且旁观会抢笔)。
|
||||||
3. **生成规则给旁观**:只要**合同**(有哪些 rule、格式、何时该生成),不要整本描写范文。
|
3. **不要**默认「旁观先写一大段设定再交给 DM」(旁观变作者)。
|
||||||
4. 真要「按规则生成实例」:旁观点 `need_generate.rule_id`,由**程序/既定生成通路**落表,而不是旁观写小说。
|
4. 生成规则给旁观:只要**合同**(有哪些 rule、格式、何时该生成),不要整本描写范文。
|
||||||
|
5. 真要「按规则生成实例」:旁观点 `need_generate.rule_id`,由**程序/既定生成通路**落表,而不是旁观写小说。
|
||||||
若以后要改成「乙」:把旁观挪到 **DM 之后**、只保留工具改表、禁止旁观输出正文——可以再开一刀改调度序;当前以已落地的「旁观在前、轻维护」为准。
|
|
||||||
|
|
||||||
## 4. 和「生成规则要不要给副 LLM」的关系
|
## 4. 和「生成规则要不要给副 LLM」的关系
|
||||||
|
|
||||||
@@ -64,5 +65,5 @@ DM 出裁决包(含变量变更)→ 旁观只动表/触发生成 → 程序
|
|||||||
|
|
||||||
## 5. 一句话
|
## 5. 一句话
|
||||||
|
|
||||||
**旁观看门与补表;DM 断事;转述说话。**
|
**主世界层断事;转述说话;旁观在回合末对表,改表给下一轮。**
|
||||||
交互上优先「轻维护 + 一次裁决」,不优先「来回多轮 LLM 传话」。
|
交互上优先「一次裁决 + 延后轻维护」,不优先「来回多轮 LLM 传话」。
|
||||||
|
|||||||
@@ -94,11 +94,10 @@ Progressive 投影 本轮主世界层只看见「当前档」;未
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
用户输入
|
用户输入
|
||||||
→ 旁观维护(副 LLM):合同+表+本轮工单;默认空操作(maintain.v1)
|
→ 主世界层(世界推演):读变量 + 已投影 data,交事件/改真值(按规则)
|
||||||
→(可选)角色视角:强信息隔离时出反应建议
|
|
||||||
→ 主世界层(世界推演):读变量 + 已投影 data + 旁观包,交事件/改真值(按规则)
|
|
||||||
→(可选)叙事转述:把裁决写成用户可见正文
|
→(可选)叙事转述:把裁决写成用户可见正文
|
||||||
→ 用户看到的终稿通常来自「呈现/叙事」执行单元,或主世界层兼呈现(规格决定)
|
→ 旁观维护(副 LLM):合同+表+本轮裁决;默认空操作(maintain.v1)
|
||||||
|
→(可选)角色视角:强信息隔离时出反应建议(默认关,插在主世界层之后)
|
||||||
```
|
```
|
||||||
|
|
||||||
| 内容 | 给谁 | 不要做成 |
|
| 内容 | 给谁 | 不要做成 |
|
||||||
|
|||||||
@@ -154,6 +154,14 @@ import type {
|
|||||||
} from "../types/runtime.js";
|
} from "../types/runtime.js";
|
||||||
import type { BlackboardItem } from "../types/blackboard.js";
|
import type { BlackboardItem } from "../types/blackboard.js";
|
||||||
|
|
||||||
|
/** 每轮开始前清掉的 transient tag(避免主世界层读到上一轮裁决/旁观包) */
|
||||||
|
const PLAY_ROUND_TRANSIENT_TAGS = [
|
||||||
|
SETTLEMENT_TAG,
|
||||||
|
MAINTAIN_TAG,
|
||||||
|
"运行.本轮.机遇",
|
||||||
|
"运行.本轮.变量变更",
|
||||||
|
] as const;
|
||||||
|
|
||||||
export type PhaseRuntimeOptions = {
|
export type PhaseRuntimeOptions = {
|
||||||
presetId?: string;
|
presetId?: string;
|
||||||
flowId?: string;
|
flowId?: string;
|
||||||
@@ -2072,6 +2080,7 @@ export class PhaseRuntime {
|
|||||||
/** 游玩:按 playWorkerIds 开一轮 */
|
/** 游玩:按 playWorkerIds 开一轮 */
|
||||||
private async startPlayTurn(): Promise<void> {
|
private async startPlayTurn(): Promise<void> {
|
||||||
this.syncSlotsToBlackboard(this.session);
|
this.syncSlotsToBlackboard(this.session);
|
||||||
|
this.clearPlayRoundTransientTags();
|
||||||
const ids = this.getInstanceWorkerDeclaration().playWorkerIds;
|
const ids = this.getInstanceWorkerDeclaration().playWorkerIds;
|
||||||
if (!ids.length) {
|
if (!ids.length) {
|
||||||
this.onMessage(
|
this.onMessage(
|
||||||
@@ -2118,6 +2127,29 @@ export class PhaseRuntime {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 新回合开始前归档上一轮 transient 产物(表合并结果保留在 变量.当前) */
|
||||||
|
private clearPlayRoundTransientTags(): void {
|
||||||
|
const slots = { ...this.session.slots };
|
||||||
|
const archivedAt = new Date().toISOString();
|
||||||
|
for (const tag of PLAY_ROUND_TRANSIENT_TAGS) {
|
||||||
|
const hadBoard = this.blackboard.getLatestByTag(tag) != null;
|
||||||
|
const hadSlot = tag in slots;
|
||||||
|
if (!hadBoard && !hadSlot) continue;
|
||||||
|
this.blackboard.write({
|
||||||
|
tag,
|
||||||
|
content: "",
|
||||||
|
source: "runtime",
|
||||||
|
metadata: {
|
||||||
|
role: "archived",
|
||||||
|
archivedAt,
|
||||||
|
reason: "play_round_reset",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
delete slots[tag];
|
||||||
|
}
|
||||||
|
this.session = { ...this.session, slots };
|
||||||
|
}
|
||||||
|
|
||||||
/** invoke_main_agent 副作用:running 时调用总管 LLM,链式推进直到需用户介入 */
|
/** invoke_main_agent 副作用:running 时调用总管 LLM,链式推进直到需用户介入 */
|
||||||
private async maybeRunMainAgent(effects: PhaseEffect[]): Promise<void> {
|
private async maybeRunMainAgent(effects: PhaseEffect[]): Promise<void> {
|
||||||
if (inferLifecycleStage(this.session) === "play") return;
|
if (inferLifecycleStage(this.session) === "play") return;
|
||||||
|
|||||||
+14
-14
@@ -6,7 +6,7 @@
|
|||||||
import type { WorkerAcceptance, WorkerSetEntry } from "./worker-set-parse.js";
|
import type { WorkerAcceptance, WorkerSetEntry } from "./worker-set-parse.js";
|
||||||
|
|
||||||
export type PlaySlotId = "auditor" | "gm" | "narrator" | "perspective";
|
export type PlaySlotId = "auditor" | "gm" | "narrator" | "perspective";
|
||||||
/** 按需槽(不进入 auditor→perspective→gm→narrator 管线) */
|
/** 按需槽(不进入 gm→perspective?→narrator→auditor 管线) */
|
||||||
export type OnDemandSlotId = "chance";
|
export type OnDemandSlotId = "chance";
|
||||||
|
|
||||||
export type PlaySlotsConfig = {
|
export type PlaySlotsConfig = {
|
||||||
@@ -82,10 +82,10 @@ export const ON_DEMAND_SLOT_META: Record<
|
|||||||
|
|
||||||
/** 推荐回合顺序(启用的槽按此排序;不含按需槽) */
|
/** 推荐回合顺序(启用的槽按此排序;不含按需槽) */
|
||||||
export const PLAY_SLOT_ORDER: PlaySlotId[] = [
|
export const PLAY_SLOT_ORDER: PlaySlotId[] = [
|
||||||
"auditor",
|
|
||||||
"perspective",
|
|
||||||
"gm",
|
"gm",
|
||||||
|
"perspective",
|
||||||
"narrator",
|
"narrator",
|
||||||
|
"auditor",
|
||||||
];
|
];
|
||||||
|
|
||||||
/** 这些槽的默认上下文不注入「对话.历史」长聊 */
|
/** 这些槽的默认上下文不注入「对话.历史」长聊 */
|
||||||
@@ -194,21 +194,21 @@ export function workerEntryForSlot(
|
|||||||
role: id,
|
role: id,
|
||||||
duty: meta.purpose,
|
duty: meta.purpose,
|
||||||
when:
|
when:
|
||||||
id === "auditor"
|
id === "gm"
|
||||||
? "每轮用户输入后、主世界层之前"
|
? "每轮用户输入后首先上场(主 LLM / 裁决权威)"
|
||||||
: id === "perspective"
|
: id === "perspective"
|
||||||
? "强信息隔离且本轮需要该角色独立反应时"
|
? "强信息隔离且本轮需要该角色独立反应时(主世界层之后)"
|
||||||
: id === "gm"
|
: id === "narrator"
|
||||||
? "每轮用户输入后(旁观维护与可选视角之后)"
|
? "主世界层裁决包就绪后"
|
||||||
: "主世界层裁决包就绪后",
|
: "转述完成后;表维护合并进变量.当前,供下一轮主世界层读取",
|
||||||
rationale:
|
rationale:
|
||||||
id === "auditor"
|
id === "gm"
|
||||||
? "表与规则补充与主裁决分槽,避免挤占对话历史上下文"
|
? "等同 SillyTavern 主 LLM:读设定与状态并产出裁决"
|
||||||
: id === "perspective"
|
: id === "perspective"
|
||||||
? "知密内容不能进主世界层上下文"
|
? "知密内容不能进主世界层上下文"
|
||||||
: id === "gm"
|
: id === "narrator"
|
||||||
? "裁决与真值变更需要独立推理槽"
|
? "用户可见正文与裁决分离,避免文风与规则互相挤压"
|
||||||
: "用户可见正文与裁决分离,避免文风与规则互相挤压",
|
: "表与规则补充延后执行,改表结果作用于下一轮而非本轮重裁",
|
||||||
acceptance: meta.defaultAcceptance,
|
acceptance: meta.defaultAcceptance,
|
||||||
invocation: "turn",
|
invocation: "turn",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* 游玩回合:按运行规格 playWorkerIds 连跑(旁观 → 视角? → 主世界层 → 转述)。
|
* 游玩回合:按运行规格 playWorkerIds 连跑(主世界层 → 视角? → 转述 → 旁观维护)。
|
||||||
|
* 旁观维护在本轮末执行,表变更供下一轮主世界层读取。
|
||||||
* 与创作总管无关;收口后的输入走这里。
|
* 与创作总管无关;收口后的输入走这里。
|
||||||
*/
|
*/
|
||||||
export const SLOT_PLAY_TURN_QUEUE = "playTurnQueue";
|
export const SLOT_PLAY_TURN_QUEUE = "playTurnQueue";
|
||||||
|
|||||||
@@ -41,22 +41,22 @@ describe("play_slots", () => {
|
|||||||
})!;
|
})!;
|
||||||
expect(slots.auditor).toBe(true);
|
expect(slots.auditor).toBe(true);
|
||||||
expect(refsFromPlaySlots(slots)).toEqual([
|
expect(refsFromPlaySlots(slots)).toEqual([
|
||||||
"auditor",
|
|
||||||
"world-simulator",
|
"world-simulator",
|
||||||
"narrator",
|
"narrator",
|
||||||
|
"auditor",
|
||||||
]);
|
]);
|
||||||
const workers = expandPlaySlotsToWorkers(slots);
|
const workers = expandPlaySlotsToWorkers(slots);
|
||||||
expect(workers.map((w) => w.ref)).toEqual([
|
expect(workers.map((w) => w.ref)).toEqual([
|
||||||
"auditor",
|
|
||||||
"world-simulator",
|
"world-simulator",
|
||||||
"narrator",
|
"narrator",
|
||||||
|
"auditor",
|
||||||
]);
|
]);
|
||||||
expect(workers[0].acceptance).toBe("continue");
|
expect(workers[0].acceptance).toBe("continue");
|
||||||
expect(workers[1].acceptance).toBe("continue");
|
expect(workers[1].acceptance).toBe("review");
|
||||||
expect(workers[2].acceptance).toBe("review");
|
expect(workers[2].acceptance).toBe("continue");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("orders auditor then perspective before gm", () => {
|
it("orders gm first then perspective before narrator and auditor", () => {
|
||||||
const slots = parsePlaySlots({
|
const slots = parsePlaySlots({
|
||||||
auditor: true,
|
auditor: true,
|
||||||
gm: true,
|
gm: true,
|
||||||
@@ -64,10 +64,10 @@ describe("play_slots", () => {
|
|||||||
perspective: true,
|
perspective: true,
|
||||||
})!;
|
})!;
|
||||||
expect(refsFromPlaySlots(slots)).toEqual([
|
expect(refsFromPlaySlots(slots)).toEqual([
|
||||||
"auditor",
|
|
||||||
"role-decide",
|
|
||||||
"world-simulator",
|
"world-simulator",
|
||||||
|
"role-decide",
|
||||||
"narrator",
|
"narrator",
|
||||||
|
"auditor",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -97,9 +97,9 @@ describe("play_slots", () => {
|
|||||||
expect(parsed?.play_slots?.auditor).toBe(true);
|
expect(parsed?.play_slots?.auditor).toBe(true);
|
||||||
expect(parsed?.workers).toHaveLength(3);
|
expect(parsed?.workers).toHaveLength(3);
|
||||||
expect(deriveRunWorkerScope(parsed)).toEqual([
|
expect(deriveRunWorkerScope(parsed)).toEqual([
|
||||||
"auditor",
|
|
||||||
"world-simulator",
|
"world-simulator",
|
||||||
"narrator",
|
"narrator",
|
||||||
|
"auditor",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user