@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.
- package/corpus/sprites/src/sprite-facts.jsonl +10 -0
- package/corpus/tier2/generate.mjs +10 -1
- package/corpus/tier2/human.jsonl +23 -0
- package/corpus/tier2/manifest.json +3 -3
- package/corpus/worlds/index.json.gz +0 -0
- package/corpus/worlds/manifest.json +15 -5
- package/corpus/worlds/shards/mud-garden.jsonl.gz +0 -0
- package/corpus/worlds/src/mud-garden.jsonl +68 -0
- package/package.json +2 -1
- package/src/domain/game-config.mjs +46 -0
- package/src/domain/grammar/ace.mjs +11 -3
- package/src/domain/grammar/lexicon-core.json +3 -0
- package/src/domain/memory/trust.mjs +13 -0
- package/src/services/adventure.mjs +351 -62
- package/src/services/chat-session.mjs +28 -3
- package/src/services/chat.mjs +2 -2
- package/src/services/mud-turn.mjs +395 -0
- package/src/services/mud-viz.mjs +752 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +60 -60
- package/src/surfaces/web/mud-browser-entry.mjs +164 -0
|
@@ -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
|
+
};
|