@polycode-projects/the-mechanical-code-talker 3.2.0 → 3.3.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.
@@ -0,0 +1,164 @@
1
+ // mud-browser-entry.mjs — the esbuild entry for mud.html's four-character,
2
+ // one-shared-world browser session (public/mud-browser.bundle.js), mirroring
3
+ // adventure-browser-entry.mjs's own session-factory shape. The difference is
4
+ // the whole point of the mud demo: adventure-browser-entry.mjs drives ONE
5
+ // player through one world; this file drives FOUR independent characters
6
+ // through the SAME live world, over the SAME memoryDir.
7
+ //
8
+ // What's shared across all four characters, and why: the store itself
9
+ // (memoryDir) — mole-1's dig must be visible to vole-1's very next look — and
10
+ // ONE planHolder.state, seeded once as "mud-garden is already live" the same
11
+ // way adventure-browser-entry.mjs seeds it, since "is the world open" is a
12
+ // property of the WORLD, not of any one character. What is deliberately NOT
13
+ // shared: each character gets its own `focus`/`last` pronoun-resolution
14
+ // state and its own `visitedRoomIds` set — a window's mid-sentence "it"
15
+ // belongs to that window's own conversation, and fog of war means each
16
+ // character's own discovered-room history is genuinely private, unlike
17
+ // adventure-browser-entry.mjs's single merged exposure set for one player.
18
+ //
19
+ // Two entry points per character, both dispatched over the exact same
20
+ // memoryDir/actingSubject: `turn(line)` runs an ordinary typed chat command
21
+ // through chat.mjs's own runTurn (identical machinery to every other viz
22
+ // page's chat dock); `autoplayTick(k)` runs mud-turn.mjs's runMudTurn — one
23
+ // whole scripted turn (investigate, walk toward known food, dig at the
24
+ // edge). The caller (mud-viz.mjs's own inlined script) is responsible for
25
+ // SERIALIZING ticks across the four characters when more than one window is
26
+ // auto-playing at once — this file makes no ordering promise between two
27
+ // concurrent calls into the same memoryDir, the same way two callers writing
28
+ // into any shared store concurrently would need their own queue.
29
+ import { runTurn } from "../../services/chat.mjs";
30
+ import { createInMemoryStore, appendFacts, appendRule, loadMemory, readFactRows } from "../../adapters/memory/core.mjs";
31
+ import { parseEntities } from "../../domain/codegraph.mjs";
32
+ import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
33
+ import {
34
+ foldWorldState, worldActionRows, worldDigestRows, roomAffordances,
35
+ personKnowledgeLines, personKnownFoodLines,
36
+ } from "../../services/adventure.mjs";
37
+ import { runMudTurn } from "../../services/mud-turn.mjs";
38
+ import { worldProvenanceTag } from "../../domain/worlds-pack.mjs";
39
+ import { resolveSpriteForClass, SPRITE_REGISTRY, classAncestorChain } from "../../domain/sprite-map.mjs";
40
+ import { resolveSpriteAsset } from "../../domain/sprite-templates.mjs";
41
+
42
+ /** A live, shared mud world four characters can each act in. `worldPayload`
43
+ * is `{ name, facts, rules, opening }` — the same shape adventure-browser-
44
+ * entry.mjs's own worldPayload takes, read once at build time through the
45
+ * real Node worlds-pack provider (see mud-viz.mjs's header for why: the
46
+ * world's canonical source is a Node-only gzipped JSONL shard the browser
47
+ * cannot read). `characters` is the roster this page drives (e.g.
48
+ * `["mole-1", "vole-1", "badger-1", "groundhog-1"]`) — every character
49
+ * already placed by the world's own seed facts.
50
+ *
51
+ * Returns `{ memoryDir, windows, snapshot }`. `windows` is a plain object
52
+ * keyed by character id, each value `{ character, turn, autoplayTick,
53
+ * visitedRoomIds }`. `snapshot()` is the one OMNISCIENT read this module
54
+ * exposes — the central world map's own data source, never a per-window
55
+ * one. */
56
+ export async function createMudSession(worldPayload, { characters = [] } = {}) {
57
+ const memoryDir = createInMemoryStore();
58
+ const tag = worldProvenanceTag(worldPayload.name);
59
+ await appendFacts(memoryDir, worldPayload.facts.map((f) => ({
60
+ subject: f.subject, predicate: f.predicate, object: f.object, provenance: tag,
61
+ })));
62
+ for (const rule of worldPayload.rules) {
63
+ await appendRule(memoryDir, { name: rule.name, kind: rule.ruleKind, slots: rule.slots, provenance: tag });
64
+ }
65
+
66
+ // ONE holder, shared by every character's runTurn call — "mud-garden is
67
+ // already live" is true for the whole world at once, the same reason
68
+ // adventure-browser-entry.mjs seeds its own single-player planHolder this
69
+ // way rather than through the "play <world>" opener (which needs a
70
+ // shipped "player" individual mud-garden deliberately has none of).
71
+ const planHolder = { state: { adventure: { world: worldPayload.name } } };
72
+ const graph = parseEntities({ individuals: [], objectProperties: [] });
73
+ const lexicon = loadLexicon();
74
+
75
+ async function roomOf(character) {
76
+ const rows = readFactRows(await loadMemory(memoryDir));
77
+ const state = foldWorldState(worldActionRows(rows));
78
+ return state.placements.get(character)?.object ?? null;
79
+ }
80
+
81
+ const windows = {};
82
+ for (const character of characters) {
83
+ // Deliberately per-closure, never on a shared object: a window's own
84
+ // "it"/"there" belongs to that window's own conversation, and its own
85
+ // discovered-room history is the real fog of war this page promises —
86
+ // sharing either across characters would leak one window's state into
87
+ // another's.
88
+ let focus = null;
89
+ let last = null;
90
+ const visitedRoomIds = new Set();
91
+ const startRoom = await roomOf(character);
92
+ if (startRoom) visitedRoomIds.add(startRoom);
93
+
94
+ windows[character] = {
95
+ character,
96
+
97
+ /** One typed chat command, dispatched exactly like every other viz
98
+ * page's chat dock — the same runTurn the CLI runs, scoped to this
99
+ * character via actingSubject. A throwing runTurn must never end the
100
+ * session; this window has no other chance to show this turn's
101
+ * answer. */
102
+ async turn(line) {
103
+ let result;
104
+ try {
105
+ result = await runTurn(line, {
106
+ config: null, source: null, graph, focus, last, memoryDir,
107
+ sessionId: character, env: {}, lexicon, uiContext: "browser",
108
+ actingSubject: character, planState: planHolder.state,
109
+ vocabHint: 'Try a world command ("dig north", "eat the carrot-1"), or ask "what food do you know about".',
110
+ });
111
+ } catch (e) {
112
+ const message = e instanceof Error ? e.message : String(e);
113
+ return { answer: `Something went wrong answering that (${message}). Try rephrasing.`, end: false };
114
+ }
115
+ focus = result.focus;
116
+ last = result.last;
117
+ if ("planState" in result) planHolder.state = result.planState;
118
+ const here = await roomOf(character);
119
+ if (here) visitedRoomIds.add(here);
120
+ return { answer: result.answer, end: Boolean(result.end) };
121
+ },
122
+
123
+ /** One whole scripted turn (mud-turn.mjs's runMudTurn): investigate,
124
+ * walk toward known food, or roll at the edge (dig). `k` is the turn
125
+ * ordinal the caller drives — mud-viz.mjs's own global turn counter,
126
+ * so every character's turn lands on a distinct, strictly increasing
127
+ * number regardless of which window fired it. Returns runMudTurn's
128
+ * own `{ character, k, room, roomAfter, actions, learned, text,
129
+ * note }` unmodified, so the caller can render the speech-bubble/
130
+ * dig-flourish triggers straight off `actions`. */
131
+ async autoplayTick(k) {
132
+ const result = await runMudTurn(character, { world: worldPayload.name, memoryDir, env: {}, graph, k });
133
+ if (result.roomAfter) visitedRoomIds.add(result.roomAfter);
134
+ return result;
135
+ },
136
+
137
+ /** This character's own discovered-room history — real fog of war,
138
+ * never merged with a sibling window's. */
139
+ visitedRoomIds: () => [...visitedRoomIds],
140
+ };
141
+ }
142
+
143
+ /** The one OMNISCIENT read this module exposes: every room, every
144
+ * character, every level, no fog of war — the central world map's own
145
+ * data source. Never call this for a per-window room view; use
146
+ * worldDigestRows/roomAffordances against ONE room instead. */
147
+ async function snapshot() {
148
+ const rows = readFactRows(await loadMemory(memoryDir));
149
+ const state = foldWorldState(worldActionRows(rows));
150
+ return { rows, state };
151
+ }
152
+
153
+ return { memoryDir, windows, snapshot };
154
+ }
155
+
156
+ // Re-exported so mud-viz.mjs's own inlined script never duplicates sprite
157
+ // resolution or the digest/affordance/knowledge readers its room view and
158
+ // chat pills already need — the same reach-through-the-global posture
159
+ // adventure-browser-entry.mjs's own globalThis.tmctAdventure takes.
160
+ globalThis.tmctMud = {
161
+ createMudSession, resolveSpriteForClass, SPRITE_REGISTRY, classAncestorChain, resolveSpriteAsset,
162
+ foldWorldState, worldActionRows, worldDigestRows, roomAffordances,
163
+ personKnowledgeLines, personKnownFoodLines,
164
+ };