@polycode-projects/the-mechanical-code-talker 5.0.4 → 5.0.6
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/README.md +72 -19
- package/package.json +1 -1
- package/src/adapters/memory/core.mjs +53 -0
- package/src/domain/ask.mjs +2 -4
- package/src/domain/codegraph.mjs +23 -9
- package/src/domain/game-config.mjs +18 -0
- package/src/domain/memory/causal-stability.mjs +82 -0
- package/src/domain/town-square-world.mjs +36 -0
- package/src/services/adventure-viz.mjs +5 -2
- package/src/services/chat-page-viz.mjs +5 -2
- package/src/services/chat.mjs +104 -16
- package/src/services/code-explorer-viz.mjs +3 -2
- package/src/services/ingest-viz.mjs +5 -4
- package/src/services/ledger-viz.mjs +5 -2
- package/src/services/mud-viz.mjs +4 -3
- package/src/services/mudiii-scene.mjs +136 -12
- package/src/services/mudiii-turn.mjs +250 -1
- package/src/services/mudiii-viz.mjs +599 -108
- package/src/services/p2p-room.mjs +1 -1
- package/src/services/plan-viz.mjs +3 -2
- package/src/services/predator-prey.mjs +133 -17
- package/src/services/research-viz.mjs +5 -4
- package/src/services/spider-fly-viz.mjs +3 -2
- package/src/services/sprite-catalog-viz.mjs +3 -2
- package/src/services/viz-theme.mjs +20 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +91 -91
- package/src/surfaces/web/mudiii-browser-entry.mjs +154 -34
|
@@ -6,14 +6,6 @@
|
|
|
6
6
|
// on `{ as: character }` — the addressee is in the sentence, "@fox-1 look"),
|
|
7
7
|
// and the simulation itself advances as ONE whole-world tick
|
|
8
8
|
// (`runTownSquareTick`) rather than mud's per-character `autoplayTick`.
|
|
9
|
-
//
|
|
10
|
-
// `src/services/predator-prey.mjs` — the engine this file drives — does not
|
|
11
|
-
// exist in every worktree yet; a concurrent track owns it. The import below
|
|
12
|
-
// is guarded (dynamic, try/catch) so this module still loads, and every
|
|
13
|
-
// test that imports mudiii-viz.mjs (which never imports this file) is
|
|
14
|
-
// unaffected either way. `createMudiiiSession` throws a clear "the engine
|
|
15
|
-
// isn't built yet" error if actually called before that track lands, rather
|
|
16
|
-
// than a bare ERR_MODULE_NOT_FOUND with no context.
|
|
17
9
|
import {
|
|
18
10
|
createInMemoryStore, appendFacts, appendRule, loadMemory, readFactRows, removeFacts,
|
|
19
11
|
} from "../../adapters/memory/core.mjs";
|
|
@@ -21,25 +13,22 @@ import { parseEntities } from "../../domain/codegraph.mjs";
|
|
|
21
13
|
import { memoryFactGraphPayload } from "../../domain/memory-facts.mjs";
|
|
22
14
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
23
15
|
import { worldProvenanceTag } from "../../domain/worlds-pack.mjs";
|
|
24
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
COMPASS_POINTS, DEFAULT_FACING, cellId, layoutNamed, parseCellId, reverseFacing, stepCellFrom, turnedFacing,
|
|
18
|
+
} from "../../domain/town-square-world.mjs";
|
|
19
|
+
import { findActionPath } from "../../domain/planning.mjs";
|
|
20
|
+
import { DEFAULT_GAME_CONFIG } from "../../domain/game-config.mjs";
|
|
25
21
|
import { parseMudEditorText, planMudEditorSync, gridWorldEditorState } from "../../services/mud-editor.mjs";
|
|
22
|
+
import { pillsForMudiii } from "../../services/mudiii-turn.mjs";
|
|
23
|
+
import { relatedForTerm } from "../../domain/skos-view.mjs";
|
|
24
|
+
import { classAncestorChain } from "../../domain/sprite-map.mjs";
|
|
26
25
|
import { createTurnSession } from "./turn-session.mjs";
|
|
27
26
|
import { publishTmctSurface } from "./tmct-surface.mjs";
|
|
28
27
|
import { graphAsk, enginePlan } from "./engine-surface.mjs";
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
engine = await import("../../services/predator-prey.mjs");
|
|
35
|
-
} catch (err) {
|
|
36
|
-
throw new Error(
|
|
37
|
-
"mudiii's engine (src/services/predator-prey.mjs) is not built in this worktree yet "
|
|
38
|
-
+ `(${err && err.message ? err.message : err})`,
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
return engine;
|
|
42
|
-
}
|
|
28
|
+
import {
|
|
29
|
+
foldTownSquareState, gridApplyActions, pathStateKey, placeFood as engPlaceFood, recastTownSquare,
|
|
30
|
+
roleOfId, runTownSquareTick, startTownSquareGame, townSquareBoard,
|
|
31
|
+
} from "../../services/predator-prey.mjs";
|
|
43
32
|
|
|
44
33
|
/** `count` entries drawn at random from `roster`, in random order, without
|
|
45
34
|
* repeats — mirrors mud-browser-entry.mjs's own `pickMudRoster`. `random` is
|
|
@@ -78,6 +67,76 @@ export function townSquareRosterArgs(agents, roleOf) {
|
|
|
78
67
|
return { predatorCount, preyCount };
|
|
79
68
|
}
|
|
80
69
|
|
|
70
|
+
// Where each press on the page's ring of controls sits relative to the agent's
|
|
71
|
+
// own facing. Up and down are steps — straight ahead and straight back — and
|
|
72
|
+
// the other six turn on the spot to the angle they sit at, which is what makes
|
|
73
|
+
// the ring one rule rather than eight cases.
|
|
74
|
+
const RING_ANGLES = Object.freeze({
|
|
75
|
+
up: 0, forward: 0,
|
|
76
|
+
"up-right": 45, right: 90, "down-right": 135,
|
|
77
|
+
down: 180, back: 180,
|
|
78
|
+
"down-left": -135, left: -90, "up-left": -45,
|
|
79
|
+
});
|
|
80
|
+
const RING_STEPS = new Set(["up", "forward", "down", "back"]);
|
|
81
|
+
|
|
82
|
+
/** The `manualMoves` entry one press asks of an agent standing at `cell` and
|
|
83
|
+
* facing `facing`: `{ cell, facing }` for a step, `{ facing }` alone for a
|
|
84
|
+
* turn on the spot. Null when `direction` names neither a ring press nor a
|
|
85
|
+
* compass point.
|
|
86
|
+
*
|
|
87
|
+
* `direction` is either a ring press — "up"/"forward" and "down"/"back" step
|
|
88
|
+
* straight ahead and straight back with the facing left alone, "left" and
|
|
89
|
+
* "right" turn ninety degrees, and the four diagonals turn to the angle they
|
|
90
|
+
* sit at (forty-five for the top pair, a hundred and thirty-five for the
|
|
91
|
+
* bottom) — or an absolute compass point, where a cardinal steps that way and
|
|
92
|
+
* faces that way and an intercardinal turns on the spot to face it.
|
|
93
|
+
*
|
|
94
|
+
* Whether the cell it names can be entered is the engine's answer rather than
|
|
95
|
+
* this function's: there is one legality table, and it is the world's own
|
|
96
|
+
* exit facts. Pure. */
|
|
97
|
+
export function driveRequest(direction, { cell, facing = DEFAULT_FACING } = {}) {
|
|
98
|
+
const press = String(direction ?? "").trim().toLowerCase();
|
|
99
|
+
if (!press) return null;
|
|
100
|
+
if (press in RING_ANGLES) {
|
|
101
|
+
const angle = RING_ANGLES[press];
|
|
102
|
+
if (!RING_STEPS.has(press)) {
|
|
103
|
+
const turned = turnedFacing(facing, angle);
|
|
104
|
+
return turned ? { facing: turned } : null;
|
|
105
|
+
}
|
|
106
|
+
const along = angle === 0 ? facing : reverseFacing(facing);
|
|
107
|
+
// An agent facing an intercardinal has no cell straight ahead of it — the
|
|
108
|
+
// grid carries no diagonal exit — so the press holds it where it stands.
|
|
109
|
+
const target = stepCellFrom(cell, along);
|
|
110
|
+
return target ? { cell: target, facing } : { facing };
|
|
111
|
+
}
|
|
112
|
+
const target = stepCellFrom(cell, press);
|
|
113
|
+
if (target) return { cell: target, facing: press };
|
|
114
|
+
return COMPASS_POINTS.includes(press) ? { facing: press } : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** The shortest route from `fromCell` to `toCell` over `factRows`' own exit
|
|
118
|
+
* facts, as `{ cells, directions }` — `cells` runs from one end to the other
|
|
119
|
+
* inclusive, `directions` names one hop each. Null when nothing connects
|
|
120
|
+
* them, so a caller declines visibly rather than drawing a line through a
|
|
121
|
+
* building the board would never let anyone walk through.
|
|
122
|
+
*
|
|
123
|
+
* The exit table is the one legality answer, the same rows the engine's own
|
|
124
|
+
* chase and forage searches read, so a route drawn here is a route the world
|
|
125
|
+
* agrees with. */
|
|
126
|
+
export function routeBetweenCells(factRows, fromCell, toCell) {
|
|
127
|
+
const from = parseCellId(fromCell);
|
|
128
|
+
const to = parseCellId(toCell);
|
|
129
|
+
if (!from || !to) return null;
|
|
130
|
+
const found = findActionPath(
|
|
131
|
+
from,
|
|
132
|
+
(searchState) => searchState.x === to.x && searchState.y === to.y,
|
|
133
|
+
gridApplyActions(factRows),
|
|
134
|
+
{ stateKey: pathStateKey },
|
|
135
|
+
);
|
|
136
|
+
if (!found) return null;
|
|
137
|
+
return { cells: found.states.map((s) => cellId(s.x, s.y)), directions: found.actions };
|
|
138
|
+
}
|
|
139
|
+
|
|
81
140
|
/** A live, shared town-square world one visitor watches and talks over.
|
|
82
141
|
* `worldPayload` is `{ name, facts, rules, opening }`, read once at build
|
|
83
142
|
* time the same way every other viz page's world payload is. `agents` is
|
|
@@ -88,15 +147,14 @@ export function townSquareRosterArgs(agents, roleOf) {
|
|
|
88
147
|
* (`runTownSquareTick`) and its conversation is a single shared dock.
|
|
89
148
|
*
|
|
90
149
|
* Returns `{ memoryDir, codeGraph, graph, refreshGraph, turn, tick, board,
|
|
91
|
-
* snapshot, applyEdit, placeFood }`.
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
150
|
+
* snapshot, applyEdit, placeFood, driveAgent, recast }`.
|
|
151
|
+
*
|
|
152
|
+
* `getTeachEnabled` is read fresh on every turn, so a visitor can tick the
|
|
153
|
+
* page's teach box mid-session and have the very next line read as a fact to
|
|
154
|
+
* store rather than a command to run. */
|
|
155
|
+
export async function createMudiiiSession(
|
|
156
|
+
worldPayload, { agents = [], epoch = 0, getTeachEnabled = () => false } = {},
|
|
157
|
+
) {
|
|
100
158
|
// Every engine call is layout-scoped: the world pack ships the BOARD, and
|
|
101
159
|
// the layout carries the geometry plus the cast counts the engine mints the
|
|
102
160
|
// animals from. Resolved once here so no call site has to remember it.
|
|
@@ -135,8 +193,11 @@ export async function createMudiiiSession(worldPayload, { agents = [], epoch = 0
|
|
|
135
193
|
|
|
136
194
|
const turnSession = createTurnSession({
|
|
137
195
|
memoryDir, graph: codeGraph, lexicon, sessionId: "town-square",
|
|
138
|
-
vocabHint: 'Try "@fox
|
|
139
|
-
buildExtraOptions: () => ({
|
|
196
|
+
vocabHint: 'Try "@fox the goblin is east", or "what does the fox see".',
|
|
197
|
+
buildExtraOptions: () => ({
|
|
198
|
+
planState: planHolder.state,
|
|
199
|
+
gameConfig: { ...DEFAULT_GAME_CONFIG, mudiii: { ...DEFAULT_GAME_CONFIG.mudiii, teach: getTeachEnabled() } },
|
|
200
|
+
}),
|
|
140
201
|
captureExtraState: async (result, state) => {
|
|
141
202
|
if ("planState" in result) planHolder.state = state.planState;
|
|
142
203
|
},
|
|
@@ -150,6 +211,56 @@ export async function createMudiiiSession(worldPayload, { agents = [], epoch = 0
|
|
|
150
211
|
return runTownSquareTick(memoryDir, { layout });
|
|
151
212
|
}
|
|
152
213
|
|
|
214
|
+
/** Drive one agent by hand for a turn — `driveAgent("fox-1", "up")`, where
|
|
215
|
+
* `direction` is a ring press or a compass point and `driveRequest` above
|
|
216
|
+
* says what each one means.
|
|
217
|
+
*
|
|
218
|
+
* A press goes through the SAME whole-world tick every other turn goes
|
|
219
|
+
* through: it spends a turn, the ecology pass runs, and every other agent
|
|
220
|
+
* decides and moves. So the return IS a tick payload, with one field added:
|
|
221
|
+
* `driven` says whether the world took the press and where the agent ended
|
|
222
|
+
* up. A press the board refuses — a wall, a prop, an agent that is not on
|
|
223
|
+
* the board — still advances the world, and that agent decides for itself
|
|
224
|
+
* this turn rather than freezing. */
|
|
225
|
+
async function driveAgent(agentId, direction) {
|
|
226
|
+
const state = foldTownSquareState(readFactRows(await loadMemory(memoryDir)));
|
|
227
|
+
const standing = state.removed.has(agentId) ? null : state.placements.get(agentId);
|
|
228
|
+
const facing = state.facing.get(agentId)?.value ?? DEFAULT_FACING;
|
|
229
|
+
const request = standing ? driveRequest(direction, { cell: standing.cell, facing }) : null;
|
|
230
|
+
const result = await runTownSquareTick(memoryDir, {
|
|
231
|
+
layout, manualMoves: request ? { [agentId]: request } : {},
|
|
232
|
+
});
|
|
233
|
+
const accepted = result.rungs[agentId] === "driven";
|
|
234
|
+
const after = result.agents[agentId] ?? null;
|
|
235
|
+
return {
|
|
236
|
+
...result,
|
|
237
|
+
driven: {
|
|
238
|
+
agent: agentId,
|
|
239
|
+
direction: String(direction ?? ""),
|
|
240
|
+
accepted,
|
|
241
|
+
from: standing?.cell ?? null,
|
|
242
|
+
cell: accepted && after ? after.cell : null,
|
|
243
|
+
facing: accepted && after ? after.facing : null,
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Re-cast this same store onto a fresh epoch and hand back the opening
|
|
249
|
+
* board. This is what a Reset means once a store is live: the world's own
|
|
250
|
+
* facts, everything taught into it and everything the editor changed all
|
|
251
|
+
* stand, and only the animals are minted again. Re-opening a whole session
|
|
252
|
+
* instead would throw away the taught facts along with the cast, which is
|
|
253
|
+
* not what "reset the board" says.
|
|
254
|
+
*
|
|
255
|
+
* `agents` sizes the new cast the same way `createMudiiiSession`'s own
|
|
256
|
+
* roster does; `epoch` defaults to one past whatever the store is on. */
|
|
257
|
+
async function recast({ agents: nextAgents = [], epoch: nextEpoch = null } = {}) {
|
|
258
|
+
await recastTownSquare(memoryDir, {
|
|
259
|
+
layout, epoch: nextEpoch, ...townSquareRosterArgs(nextAgents, (id) => roleOfId(id)),
|
|
260
|
+
});
|
|
261
|
+
return townSquareBoard(memoryDir, { layout });
|
|
262
|
+
}
|
|
263
|
+
|
|
153
264
|
/** The board as it stands, in the same payload shape `tick` returns, with no
|
|
154
265
|
* turn spent. A page opens a session and then draws THIS — otherwise its
|
|
155
266
|
* first sight of where anything stands is the first tick, and every mesh
|
|
@@ -201,6 +312,8 @@ export async function createMudiiiSession(worldPayload, { agents = [], epoch = 0
|
|
|
201
312
|
refreshGraph,
|
|
202
313
|
turn: turnSession.turn,
|
|
203
314
|
tick,
|
|
315
|
+
driveAgent,
|
|
316
|
+
recast,
|
|
204
317
|
board,
|
|
205
318
|
snapshot,
|
|
206
319
|
applyEdit,
|
|
@@ -220,5 +333,12 @@ publishTmctSurface({
|
|
|
220
333
|
plan: enginePlan,
|
|
221
334
|
page: {
|
|
222
335
|
pickMudiiiRoster,
|
|
336
|
+
// pillsForMudiii closes over two other modules' bindings, so the page
|
|
337
|
+
// reaches it through this bag rather than a `.toString()` splice.
|
|
338
|
+
pillsForMudiii,
|
|
339
|
+
// What the editor's own suggestion rail reads for the word under the
|
|
340
|
+
// cursor: the lateral SKOS neighbourhood and the vertical is-a chain.
|
|
341
|
+
relatedForTerm, classAncestorChain,
|
|
342
|
+
routeBetweenCells,
|
|
223
343
|
},
|
|
224
344
|
});
|