@polycode-projects/the-mechanical-code-talker 2.7.3 → 2.7.4
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 +2 -1
- package/src/domain/sprite-map.mjs +131 -0
- package/src/services/adventure.mjs +7 -0
- package/src/services/chat.mjs +25 -0
- package/src/services/spider-fly-turn.mjs +352 -0
- package/src/services/spider-fly-viz.mjs +492 -0
- package/src/services/spider-fly.mjs +491 -0
- package/src/services/viz-ticker.mjs +119 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +29 -0
- package/src/surfaces/web/spider-fly-browser-entry.mjs +152 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// spider-fly-browser-entry.mjs — the esbuild entry for the spider-and-fly
|
|
2
|
+
// full-screen/home-page pages (public/spider-fly-browser.bundle.js, built by
|
|
3
|
+
// scripts/build-spider-fly-bundle.mjs).
|
|
4
|
+
//
|
|
5
|
+
// Exposes ONE session factory over the real engine, `createSpiderFlySession`,
|
|
6
|
+
// mirroring chat-browser-entry.mjs's `createChatSession` shape exactly (same
|
|
7
|
+
// underlying runTurn, same minus-every-filesystem-side-effect posture — no
|
|
8
|
+
// transcript log, no sidecar, no graph upsert) with two additions specific to
|
|
9
|
+
// this game's rendering needs:
|
|
10
|
+
//
|
|
11
|
+
// - `session.tick()` runs ONE real engine turn directly
|
|
12
|
+
// (spider-fly.mjs's runSpiderFlyTick), unmediated by chat text, so the
|
|
13
|
+
// page's own ticker gets back the structured `{ turn, agents, ecology }`
|
|
14
|
+
// shape it needs to redraw the board and the HUD's goal lines. This is
|
|
15
|
+
// the "page's ticker calls a turn" half of the brief.
|
|
16
|
+
// - `session.turn(line)` runs the FULL chat turn engine (chat.mjs's
|
|
17
|
+
// runTurn — the exact dispatch the CLI and the home page's own chat run,
|
|
18
|
+
// with the spider-fly lane already wired in), so the in-page chat dock
|
|
19
|
+
// supports the addressed teach-frame ("@spider the fly is east"), the
|
|
20
|
+
// bare "tick" command, and any ordinary fallthrough question ("where is
|
|
21
|
+
// the spider") exactly as the CLI does. This is the "chat dock runs
|
|
22
|
+
// runTurn" half.
|
|
23
|
+
// - `session.snapshot()` is a READ-ONLY fold (spider-fly.mjs's own
|
|
24
|
+
// foldSpiderFlyState, no engine advance) so the page can resync agent
|
|
25
|
+
// positions after a CHAT-driven tick/address turn (whose reply is text,
|
|
26
|
+
// not the structured tick() shape) without double-advancing the turn.
|
|
27
|
+
// It carries no `.goal` — the chat reply text itself already narrates
|
|
28
|
+
// that turn's outcome; only a raw tick() refreshes the HUD's goal lines.
|
|
29
|
+
//
|
|
30
|
+
// tick(), turn() and snapshot() all read/write the SAME in-memory store
|
|
31
|
+
// (`memoryDir`), so the board and the chat dock never disagree about the
|
|
32
|
+
// game's state. A caller that lets a play button and a chat submit fire
|
|
33
|
+
// concurrently must serialize its own calls against this session — this
|
|
34
|
+
// module runs each call to completion but does not queue overlapping ones
|
|
35
|
+
// itself (see spider-fly-viz.mjs's own inlined `withLock` wrapper).
|
|
36
|
+
//
|
|
37
|
+
// The world bootstrap never touches the worlds-pack fetch/provider machinery
|
|
38
|
+
// spider-fly-turn.mjs's openSpiderFlyGame uses (that path needs a Node fs
|
|
39
|
+
// read or a registered fetch provider, neither of which this bundle carries):
|
|
40
|
+
// spider-fly-world.mjs's worldFactRows()/startSpiderFlyGame are already pure/
|
|
41
|
+
// in-memory, so the browser bootstraps the identical board directly from
|
|
42
|
+
// them. The world's rule rows (worldRuleRows) are skipped on purpose —
|
|
43
|
+
// spider-fly.mjs's own header comment confirms grid movement never reads
|
|
44
|
+
// them back (hand-written pathfinding over has-exit-* facts, not the taught
|
|
45
|
+
// action-rule DSL), so nothing here depends on them being loaded.
|
|
46
|
+
import { runTurn } from "../../services/chat.mjs";
|
|
47
|
+
import {
|
|
48
|
+
createInMemoryStore, normFactTerm, appendFacts, loadMemory, readFactRows,
|
|
49
|
+
} from "../../adapters/memory/core.mjs";
|
|
50
|
+
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
51
|
+
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
52
|
+
import {
|
|
53
|
+
worldFactRows, WORLD_NAME, WORLD_OPENING, cellId, parseCellId, DIRECTION_DELTA, visibleCells,
|
|
54
|
+
} from "../../domain/spider-fly-world.mjs";
|
|
55
|
+
import { foldSpiderFlyState, runSpiderFlyTick, startSpiderFlyGame, DEFAULT_VISION_RADIUS } from "../../services/spider-fly.mjs";
|
|
56
|
+
import { resolveSpriteForClass, SPRITE_REGISTRY } from "../../domain/sprite-map.mjs";
|
|
57
|
+
|
|
58
|
+
/** A live in-memory game the page's ticker and chat dock can both drive.
|
|
59
|
+
* Returns { memoryDir, sessionId, opening, initial, taxonomyRows, tick,
|
|
60
|
+
* turn, snapshot }. `initial` is the freshly-bootstrapped board's starting
|
|
61
|
+
* agents ({ [id]: { cell } }, turn 0, no goal computed yet — the CLI's own
|
|
62
|
+
* opener shows the same static starting board before any real tick runs).
|
|
63
|
+
* `taxonomyRows` is the world's static rdfs:subClassOf rows, for
|
|
64
|
+
* resolveSpriteForClass — immutable for the life of the session, so it is
|
|
65
|
+
* computed once here rather than re-read from memory on every render. */
|
|
66
|
+
export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
67
|
+
const memoryDir = createInMemoryStore();
|
|
68
|
+
const tag = `world:${WORLD_NAME}`;
|
|
69
|
+
const worldRows = [...worldFactRows()];
|
|
70
|
+
await appendFacts(memoryDir, worldRows.map((f) => ({
|
|
71
|
+
subject: f.subject, predicate: f.predicate, object: f.object, provenance: tag,
|
|
72
|
+
})));
|
|
73
|
+
const taxonomyRows = worldRows
|
|
74
|
+
.filter((f) => f.predicate === "rdfs:subClassOf")
|
|
75
|
+
.map((f) => ({ subject: f.subject, predicate: f.predicate, object: f.object }));
|
|
76
|
+
|
|
77
|
+
const { facts: startFacts } = await startSpiderFlyGame(memoryDir, { flyCount });
|
|
78
|
+
const initialAgents = {};
|
|
79
|
+
for (const f of startFacts) {
|
|
80
|
+
if (f.predicate !== "mgx:currently-in") continue;
|
|
81
|
+
initialAgents[f.subject] = { cell: f.object };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const graph = parseEntities({ individuals: [], objectProperties: [] });
|
|
85
|
+
const lexicon = loadLexicon();
|
|
86
|
+
const sessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
87
|
+
|
|
88
|
+
let focus = null;
|
|
89
|
+
let last = null;
|
|
90
|
+
let planState = { spiderFly: { turn: 0 } };
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
memoryDir,
|
|
94
|
+
sessionId,
|
|
95
|
+
opening: WORLD_OPENING,
|
|
96
|
+
initial: { turn: 0, agents: initialAgents },
|
|
97
|
+
taxonomyRows,
|
|
98
|
+
|
|
99
|
+
/** Run one real engine turn directly. Returns spider-fly.mjs's own
|
|
100
|
+
* { turn, agents, ecology } shape unmodified. */
|
|
101
|
+
async tick() {
|
|
102
|
+
const result = await runSpiderFlyTick(memoryDir);
|
|
103
|
+
planState = { spiderFly: { turn: result.turn } };
|
|
104
|
+
return result;
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
/** One dispatched chat turn — the SAME runTurn the CLI and the home
|
|
108
|
+
* page's own chat run, over this session's own memoryDir. A throwing
|
|
109
|
+
* runTurn must never kill the session — the page has no other chance
|
|
110
|
+
* to show this turn's answer. */
|
|
111
|
+
async turn(line) {
|
|
112
|
+
let result;
|
|
113
|
+
try {
|
|
114
|
+
result = await runTurn(line, {
|
|
115
|
+
config: null, source: null, graph, focus, last, memoryDir, sessionId,
|
|
116
|
+
env: {}, lexicon, vocabHint: "", planState,
|
|
117
|
+
});
|
|
118
|
+
} catch (e) {
|
|
119
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
120
|
+
return { answer: `Something went wrong answering that (${message}). Try rephrasing, or /help.`, end: false, record: null, plan: null };
|
|
121
|
+
}
|
|
122
|
+
focus = result.focus;
|
|
123
|
+
last = result.last;
|
|
124
|
+
if ("planState" in result) planState = result.planState;
|
|
125
|
+
return { answer: result.answer, end: Boolean(result.end), record: result.record ?? null, plan: result.plan ?? null };
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
/** A read-only fold of the CURRENT board — no engine advance, no goal
|
|
129
|
+
* lines (see the header comment: only a raw tick() recomputes those).
|
|
130
|
+
* Lets the page resync positions/turn count after a chat-driven tick. */
|
|
131
|
+
async snapshot() {
|
|
132
|
+
const rows = readFactRows(await loadMemory(memoryDir));
|
|
133
|
+
const state = foldSpiderFlyState(rows);
|
|
134
|
+
const agents = {};
|
|
135
|
+
for (const [id, place] of state.placements) {
|
|
136
|
+
if (state.removed.has(id)) continue;
|
|
137
|
+
agents[id] = { cell: place.cell };
|
|
138
|
+
}
|
|
139
|
+
return { turn: state.turnCount, agents };
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// cellId/parseCellId/DIRECTION_DELTA/visibleCells/DEFAULT_VISION_RADIUS are
|
|
145
|
+
// re-exported so the page's own rendering script (spider-fly-viz.mjs) never
|
|
146
|
+
// has to duplicate grid geometry or the vision-radius default: reconstructing
|
|
147
|
+
// a spider's remaining silk-thread path from its returned direction list, and
|
|
148
|
+
// computing the POV overlay's visible-cell mask, both need them.
|
|
149
|
+
globalThis.tmctSpiderFly = {
|
|
150
|
+
createSpiderFlySession, normFactTerm, resolveSpriteForClass, SPRITE_REGISTRY,
|
|
151
|
+
cellId, parseCellId, DIRECTION_DELTA, visibleCells, DEFAULT_VISION_RADIUS,
|
|
152
|
+
};
|