Add gm harness with batch chance tools for play.

Main world layer can loop on the chance tool (requests array) until settlement JSON. Driver forwards output deltas; standalone chance worker accepts batch requests. play_slots.chance gates the harness.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-25 00:49:19 +08:00
co-authored by Cursor
parent 6b4156fd0f
commit 8360e885bb
9 changed files with 428 additions and 32 deletions
+45
View File
@@ -0,0 +1,45 @@
import { describe, expect, it } from "vitest";
import {
executeChanceBatch,
parseChanceBatchRequest,
} from "../src/skills/chance-tools.js";
import {
executeGmChanceTool,
gmChanceToolsEnabled,
} from "../src/skills/gm-tools.js";
describe("chance batch", () => {
it("parses requests array", () => {
const items = parseChanceBatchRequest({
requests: [
{ id: "door", op: "roll", expression: "1d20" },
{ id: "win", op: "compare", left: 15, right: 12 },
],
});
expect(items?.map((x) => x.id)).toEqual(["door", "win"]);
});
it("executeGmChanceTool returns batch schema", () => {
const parsed = executeGmChanceTool(
JSON.stringify({
requests: [{ id: "x", op: "roll", expression: "1d6" }],
}),
);
expect(parsed.schema).toBe("chance.batch.v1");
expect(parsed.results[0]!.id).toBe("x");
});
});
describe("gmChanceToolsEnabled", () => {
it("defaults on when play_slots omitted", () => {
expect(gmChanceToolsEnabled(undefined)).toBe(true);
});
it("respects chance=false", () => {
expect(gmChanceToolsEnabled({ chance: false })).toBe(false);
});
it("enables when chance=true", () => {
expect(gmChanceToolsEnabled({ chance: true })).toBe(true);
});
});
+11 -9
View File
@@ -27,6 +27,7 @@ import {
} from "../src/blackboard/table-cells.js";
import {
executeChance,
executeChanceBatch,
parseChanceRequest,
} from "../src/skills/chance-tools.js";
import { synthesizeContextOrderFromWorkers } from "../src/skills/context-order.js";
@@ -185,15 +186,16 @@ describe("chance tools", () => {
expect(result.detail.win).toBe(true);
});
it("draws from pool", () => {
const result = executeChance({
op: "draw",
pool: ["甲", "乙", "丙"],
count: 2,
unique: true,
});
expect(result.ok).toBe(true);
expect((result.detail.drawn as string[]).length).toBe(2);
it("batch executes multiple requests with ids", () => {
const batch = executeChanceBatch([
{ id: "a", op: "roll", expression: "1d6" },
{ id: "b", op: "compare", left: 10, right: 5, mode: "gte" },
]);
expect(batch.schema).toBe("chance.batch.v1");
expect(batch.ok).toBe(true);
expect(batch.results).toHaveLength(2);
expect(batch.results[0]!.id).toBe("a");
expect(batch.results[1]!.id).toBe("b");
});
});