run-dmcp 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/run-dmcp.js +16 -1
- package/dist/db/schema.js +4 -3
- package/dist/index.d.ts +6 -2
- package/dist/index.js +12 -1
- package/dist/mcp-server.d.ts +6 -1
- package/dist/mcp-server.js +11 -3
- package/dist/reader/preflight.d.ts +62 -0
- package/dist/reader/preflight.js +40 -0
- package/dist/reader/turnReader.d.ts +169 -13
- package/dist/reader/turnReader.js +232 -78
- package/dist/register/conditions.d.ts +16 -0
- package/dist/register/conditions.js +45 -0
- package/dist/register/reader.d.ts +2 -0
- package/dist/register/reader.js +132 -0
- package/dist/register/render.js +7 -2
- package/dist/register/resolve.js +8 -2
- package/dist/register/timeline.js +29 -2
- package/dist/rpg/server.d.ts +2 -0
- package/dist/schemas/index.d.ts +10 -10
- package/dist/timeline/constrained.d.ts +4 -5
- package/dist/timeline/constrained.js +31 -25
- package/dist/timeline/declared.d.ts +147 -0
- package/dist/timeline/declared.js +388 -0
- package/dist/timeline/render.d.ts +3 -0
- package/dist/timeline/render.js +1 -1
- package/dist/timeline/replay.d.ts +15 -0
- package/dist/timeline/replay.js +33 -2
- package/dist/timeline/resolve.d.ts +13 -3
- package/dist/timeline/resolve.js +34 -3
- package/dist/timeline/schema.js +28 -0
- package/dist/timeline/turns.d.ts +60 -0
- package/dist/timeline/turns.js +93 -0
- package/dist/utils/output-schemas.d.ts +6 -6
- package/package.json +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type T } from "./t.js";
|
|
2
|
+
/**
|
|
3
|
+
* A principal is due to act (GitHub issue #40). The issue draws the line:
|
|
4
|
+
* "The engine records whose turn it is and refuses out-of-turn proposals if
|
|
5
|
+
* a game declares turns. It never runs the turn itself." So this module holds
|
|
6
|
+
* a DECLARATION -- which entities act, in what cycle, from which t -- and
|
|
7
|
+
* answers "who is due at t" for any t, past included. It never schedules,
|
|
8
|
+
* waits, notifies or calls anything: an MCP server answers calls, so
|
|
9
|
+
* whatever runs minds asks (pull), moves the clock with `advanceTurn`, and
|
|
10
|
+
* proposes as that principal. `resolve()` refuses a proposal whose `actor` is
|
|
11
|
+
* not the one due (resolve.ts, `out-of-turn`).
|
|
12
|
+
*
|
|
13
|
+
* WHY A COUNTER AXIS ONLY. A turn is a count of things that happened, which is
|
|
14
|
+
* exactly what `counter` means (t.ts). On `sequence` the engine's own writes
|
|
15
|
+
* advance t, so "one principal per t" would hand the turn over mid-action;
|
|
16
|
+
* on `elapsed` t is a measure, and turns-per-second is a policy this engine
|
|
17
|
+
* has no business guessing.
|
|
18
|
+
*
|
|
19
|
+
* WHY APPEND-ONLY. `dueAt(t)` for a past t is history, and this engine can
|
|
20
|
+
* always say what the world looked like at any point. A changed order is a
|
|
21
|
+
* NEW declaration from a later t; the old one keeps answering for the t it
|
|
22
|
+
* covered. The `turn_orders` triggers (schema.ts) make an edit
|
|
23
|
+
* unconstructable rather than discouraged.
|
|
24
|
+
*
|
|
25
|
+
* A principal is an entity of the game (DESIGN.md §8's admission criterion:
|
|
26
|
+
* it resolves to an entity the engine already has). The engine never learns
|
|
27
|
+
* what a principal is for.
|
|
28
|
+
*/
|
|
29
|
+
export interface TurnDue {
|
|
30
|
+
t: T;
|
|
31
|
+
principal: string;
|
|
32
|
+
/** 1-based: which pass through the declared cycle this t belongs to. */
|
|
33
|
+
round: number;
|
|
34
|
+
/** 0-based position in the cycle. */
|
|
35
|
+
position: number;
|
|
36
|
+
/** The declaration this answer comes from. */
|
|
37
|
+
fromT: T;
|
|
38
|
+
}
|
|
39
|
+
export declare function declareTurnOrder(params: {
|
|
40
|
+
gameId: string;
|
|
41
|
+
principals: readonly string[];
|
|
42
|
+
fromT: T;
|
|
43
|
+
}): {
|
|
44
|
+
fromT: T;
|
|
45
|
+
principals: string[];
|
|
46
|
+
};
|
|
47
|
+
/** Who is due at `t`, or `null`: no declaration covers `t`, or `t` is not a
|
|
48
|
+
* whole step on from the declaration's `fromT`. */
|
|
49
|
+
export declare function dueAt(params: {
|
|
50
|
+
gameId: string;
|
|
51
|
+
t: T;
|
|
52
|
+
}): TurnDue | null;
|
|
53
|
+
/** Moves the clock to the next turn -- one step on, or to the first
|
|
54
|
+
* declaration's `fromT` if the clock has not reached it -- and says who is
|
|
55
|
+
* due there. Moves time only; runs nothing. */
|
|
56
|
+
export declare function advanceTurn(params: {
|
|
57
|
+
gameId: string;
|
|
58
|
+
}): TurnDue;
|
|
59
|
+
/** Whether the game has declared any turn order at all. */
|
|
60
|
+
export declare function hasTurnOrder(gameId: string): boolean;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from "uuid";
|
|
2
|
+
import { getDatabase } from "../db/connection.js";
|
|
3
|
+
import { currentStoryTime, setStoryTime } from "./clock.js";
|
|
4
|
+
import { assertT } from "./t.js";
|
|
5
|
+
function declarationAt(gameId, t) {
|
|
6
|
+
return getDatabase()
|
|
7
|
+
.prepare(`SELECT from_t, principals FROM turn_orders WHERE game_id = ? AND from_t <= ? ORDER BY from_t DESC LIMIT 1`)
|
|
8
|
+
.get(gameId, t);
|
|
9
|
+
}
|
|
10
|
+
function latestDeclaration(gameId) {
|
|
11
|
+
return getDatabase()
|
|
12
|
+
.prepare(`SELECT from_t, principals FROM turn_orders WHERE game_id = ? ORDER BY from_t DESC LIMIT 1`)
|
|
13
|
+
.get(gameId);
|
|
14
|
+
}
|
|
15
|
+
export function declareTurnOrder(params) {
|
|
16
|
+
const { gameId, fromT } = params;
|
|
17
|
+
assertT(fromT);
|
|
18
|
+
const principals = [...params.principals];
|
|
19
|
+
const story = currentStoryTime(gameId);
|
|
20
|
+
if (!story || story.axis.kind !== "counter") {
|
|
21
|
+
throw new Error(`declareTurnOrder: game '${gameId}' must be on a counter axis -- a turn is a count of things that happened ` +
|
|
22
|
+
`(declare_time_axis with kind 'counter')`);
|
|
23
|
+
}
|
|
24
|
+
if (!Number.isInteger(fromT))
|
|
25
|
+
throw new Error(`declareTurnOrder: fromT must be an integer t on the counter axis, got ${fromT}`);
|
|
26
|
+
if (fromT < story.t) {
|
|
27
|
+
throw new Error(`declareTurnOrder: fromT ${fromT} is before the game's current t (${story.t}); turns already taken cannot be reordered`);
|
|
28
|
+
}
|
|
29
|
+
const latest = latestDeclaration(gameId);
|
|
30
|
+
// The current t may already be someone's turn (and a resolution may
|
|
31
|
+
// already record them acting in it); reassigning it would rewrite that.
|
|
32
|
+
if (latest && latest.from_t <= story.t && fromT <= story.t) {
|
|
33
|
+
throw new Error(`declareTurnOrder: t=${story.t} is already covered by the declaration from ${latest.from_t}; a new order can start at t=${story.t + 1} at the earliest`);
|
|
34
|
+
}
|
|
35
|
+
if (latest && fromT <= latest.from_t) {
|
|
36
|
+
throw new Error(`declareTurnOrder: fromT ${fromT} must come after the latest declaration's fromT (${latest.from_t})`);
|
|
37
|
+
}
|
|
38
|
+
if (principals.length === 0)
|
|
39
|
+
throw new Error(`declareTurnOrder: a turn order needs at least one principal`);
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
const alive = getDatabase().prepare(`SELECT 1 FROM entities WHERE id = ? AND game_id = ? AND created_at_t <= ? AND (destroyed_at_t IS NULL OR destroyed_at_t > ?)`);
|
|
42
|
+
for (const principal of principals) {
|
|
43
|
+
if (typeof principal !== "string")
|
|
44
|
+
throw new Error(`declareTurnOrder: a principal must be an entity id, got ${JSON.stringify(principal)}`);
|
|
45
|
+
if (seen.has(principal))
|
|
46
|
+
throw new Error(`declareTurnOrder: principal '${principal}' appears twice in one cycle`);
|
|
47
|
+
seen.add(principal);
|
|
48
|
+
if (!alive.get(principal, gameId, fromT, fromT)) {
|
|
49
|
+
throw new Error(`declareTurnOrder: '${principal}' is not an entity of this game alive at t=${fromT}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
getDatabase()
|
|
53
|
+
.prepare(`INSERT INTO turn_orders (id, game_id, from_t, principals, declared_at) VALUES (?, ?, ?, ?, ?)`)
|
|
54
|
+
.run(uuidv4(), gameId, fromT, JSON.stringify(principals), new Date().toISOString());
|
|
55
|
+
return { fromT, principals };
|
|
56
|
+
}
|
|
57
|
+
/** Who is due at `t`, or `null`: no declaration covers `t`, or `t` is not a
|
|
58
|
+
* whole step on from the declaration's `fromT`. */
|
|
59
|
+
export function dueAt(params) {
|
|
60
|
+
const { gameId, t } = params;
|
|
61
|
+
assertT(t);
|
|
62
|
+
const declaration = declarationAt(gameId, t);
|
|
63
|
+
if (!declaration)
|
|
64
|
+
return null;
|
|
65
|
+
const offset = t - declaration.from_t;
|
|
66
|
+
if (!Number.isInteger(offset))
|
|
67
|
+
return null;
|
|
68
|
+
const principals = JSON.parse(declaration.principals);
|
|
69
|
+
const position = offset % principals.length;
|
|
70
|
+
return { t, principal: principals[position], round: Math.floor(offset / principals.length) + 1, position, fromT: declaration.from_t };
|
|
71
|
+
}
|
|
72
|
+
/** Moves the clock to the next turn -- one step on, or to the first
|
|
73
|
+
* declaration's `fromT` if the clock has not reached it -- and says who is
|
|
74
|
+
* due there. Moves time only; runs nothing. */
|
|
75
|
+
export function advanceTurn(params) {
|
|
76
|
+
const { gameId } = params;
|
|
77
|
+
const story = currentStoryTime(gameId);
|
|
78
|
+
const first = getDatabase()
|
|
79
|
+
.prepare(`SELECT from_t FROM turn_orders WHERE game_id = ? ORDER BY from_t ASC LIMIT 1`)
|
|
80
|
+
.get(gameId);
|
|
81
|
+
if (!story || !first)
|
|
82
|
+
throw new Error(`advanceTurn: game '${gameId}' has no turn order declared (declare_turn_order)`);
|
|
83
|
+
const next = story.t < first.from_t ? first.from_t : Math.floor(story.t) + 1;
|
|
84
|
+
const due = dueAt({ gameId, t: next });
|
|
85
|
+
if (!due)
|
|
86
|
+
throw new Error(`advanceTurn: nobody is due at t=${next}`);
|
|
87
|
+
setStoryTime({ gameId, t: next });
|
|
88
|
+
return due;
|
|
89
|
+
}
|
|
90
|
+
/** Whether the game has declared any turn order at all. */
|
|
91
|
+
export function hasTurnOrder(gameId) {
|
|
92
|
+
return getDatabase().prepare(`SELECT 1 FROM turn_orders WHERE game_id = ? LIMIT 1`).get(gameId) !== undefined;
|
|
93
|
+
}
|
|
@@ -255,14 +255,14 @@ export declare const characterOutputSchema: {
|
|
|
255
255
|
condition: z.ZodOptional<z.ZodString>;
|
|
256
256
|
glowOrEffects: z.ZodOptional<z.ZodString>;
|
|
257
257
|
}, "strip", z.ZodTypeAny, {
|
|
258
|
+
condition?: string | undefined;
|
|
258
259
|
size?: string | undefined;
|
|
259
260
|
material?: string | undefined;
|
|
260
|
-
condition?: string | undefined;
|
|
261
261
|
glowOrEffects?: string | undefined;
|
|
262
262
|
}, {
|
|
263
|
+
condition?: string | undefined;
|
|
263
264
|
size?: string | undefined;
|
|
264
265
|
material?: string | undefined;
|
|
265
|
-
condition?: string | undefined;
|
|
266
266
|
glowOrEffects?: string | undefined;
|
|
267
267
|
}>>;
|
|
268
268
|
pose: z.ZodOptional<z.ZodString>;
|
|
@@ -299,9 +299,9 @@ export declare const characterOutputSchema: {
|
|
|
299
299
|
vegetation?: string | undefined;
|
|
300
300
|
} | undefined;
|
|
301
301
|
objectDetails?: {
|
|
302
|
+
condition?: string | undefined;
|
|
302
303
|
size?: string | undefined;
|
|
303
304
|
material?: string | undefined;
|
|
304
|
-
condition?: string | undefined;
|
|
305
305
|
glowOrEffects?: string | undefined;
|
|
306
306
|
} | undefined;
|
|
307
307
|
pose?: string | undefined;
|
|
@@ -338,9 +338,9 @@ export declare const characterOutputSchema: {
|
|
|
338
338
|
vegetation?: string | undefined;
|
|
339
339
|
} | undefined;
|
|
340
340
|
objectDetails?: {
|
|
341
|
+
condition?: string | undefined;
|
|
341
342
|
size?: string | undefined;
|
|
342
343
|
material?: string | undefined;
|
|
343
|
-
condition?: string | undefined;
|
|
344
344
|
glowOrEffects?: string | undefined;
|
|
345
345
|
} | undefined;
|
|
346
346
|
pose?: string | undefined;
|
|
@@ -587,9 +587,9 @@ export declare const characterOutputSchema: {
|
|
|
587
587
|
vegetation?: string | undefined;
|
|
588
588
|
} | undefined;
|
|
589
589
|
objectDetails?: {
|
|
590
|
+
condition?: string | undefined;
|
|
590
591
|
size?: string | undefined;
|
|
591
592
|
material?: string | undefined;
|
|
592
|
-
condition?: string | undefined;
|
|
593
593
|
glowOrEffects?: string | undefined;
|
|
594
594
|
} | undefined;
|
|
595
595
|
pose?: string | undefined;
|
|
@@ -683,9 +683,9 @@ export declare const characterOutputSchema: {
|
|
|
683
683
|
vegetation?: string | undefined;
|
|
684
684
|
} | undefined;
|
|
685
685
|
objectDetails?: {
|
|
686
|
+
condition?: string | undefined;
|
|
686
687
|
size?: string | undefined;
|
|
687
688
|
material?: string | undefined;
|
|
688
|
-
condition?: string | undefined;
|
|
689
689
|
glowOrEffects?: string | undefined;
|
|
690
690
|
} | undefined;
|
|
691
691
|
pose?: string | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "run-dmcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "An MCP server for LLM-run interactive fiction where the server owns what is true - including when it was true. A continuation of DMCP.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Derek Ferguson",
|