引入游玩呈现壳与旁观维护槽,完善结算合并、十分制自评与技能产物 UI。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-12 02:03:29 +08:00
parent 94f67fa744
commit 120d67ee82
46 changed files with 2513 additions and 236 deletions

View File

@@ -11,53 +11,104 @@ import {
deriveRunWorkerScope,
parseWorkerSetYaml,
} from "../src/skills/worker-set-parse.js";
import { parseSettlementPacket } from "../src/skills/settlement-packet.js";
import {
parseSettlementPacket,
settlementChangesToTablePatch,
} from "../src/skills/settlement-packet.js";
import {
emptyMaintainPacket,
isMaintainPacketEmpty,
maintainOpsToTablePatch,
parseMaintainPacket,
} from "../src/skills/maintain-packet.js";
import {
createTableFromValues,
mergeTableCells,
} from "../src/blackboard/table-cells.js";
import {
executeChance,
parseChanceRequest,
} from "../src/skills/chance-tools.js";
import { synthesizeContextOrderFromWorkers } from "../src/skills/context-order.js";
import { DIALOGUE_HISTORY_TAG } from "../src/skills/dialogue-history.js";
describe("play_slots", () => {
it("expands default gm+narrator", () => {
const slots = parsePlaySlots({ gm: true, narrator: true, perspective: false })!;
expect(refsFromPlaySlots(slots)).toEqual(["world-simulator", "narrator"]);
it("expands default auditor+gm+narrator", () => {
const slots = parsePlaySlots({
gm: true,
narrator: true,
perspective: false,
})!;
expect(slots.auditor).toBe(true);
expect(refsFromPlaySlots(slots)).toEqual([
"auditor",
"world-simulator",
"narrator",
]);
const workers = expandPlaySlotsToWorkers(slots);
expect(workers.map((w) => w.ref)).toEqual(["world-simulator", "narrator"]);
expect(workers.map((w) => w.ref)).toEqual([
"auditor",
"world-simulator",
"narrator",
]);
expect(workers[0].acceptance).toBe("continue");
expect(workers[1].acceptance).toBe("review");
expect(workers[1].acceptance).toBe("continue");
expect(workers[2].acceptance).toBe("review");
});
it("orders perspective before gm", () => {
it("orders auditor then perspective before gm", () => {
const slots = parsePlaySlots({
auditor: true,
gm: true,
narrator: true,
perspective: true,
})!;
expect(refsFromPlaySlots(slots)).toEqual([
"auditor",
"role-decide",
"world-simulator",
"narrator",
]);
});
it("can disable auditor", () => {
const slots = parsePlaySlots({
auditor: false,
gm: true,
narrator: true,
perspective: false,
})!;
expect(refsFromPlaySlots(slots)).toEqual(["world-simulator", "narrator"]);
});
it("merges play_slots into worker set JSON", () => {
const parsed = parseWorkerSetYaml(
JSON.stringify({
form_summary: "恋爱升温",
play_slots: { gm: true, narrator: true, perspective: false },
play_slots: {
auditor: true,
gm: true,
narrator: true,
perspective: false,
},
workers: [],
}),
);
expect(parsed?.play_slots?.gm).toBe(true);
expect(parsed?.workers).toHaveLength(2);
expect(parsed?.play_slots?.auditor).toBe(true);
expect(parsed?.workers).toHaveLength(3);
expect(deriveRunWorkerScope(parsed)).toEqual([
"auditor",
"world-simulator",
"narrator",
]);
});
it("preserves custom context on merge", () => {
const slots = parsePlaySlots({ gm: true, narrator: true })!;
const slots = parsePlaySlots({
auditor: false,
gm: true,
narrator: true,
})!;
const merged = mergeWorkersWithPlaySlots(
[
{
@@ -76,6 +127,7 @@ describe("play_slots", () => {
it("chance is on-demand and not in turn pipeline", () => {
const slots = parsePlaySlots({
auditor: false,
gm: true,
narrator: true,
perspective: false,
@@ -96,6 +148,7 @@ describe("play_slots", () => {
const parsed = parseWorkerSetYaml(
JSON.stringify({
play_slots: {
auditor: false,
gm: true,
narrator: true,
perspective: false,
@@ -166,4 +219,91 @@ describe("settlement packet", () => {
expect(view.ok).toBe(true);
expect(view.packet?.visible_now).toContain("画册");
});
it("merges variable_changes into table patch with delta", () => {
const current = createTableFromValues({ 好感: 10 }, "system");
const patch = settlementChangesToTablePatch(
[{ key: "好感", delta: 8 }],
current,
)!;
const { doc } = mergeTableCells({
current,
patch,
actor: "worker:world-simulator",
});
expect(doc.rows.find((r) => r.key === "好感")?.value).toBe(18);
});
it("merges variable_changes with absolute to", () => {
const current = createTableFromValues({ : "相识" }, "system");
const patch = settlementChangesToTablePatch(
[{ key: "阶段", to: "暧昧" }],
current,
)!;
const { doc } = mergeTableCells({
current,
patch,
actor: "worker:world-simulator",
});
expect(doc.rows.find((r) => r.key === "阶段")?.value).toBe("暧昧");
});
});
describe("maintain packet", () => {
it("defaults to empty ops", () => {
const view = parseMaintainPacket(
JSON.stringify({
schema: "maintain.v1",
need_generate: false,
table_ops: [],
notes: [],
}),
);
expect(view.ok).toBe(true);
expect(view.empty).toBe(true);
expect(isMaintainPacketEmpty(emptyMaintainPacket())).toBe(true);
});
it("accepts noop shorthand", () => {
expect(parseMaintainPacket("无需").empty).toBe(true);
});
it("applies table_ops delta to 变量.当前", () => {
const current = createTableFromValues({ 好感: 5 }, "system");
const view = parseMaintainPacket(
JSON.stringify({
schema: "maintain.v1",
table_ops: [{ op: "delta", key: "好感", delta: 3 }],
}),
);
const patch = maintainOpsToTablePatch(
view.packet!.table_ops!,
current,
"变量.当前",
)!;
const { doc } = mergeTableCells({
current,
patch,
actor: "worker:auditor",
});
expect(doc.rows.find((r) => r.key === "好感")?.value).toBe(8);
});
});
describe("context order synth", () => {
it("skips dialogue history for auditor", () => {
const order = synthesizeContextOrderFromWorkers(
[
{ ref: "auditor", name: "旁观维护" },
{ ref: "world-simulator", name: "主世界层" },
],
parsePlaySlots({ auditor: true, gm: true, narrator: false }),
)!;
const auditor = order.slots.find((s) => s.ref === "auditor")!;
const gm = order.slots.find((s) => s.ref === "world-simulator")!;
expect(auditor.inserts.some((i) => i.ref === DIALOGUE_HISTORY_TAG)).toBe(
false,
);
expect(gm.inserts.some((i) => i.ref === DIALOGUE_HISTORY_TAG)).toBe(true);
});
});