@polycode-projects/the-mechanical-code-talker 5.0.4 → 5.0.5
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/package.json +1 -1
- package/src/adapters/memory/core.mjs +53 -0
- package/src/domain/game-config.mjs +6 -0
- package/src/domain/memory/causal-stability.mjs +82 -0
- package/src/domain/town-square-world.mjs +36 -0
- package/src/services/mudiii-turn.mjs +250 -1
- package/src/services/mudiii-viz.mjs +60 -45
- package/src/services/predator-prey.mjs +68 -7
- package/src/surfaces/web/memory-ask-browser.bundle.js +91 -91
- package/src/surfaces/web/mudiii-browser-entry.mjs +86 -2
|
@@ -21,7 +21,9 @@ import { parseEntities } from "../../domain/codegraph.mjs";
|
|
|
21
21
|
import { memoryFactGraphPayload } from "../../domain/memory-facts.mjs";
|
|
22
22
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
23
23
|
import { worldProvenanceTag } from "../../domain/worlds-pack.mjs";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
COMPASS_POINTS, DEFAULT_FACING, layoutNamed, reverseFacing, stepCellFrom, turnedFacing,
|
|
26
|
+
} from "../../domain/town-square-world.mjs";
|
|
25
27
|
import { parseMudEditorText, planMudEditorSync, gridWorldEditorState } from "../../services/mud-editor.mjs";
|
|
26
28
|
import { createTurnSession } from "./turn-session.mjs";
|
|
27
29
|
import { publishTmctSurface } from "./tmct-surface.mjs";
|
|
@@ -78,6 +80,53 @@ export function townSquareRosterArgs(agents, roleOf) {
|
|
|
78
80
|
return { predatorCount, preyCount };
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
// Where each press on the page's ring of controls sits relative to the agent's
|
|
84
|
+
// own facing. Up and down are steps — straight ahead and straight back — and
|
|
85
|
+
// the other six turn on the spot to the angle they sit at, which is what makes
|
|
86
|
+
// the ring one rule rather than eight cases.
|
|
87
|
+
const RING_ANGLES = Object.freeze({
|
|
88
|
+
up: 0, forward: 0,
|
|
89
|
+
"up-right": 45, right: 90, "down-right": 135,
|
|
90
|
+
down: 180, back: 180,
|
|
91
|
+
"down-left": -135, left: -90, "up-left": -45,
|
|
92
|
+
});
|
|
93
|
+
const RING_STEPS = new Set(["up", "forward", "down", "back"]);
|
|
94
|
+
|
|
95
|
+
/** The `manualMoves` entry one press asks of an agent standing at `cell` and
|
|
96
|
+
* facing `facing`: `{ cell, facing }` for a step, `{ facing }` alone for a
|
|
97
|
+
* turn on the spot. Null when `direction` names neither a ring press nor a
|
|
98
|
+
* compass point.
|
|
99
|
+
*
|
|
100
|
+
* `direction` is either a ring press — "up"/"forward" and "down"/"back" step
|
|
101
|
+
* straight ahead and straight back with the facing left alone, "left" and
|
|
102
|
+
* "right" turn ninety degrees, and the four diagonals turn to the angle they
|
|
103
|
+
* sit at (forty-five for the top pair, a hundred and thirty-five for the
|
|
104
|
+
* bottom) — or an absolute compass point, where a cardinal steps that way and
|
|
105
|
+
* faces that way and an intercardinal turns on the spot to face it.
|
|
106
|
+
*
|
|
107
|
+
* Whether the cell it names can be entered is the engine's answer rather than
|
|
108
|
+
* this function's: there is one legality table, and it is the world's own
|
|
109
|
+
* exit facts. Pure. */
|
|
110
|
+
export function driveRequest(direction, { cell, facing = DEFAULT_FACING } = {}) {
|
|
111
|
+
const press = String(direction ?? "").trim().toLowerCase();
|
|
112
|
+
if (!press) return null;
|
|
113
|
+
if (press in RING_ANGLES) {
|
|
114
|
+
const angle = RING_ANGLES[press];
|
|
115
|
+
if (!RING_STEPS.has(press)) {
|
|
116
|
+
const turned = turnedFacing(facing, angle);
|
|
117
|
+
return turned ? { facing: turned } : null;
|
|
118
|
+
}
|
|
119
|
+
const along = angle === 0 ? facing : reverseFacing(facing);
|
|
120
|
+
// An agent facing an intercardinal has no cell straight ahead of it — the
|
|
121
|
+
// grid carries no diagonal exit — so the press holds it where it stands.
|
|
122
|
+
const target = stepCellFrom(cell, along);
|
|
123
|
+
return target ? { cell: target, facing } : { facing };
|
|
124
|
+
}
|
|
125
|
+
const target = stepCellFrom(cell, press);
|
|
126
|
+
if (target) return { cell: target, facing: press };
|
|
127
|
+
return COMPASS_POINTS.includes(press) ? { facing: press } : null;
|
|
128
|
+
}
|
|
129
|
+
|
|
81
130
|
/** A live, shared town-square world one visitor watches and talks over.
|
|
82
131
|
* `worldPayload` is `{ name, facts, rules, opening }`, read once at build
|
|
83
132
|
* time the same way every other viz page's world payload is. `agents` is
|
|
@@ -88,7 +137,7 @@ export function townSquareRosterArgs(agents, roleOf) {
|
|
|
88
137
|
* (`runTownSquareTick`) and its conversation is a single shared dock.
|
|
89
138
|
*
|
|
90
139
|
* Returns `{ memoryDir, codeGraph, graph, refreshGraph, turn, tick, board,
|
|
91
|
-
* snapshot, applyEdit, placeFood }`. A reset is not a method here: the
|
|
140
|
+
* snapshot, applyEdit, placeFood, driveAgent }`. A reset is not a method here: the
|
|
92
141
|
* page re-opens a whole session for it, which is what a reset means when the
|
|
93
142
|
* store is in memory and belongs to one visitor. */
|
|
94
143
|
export async function createMudiiiSession(worldPayload, { agents = [], epoch = 0 } = {}) {
|
|
@@ -150,6 +199,40 @@ export async function createMudiiiSession(worldPayload, { agents = [], epoch = 0
|
|
|
150
199
|
return runTownSquareTick(memoryDir, { layout });
|
|
151
200
|
}
|
|
152
201
|
|
|
202
|
+
/** Drive one agent by hand for a turn — `driveAgent("fox-1", "up")`, where
|
|
203
|
+
* `direction` is a ring press or a compass point and `driveRequest` above
|
|
204
|
+
* says what each one means.
|
|
205
|
+
*
|
|
206
|
+
* A press goes through the SAME whole-world tick every other turn goes
|
|
207
|
+
* through: it spends a turn, the ecology pass runs, and every other agent
|
|
208
|
+
* decides and moves. So the return IS a tick payload, with one field added:
|
|
209
|
+
* `driven` says whether the world took the press and where the agent ended
|
|
210
|
+
* up. A press the board refuses — a wall, a prop, an agent that is not on
|
|
211
|
+
* the board — still advances the world, and that agent decides for itself
|
|
212
|
+
* this turn rather than freezing. */
|
|
213
|
+
async function driveAgent(agentId, direction) {
|
|
214
|
+
const state = foldTownSquareState(readFactRows(await loadMemory(memoryDir)));
|
|
215
|
+
const standing = state.removed.has(agentId) ? null : state.placements.get(agentId);
|
|
216
|
+
const facing = state.facing.get(agentId)?.value ?? DEFAULT_FACING;
|
|
217
|
+
const request = standing ? driveRequest(direction, { cell: standing.cell, facing }) : null;
|
|
218
|
+
const result = await runTownSquareTick(memoryDir, {
|
|
219
|
+
layout, manualMoves: request ? { [agentId]: request } : {},
|
|
220
|
+
});
|
|
221
|
+
const accepted = result.rungs[agentId] === "driven";
|
|
222
|
+
const after = result.agents[agentId] ?? null;
|
|
223
|
+
return {
|
|
224
|
+
...result,
|
|
225
|
+
driven: {
|
|
226
|
+
agent: agentId,
|
|
227
|
+
direction: String(direction ?? ""),
|
|
228
|
+
accepted,
|
|
229
|
+
from: standing?.cell ?? null,
|
|
230
|
+
cell: accepted && after ? after.cell : null,
|
|
231
|
+
facing: accepted && after ? after.facing : null,
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
153
236
|
/** The board as it stands, in the same payload shape `tick` returns, with no
|
|
154
237
|
* turn spent. A page opens a session and then draws THIS — otherwise its
|
|
155
238
|
* first sight of where anything stands is the first tick, and every mesh
|
|
@@ -201,6 +284,7 @@ export async function createMudiiiSession(worldPayload, { agents = [], epoch = 0
|
|
|
201
284
|
refreshGraph,
|
|
202
285
|
turn: turnSession.turn,
|
|
203
286
|
tick,
|
|
287
|
+
driveAgent,
|
|
204
288
|
board,
|
|
205
289
|
snapshot,
|
|
206
290
|
applyEdit,
|