@polycode-projects/the-mechanical-code-talker 3.2.0 → 4.0.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 +28 -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 +101 -0
- package/package.json +2 -1
- package/src/adapters/p2p/webrtc-transport.mjs +146 -0
- package/src/domain/game-config.mjs +67 -0
- package/src/domain/grammar/ace.mjs +11 -3
- package/src/domain/grammar/lexicon-core.json +3 -0
- package/src/domain/grammar/lexicon.mjs +13 -0
- package/src/domain/memory/trust.mjs +15 -0
- package/src/domain/p2p/facts.mjs +81 -0
- package/src/domain/p2p/peer-id.mjs +32 -0
- package/src/domain/p2p/provenance-relabel.mjs +26 -0
- package/src/domain/p2p/sync-filter.mjs +31 -0
- package/src/domain/p2p/wire.mjs +123 -0
- package/src/domain/sprite-map.mjs +10 -2
- package/src/services/adventure-editor.mjs +10 -2
- package/src/services/adventure-viz.mjs +192 -39
- package/src/services/adventure.mjs +989 -69
- package/src/services/chat-page-viz.mjs +1060 -7
- package/src/services/chat-session.mjs +28 -3
- package/src/services/chat.mjs +2 -2
- package/src/services/mud-editor.mjs +313 -0
- package/src/services/mud-turn.mjs +572 -0
- package/src/services/mud-viz.mjs +2055 -0
- package/src/services/p2p-room.mjs +559 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +77 -77
- package/src/surfaces/web/mud-browser-entry.mjs +330 -0
- package/src/surfaces/web/p2p-browser-entry.mjs +39 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
// mud-browser-entry.mjs — the esbuild entry for mud.html's multi-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 SEVERAL independent characters
|
|
6
|
+
// through the SAME live world, over the SAME memoryDir. Which of the world's
|
|
7
|
+
// animals are played is the caller's choice (pickMudRoster draws them fresh
|
|
8
|
+
// each reset), never a fixed pair baked in here.
|
|
9
|
+
//
|
|
10
|
+
// What's shared across every character, and why: the store itself
|
|
11
|
+
// (memoryDir) — mole-1's dig must be visible to vole-1's very next look — and
|
|
12
|
+
// ONE planHolder.state, seeded once as "mud-garden is already live" the same
|
|
13
|
+
// way adventure-browser-entry.mjs seeds it, since "is the world open" is a
|
|
14
|
+
// property of the WORLD, not of any one character. What is deliberately NOT
|
|
15
|
+
// shared: each character gets its own `focus`/`last` pronoun-resolution
|
|
16
|
+
// state and its own `visitedRoomIds` set — a window's mid-sentence "it"
|
|
17
|
+
// belongs to that window's own conversation, and fog of war means each
|
|
18
|
+
// character's own discovered-room history is genuinely private, unlike
|
|
19
|
+
// adventure-browser-entry.mjs's single merged exposure set for one player.
|
|
20
|
+
//
|
|
21
|
+
// Two entry points per character, both dispatched over the exact same
|
|
22
|
+
// memoryDir/actingSubject: `turn(line)` runs an ordinary typed chat command
|
|
23
|
+
// through chat.mjs's own runTurn (identical machinery to every other viz
|
|
24
|
+
// page's chat dock); `autoplayTick(k)` runs mud-turn.mjs's runMudTurn — one
|
|
25
|
+
// whole scripted turn (investigate, walk toward known food, dig at the
|
|
26
|
+
// edge). The caller (mud-viz.mjs's own inlined script) is responsible for
|
|
27
|
+
// SERIALIZING ticks across the characters when more than one window is
|
|
28
|
+
// auto-playing at once — this file makes no ordering promise between two
|
|
29
|
+
// concurrent calls into the same memoryDir, the same way two callers writing
|
|
30
|
+
// into any shared store concurrently would need their own queue.
|
|
31
|
+
import { runTurn } from "../../services/chat.mjs";
|
|
32
|
+
import {
|
|
33
|
+
createInMemoryStore, appendFacts, appendRule, loadMemory, readFactRows, removeFacts,
|
|
34
|
+
} from "../../adapters/memory/core.mjs";
|
|
35
|
+
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
36
|
+
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
37
|
+
import {
|
|
38
|
+
foldWorldState, worldActionRows, worldDigestRows, roomAffordances,
|
|
39
|
+
personKnowledgeLines, personKnownFoodLines,
|
|
40
|
+
diggableDirections, castInRoom, displayNameOf, isOutOfPlay, outOfPlayReasonOf, outOfPlayPhrase,
|
|
41
|
+
roomKindOf,
|
|
42
|
+
} from "../../services/adventure.mjs";
|
|
43
|
+
import { relatedForTerm } from "../../domain/skos-view.mjs";
|
|
44
|
+
import { runMudTurn } from "../../services/mud-turn.mjs";
|
|
45
|
+
import { parseMudEditorText, planMudEditorSync } from "../../services/mud-editor.mjs";
|
|
46
|
+
import { mudSpeciesOf } from "../../domain/game-config.mjs";
|
|
47
|
+
import { worldProvenanceTag } from "../../domain/worlds-pack.mjs";
|
|
48
|
+
import { resolveSpriteForClass, SPRITE_REGISTRY, classAncestorChain } from "../../domain/sprite-map.mjs";
|
|
49
|
+
import { resolveSpriteAsset } from "../../domain/sprite-templates.mjs";
|
|
50
|
+
|
|
51
|
+
/** A live, shared mud world several characters can each act in. `worldPayload`
|
|
52
|
+
* is `{ name, facts, rules, opening }` — the same shape adventure-browser-
|
|
53
|
+
* entry.mjs's own worldPayload takes, read once at build time through the
|
|
54
|
+
* real Node worlds-pack provider (see mud-viz.mjs's header for why: the
|
|
55
|
+
* world's canonical source is a Node-only gzipped JSONL shard the browser
|
|
56
|
+
* cannot read). `characters` is the roster this page drives (e.g. the two
|
|
57
|
+
* ids pickMudRoster drew this reset). The world is opened for exactly that
|
|
58
|
+
* list (worldFactsForCast, below): more animals than mud-garden hand-authors
|
|
59
|
+
* are minted, fewer leaves the ones nobody is playing out of the world
|
|
60
|
+
* altogether.
|
|
61
|
+
*
|
|
62
|
+
* Returns `{ memoryDir, windows, snapshot }`. `windows` is a plain object
|
|
63
|
+
* keyed by character id, each value `{ character, turn, autoplayTick,
|
|
64
|
+
* visitedRoomIds, turnsTaken, isOutOfPlay, outOfPlayReason }`. `snapshot()` is
|
|
65
|
+
* the one OMNISCIENT read this module exposes — the central world map's own
|
|
66
|
+
* data source, never a per-window one. */
|
|
67
|
+
export async function createMudSession(worldPayload, { characters = [] } = {}) {
|
|
68
|
+
const memoryDir = createInMemoryStore();
|
|
69
|
+
const tag = worldProvenanceTag(worldPayload.name);
|
|
70
|
+
const seedFacts = worldFactsForCast(worldPayload.facts, characters);
|
|
71
|
+
await appendFacts(memoryDir, seedFacts.map((f) => ({
|
|
72
|
+
subject: f.subject, predicate: f.predicate, object: f.object, provenance: tag,
|
|
73
|
+
})));
|
|
74
|
+
for (const rule of worldPayload.rules) {
|
|
75
|
+
await appendRule(memoryDir, { name: rule.name, kind: rule.ruleKind, slots: rule.slots, provenance: tag });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ONE holder, shared by every character's runTurn call — "mud-garden is
|
|
79
|
+
// already live" is true for the whole world at once, the same reason
|
|
80
|
+
// adventure-browser-entry.mjs seeds its own single-player planHolder this
|
|
81
|
+
// way rather than through the "play <world>" opener (which needs a
|
|
82
|
+
// shipped "player" individual mud-garden deliberately has none of).
|
|
83
|
+
const planHolder = { state: { adventure: { world: worldPayload.name } } };
|
|
84
|
+
const graph = parseEntities({ individuals: [], objectProperties: [] });
|
|
85
|
+
// The world's own minted ids ("groundhog-1", "carrot-2") are declared as
|
|
86
|
+
// vocabulary inside the adventure lane itself, for the length of one world
|
|
87
|
+
// command — see adventure.mjs's own worldLexicon. This page hands over the
|
|
88
|
+
// plain core lexicon and lets the lane do it.
|
|
89
|
+
const lexicon = loadLexicon();
|
|
90
|
+
|
|
91
|
+
async function readWorld() {
|
|
92
|
+
const rows = readFactRows(await loadMemory(memoryDir));
|
|
93
|
+
return { rows, state: foldWorldState(worldActionRows(rows)) };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function roomOf(character) {
|
|
97
|
+
const { state } = await readWorld();
|
|
98
|
+
return state.placements.get(character)?.object ?? null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const windows = {};
|
|
102
|
+
for (const character of characters) {
|
|
103
|
+
// Deliberately per-closure, never on a shared object: a window's own
|
|
104
|
+
// "it"/"there" belongs to that window's own conversation, and its own
|
|
105
|
+
// discovered-room history is the real fog of war this page promises —
|
|
106
|
+
// sharing either across characters would leak one window's state into
|
|
107
|
+
// another's.
|
|
108
|
+
let focus = null;
|
|
109
|
+
let last = null;
|
|
110
|
+
const visitedRoomIds = new Set();
|
|
111
|
+
// This character's OWN turns, not the page's shared tick counter: two
|
|
112
|
+
// windows playing at different speeds, or one paused while the other
|
|
113
|
+
// runs, have genuinely different counts, and showing the shared one under
|
|
114
|
+
// both animals says something untrue about each.
|
|
115
|
+
let turnsTaken = 0;
|
|
116
|
+
const startRoom = await roomOf(character);
|
|
117
|
+
if (startRoom) visitedRoomIds.add(startRoom);
|
|
118
|
+
|
|
119
|
+
windows[character] = {
|
|
120
|
+
character,
|
|
121
|
+
|
|
122
|
+
/** One typed chat command, dispatched exactly like every other viz
|
|
123
|
+
* page's chat dock — the same runTurn the CLI runs, scoped to this
|
|
124
|
+
* character via actingSubject. A throwing runTurn must never end the
|
|
125
|
+
* session; this window has no other chance to show this turn's
|
|
126
|
+
* answer. */
|
|
127
|
+
async turn(line) {
|
|
128
|
+
let result;
|
|
129
|
+
try {
|
|
130
|
+
result = await runTurn(line, {
|
|
131
|
+
config: null, source: null, graph, focus, last, memoryDir,
|
|
132
|
+
sessionId: character, env: {}, lexicon, uiContext: "browser",
|
|
133
|
+
actingSubject: character, planState: planHolder.state,
|
|
134
|
+
vocabHint: 'Try a world command ("dig north", "eat the carrot-1"), or ask "what food do you know about".',
|
|
135
|
+
});
|
|
136
|
+
} catch (e) {
|
|
137
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
138
|
+
return { answer: `Something went wrong answering that (${message}). Try rephrasing.`, end: false };
|
|
139
|
+
}
|
|
140
|
+
focus = result.focus;
|
|
141
|
+
last = result.last;
|
|
142
|
+
if ("planState" in result) planHolder.state = result.planState;
|
|
143
|
+
turnsTaken += 1;
|
|
144
|
+
const here = await roomOf(character);
|
|
145
|
+
if (here) visitedRoomIds.add(here);
|
|
146
|
+
return { answer: result.answer, end: Boolean(result.end) };
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
/** One whole scripted turn (mud-turn.mjs's runMudTurn): investigate,
|
|
150
|
+
* walk toward known food, or roll at the edge (dig). `k` is the turn
|
|
151
|
+
* ordinal the caller drives — mud-viz.mjs's own global turn counter,
|
|
152
|
+
* so every character's turn lands on a distinct, strictly increasing
|
|
153
|
+
* number regardless of which window fired it. Returns runMudTurn's
|
|
154
|
+
* own `{ character, k, room, roomAfter, actions, learned, text,
|
|
155
|
+
* note }` unmodified, so the caller can render the speech-bubble/
|
|
156
|
+
* dig-flourish triggers straight off `actions`. */
|
|
157
|
+
async autoplayTick(k) {
|
|
158
|
+
const result = await runMudTurn(character, { world: worldPayload.name, memoryDir, env: {}, graph, k });
|
|
159
|
+
// A turn that ended in starvation still happened, and still counts —
|
|
160
|
+
// only a DECLINED turn (no room to act in) leaves the tally alone.
|
|
161
|
+
if (result.room) turnsTaken += 1;
|
|
162
|
+
if (result.roomAfter) visitedRoomIds.add(result.roomAfter);
|
|
163
|
+
return result;
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
/** This character's own discovered-room history — real fog of war,
|
|
167
|
+
* never merged with a sibling window's. */
|
|
168
|
+
visitedRoomIds: () => [...visitedRoomIds],
|
|
169
|
+
|
|
170
|
+
/** How many turns THIS character has taken — its own scripted ticks and
|
|
171
|
+
* its own typed commands, and nobody else's. */
|
|
172
|
+
turnsTaken: () => turnsTaken,
|
|
173
|
+
|
|
174
|
+
/** True once this character's run has ended — a predator ate it, or its
|
|
175
|
+
* mass ran out. It takes no further turns and every command it gives
|
|
176
|
+
* declines, so a caller can stop ticking it and say so on screen. */
|
|
177
|
+
async isOutOfPlay() {
|
|
178
|
+
const { state } = await readWorld();
|
|
179
|
+
return isOutOfPlay(state, character);
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
/** WHICH ending it was — "eaten" or "starved" — or null while it is still
|
|
183
|
+
* playing. The two read nothing alike on screen, so a pane showing a
|
|
184
|
+
* fate needs the reason, not just the fact. */
|
|
185
|
+
async outOfPlayReason() {
|
|
186
|
+
const { state } = await readWorld();
|
|
187
|
+
return outOfPlayReasonOf(state, character);
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** The one OMNISCIENT read this module exposes: every room, every
|
|
193
|
+
* character, every level, no fog of war — the central world map's own
|
|
194
|
+
* data source. Never call this for a per-window room view; use
|
|
195
|
+
* worldDigestRows/roomAffordances against ONE room instead. */
|
|
196
|
+
async function snapshot() {
|
|
197
|
+
const rows = readFactRows(await loadMemory(memoryDir));
|
|
198
|
+
const state = foldWorldState(worldActionRows(rows));
|
|
199
|
+
return { rows, state };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** The world editor's own store sync: parse `text` (mud-editor.mjs's own
|
|
203
|
+
* parseMudEditorText), plan the writes it implies, and apply them — scoped to
|
|
204
|
+
* THIS world's provenance tag only. Returns `{ unrecognized, added, removed }`.
|
|
205
|
+
*
|
|
206
|
+
* Two things this does that a fresh, unplayed world would not need.
|
|
207
|
+
* Retractions run only when the WHOLE document parsed cleanly, so a half-typed
|
|
208
|
+
* line is never read as "this fact is gone". And a fold-versioned write
|
|
209
|
+
* (a placement, an openness, a mass) is stamped `subject@turnN` at one past
|
|
210
|
+
* the world's own turn count, exactly the way every in-game action's commit
|
|
211
|
+
* writes: the fold takes the newest turn, so an untagged row would sit at turn
|
|
212
|
+
* zero and lose to the snapshot the last played turn already left behind —
|
|
213
|
+
* the edit would look accepted and change nothing. */
|
|
214
|
+
async function applyEdit(text) {
|
|
215
|
+
const allRows = readFactRows(await loadMemory(memoryDir));
|
|
216
|
+
const worldRows = allRows.filter((r) => typeof r.provenance === "string" && r.provenance.indexOf(tag) === 0);
|
|
217
|
+
const state = foldWorldState(worldRows);
|
|
218
|
+
const { triples, unrecognized } = parseMudEditorText(text);
|
|
219
|
+
const { toAppend, toRemoveIds } = planMudEditorSync(worldRows, state, triples);
|
|
220
|
+
const editTurn = state.turnCount + 1;
|
|
221
|
+
if (toAppend.length) {
|
|
222
|
+
await appendFacts(memoryDir, toAppend.map((f) => ({
|
|
223
|
+
subject: f.kind === "other" ? f.subject : `${f.subject}@turn${editTurn}`,
|
|
224
|
+
predicate: f.predicate,
|
|
225
|
+
object: f.object,
|
|
226
|
+
provenance: f.kind === "other" ? tag : `${tag}:turn${editTurn}`,
|
|
227
|
+
})));
|
|
228
|
+
}
|
|
229
|
+
const removed = unrecognized.length === 0 && toRemoveIds.length
|
|
230
|
+
? (await removeFacts(memoryDir, toRemoveIds)).removed.length
|
|
231
|
+
: 0;
|
|
232
|
+
return { unrecognized, added: toAppend.length, removed };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return { memoryDir, windows, snapshot, applyEdit };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** `count` entries drawn at random from `roster`, in random order, without
|
|
239
|
+
* repeats — which animals this visit is played with. Called fresh on every
|
|
240
|
+
* reset, so the same page gives a different pairing each time and the world
|
|
241
|
+
* never reads as one fixed cast. `random` is injectable so a caller can pin
|
|
242
|
+
* the draw; the world engine itself still writes no randomness anywhere, and
|
|
243
|
+
* this picks the players, never anything the world folds. */
|
|
244
|
+
export function pickMudRoster(roster, { count = 2, random = Math.random } = {}) {
|
|
245
|
+
const pool = [...(roster || [])];
|
|
246
|
+
for (let i = pool.length - 1; i > 0; i -= 1) {
|
|
247
|
+
const j = Math.floor(random() * (i + 1));
|
|
248
|
+
[pool[i], pool[j]] = [pool[j], pool[i]];
|
|
249
|
+
}
|
|
250
|
+
return pool.slice(0, Math.min(count, pool.length));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** `roster` grown to `size` ids by numbering more instances of the species it
|
|
254
|
+
* already names — "mole-2", "vole-3" — so a page can cast more animals than
|
|
255
|
+
* the world hand-authors individuals for. The authored ids come first and
|
|
256
|
+
* keep their own numbers; a minted id never collides with one. The species
|
|
257
|
+
* round-robins, so the extras stay spread across the roster's animals rather
|
|
258
|
+
* than piling ten moles into the garden. Pure. */
|
|
259
|
+
export function expandMudRoster(roster, size) {
|
|
260
|
+
const ids = [...(roster || [])];
|
|
261
|
+
if (!ids.length) return ids;
|
|
262
|
+
const used = new Set(ids);
|
|
263
|
+
const species = [...new Set(ids.map(mudSpeciesOf))];
|
|
264
|
+
for (let instance = 1; ids.length < size; instance += 1) {
|
|
265
|
+
for (const kind of species) {
|
|
266
|
+
if (ids.length >= size) break;
|
|
267
|
+
const id = `${kind}-${instance}`;
|
|
268
|
+
if (used.has(id)) continue;
|
|
269
|
+
used.add(id);
|
|
270
|
+
ids.push(id);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return ids;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** The facts that place `characters` the world's own `facts` never placed —
|
|
277
|
+
* each one copied wholesale from an authored individual of the same species,
|
|
278
|
+
* with the subject swapped. Copying rather than composing is what keeps a
|
|
279
|
+
* minted animal an ORDINARY one: it arrives with the same type, the same
|
|
280
|
+
* starting room and the same mass mud-garden gives its own mole, and it
|
|
281
|
+
* picks up anything a later edit adds to that mole for free. A species the
|
|
282
|
+
* world authors nobody of is skipped rather than guessed at. Pure. */
|
|
283
|
+
export function mintedCharacterFacts(facts, characters) {
|
|
284
|
+
const rows = facts || [];
|
|
285
|
+
const placedIn = (subject) => rows.some((f) => f.subject === subject && f.predicate === "mgx:currently-in");
|
|
286
|
+
const minted = [];
|
|
287
|
+
for (const character of characters || []) {
|
|
288
|
+
if (placedIn(character) || minted.some((f) => f.subject === character)) continue;
|
|
289
|
+
const species = mudSpeciesOf(character);
|
|
290
|
+
const template = rows.find((f) => f.predicate === "mgx:currently-in" && mudSpeciesOf(f.subject) === species);
|
|
291
|
+
if (!template) continue;
|
|
292
|
+
for (const fact of rows) {
|
|
293
|
+
if (fact.subject !== template.subject) continue;
|
|
294
|
+
minted.push({ subject: character, predicate: fact.predicate, object: fact.object });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return minted;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** `facts` opened for exactly `characters`: the playable animals nobody is
|
|
301
|
+
* playing are left out of the world, and the characters the world authors
|
|
302
|
+
* nobody for are minted in. The first half is what keeps the cast honest —
|
|
303
|
+
* an authored animal nobody drives stands in its starting room for the whole
|
|
304
|
+
* run, shows up in every room description and answers when talked to, all
|
|
305
|
+
* without ever taking a turn. The fox, a den's resident mouse and every prop
|
|
306
|
+
* are untouched: they are the world, not the cast. Pure. */
|
|
307
|
+
export function worldFactsForCast(facts, characters) {
|
|
308
|
+
const rows = facts || [];
|
|
309
|
+
const cast = new Set(characters || []);
|
|
310
|
+
const uncast = new Set(rows
|
|
311
|
+
.filter((f) => f.predicate === "rdf:type" && f.object === "adventurer" && !cast.has(f.subject))
|
|
312
|
+
.map((f) => f.subject));
|
|
313
|
+
return rows.filter((f) => !uncast.has(f.subject)).concat(mintedCharacterFacts(rows, characters));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Re-exported so mud-viz.mjs's own inlined script never duplicates sprite
|
|
317
|
+
// resolution or the digest/affordance/knowledge readers its room view and
|
|
318
|
+
// chat pills already need — the same reach-through-the-global posture
|
|
319
|
+
// adventure-browser-entry.mjs's own globalThis.tmctAdventure takes.
|
|
320
|
+
globalThis.tmctMud = {
|
|
321
|
+
createMudSession, pickMudRoster, expandMudRoster, mintedCharacterFacts, worldFactsForCast,
|
|
322
|
+
resolveSpriteForClass, SPRITE_REGISTRY, classAncestorChain, resolveSpriteAsset,
|
|
323
|
+
foldWorldState, worldActionRows, worldDigestRows, roomAffordances,
|
|
324
|
+
personKnowledgeLines, personKnownFoodLines,
|
|
325
|
+
diggableDirections, castInRoom, displayNameOf, isOutOfPlay, outOfPlayReasonOf, outOfPlayPhrase,
|
|
326
|
+
roomKindOf,
|
|
327
|
+
// The edit mode's own reach-throughs: the SKOS neighbourhood and the is-a
|
|
328
|
+
// chain its cursor-suggestion pills read, neither of which is splice-safe.
|
|
329
|
+
relatedForTerm,
|
|
330
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// p2p-browser-entry.mjs — the esbuild entry for public/vendor/p2p.js, the ONE
|
|
2
|
+
// shared networking asset every page that joins a world imports at runtime.
|
|
3
|
+
// Same arrangement as ./vendor/wink.js: an ESM module, same-origin, one cached
|
|
4
|
+
// copy site-wide, imported dynamically by a page's own inline script rather
|
|
5
|
+
// than bundled into each page's engine bundle.
|
|
6
|
+
//
|
|
7
|
+
// A page imports it LAZILY, off the boot path, so networking nobody asked for
|
|
8
|
+
// never delays a first answer. The service worker precaches it anyway, which
|
|
9
|
+
// is what lets two laptops on a LAN with no internet still invite each other
|
|
10
|
+
// on a return visit.
|
|
11
|
+
//
|
|
12
|
+
// This module re-exports only; every rule lives in the layers below. The
|
|
13
|
+
// memory core rides along because p2p-room.mjs merges through appendFacts, and
|
|
14
|
+
// this asset therefore holds its own copy of that code separate from the page's
|
|
15
|
+
// engine bundle. The two copies operate on the SAME handle object the page
|
|
16
|
+
// passes in, and appendFacts rebuilds its lookup index from the payload on
|
|
17
|
+
// every mutation, so neither copy can read the other's stale index.
|
|
18
|
+
export {
|
|
19
|
+
createP2pRoom,
|
|
20
|
+
PRESENCE_SCOPE,
|
|
21
|
+
ROOM_IDLE,
|
|
22
|
+
ROOM_SHARING,
|
|
23
|
+
ROOM_ANSWERING,
|
|
24
|
+
ROOM_CONNECTING,
|
|
25
|
+
ROOM_CONNECTED,
|
|
26
|
+
ROOM_FAILED,
|
|
27
|
+
} from "../../services/p2p-room.mjs";
|
|
28
|
+
export { createTransport } from "../../adapters/p2p/webrtc-transport.mjs";
|
|
29
|
+
export { generatePeerId, generateWorldId, generateDisplayName } from "../../domain/p2p/peer-id.mjs";
|
|
30
|
+
export { chatSyncableFacts, mudSyncableFacts } from "../../domain/p2p/sync-filter.mjs";
|
|
31
|
+
export { decodeInviteBlob, encodeInviteBlob } from "../../domain/p2p/wire.mjs";
|
|
32
|
+
export {
|
|
33
|
+
latestProvenanceTimestamp,
|
|
34
|
+
isRecentWave,
|
|
35
|
+
latestFact,
|
|
36
|
+
NODE_NAME_PREDICATE,
|
|
37
|
+
WORLD_NAME_PREDICATE,
|
|
38
|
+
WAVED_PREDICATE,
|
|
39
|
+
} from "../../domain/p2p/facts.mjs";
|