@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.
@@ -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
+ };