@polycode-projects/the-mechanical-code-talker 3.3.0 → 4.0.1

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.
Files changed (84) hide show
  1. package/README.md +176 -8
  2. package/corpus/reference/index.json.gz +0 -0
  3. package/corpus/reference/manifest.json +8 -8
  4. package/corpus/reference/shards/ref-00.jsonl.gz +0 -0
  5. package/corpus/sprites/src/sprite-facts.jsonl +52 -0
  6. package/corpus/worlds/index.json.gz +0 -0
  7. package/corpus/worlds/manifest.json +53 -13
  8. package/corpus/worlds/shards/greyvale-museum.jsonl.gz +0 -0
  9. package/corpus/worlds/shards/lantern-cottage.jsonl.gz +0 -0
  10. package/corpus/worlds/shards/mud-garden.jsonl.gz +0 -0
  11. package/corpus/worlds/shards/mud-hollow.jsonl.gz +0 -0
  12. package/corpus/worlds/shards/mud-warren.jsonl.gz +0 -0
  13. package/corpus/worlds/shards/spider-fly.jsonl.gz +0 -0
  14. package/corpus/worlds/src/greyvale-museum.jsonl +136 -0
  15. package/corpus/worlds/src/lantern-cottage.jsonl +60 -0
  16. package/corpus/worlds/src/mud-garden.jsonl +34 -1
  17. package/corpus/worlds/src/mud-hollow.jsonl +82 -0
  18. package/corpus/worlds/src/mud-warren.jsonl +124 -0
  19. package/corpus/worlds/src/spider-fly.jsonl +1 -1
  20. package/data/sprites/animal-icon.toml +11 -5
  21. package/data/sprites/book-icon.toml +9 -5
  22. package/data/sprites/cabinet-icon.toml +10 -5
  23. package/data/sprites/cellar-icon.toml +16 -6
  24. package/data/sprites/container-icon.toml +7 -3
  25. package/data/sprites/desk-icon.toml +8 -5
  26. package/data/sprites/dog-icon.toml +8 -5
  27. package/data/sprites/dog-with-colour-icon.toml +13 -12
  28. package/data/sprites/drawing-room-icon.toml +14 -5
  29. package/data/sprites/egg-icon.toml +6 -3
  30. package/data/sprites/fly-icon.toml +10 -5
  31. package/data/sprites/furniture-icon.toml +5 -3
  32. package/data/sprites/garden-icon.toml +10 -3
  33. package/data/sprites/key-icon.toml +3 -1
  34. package/data/sprites/kitchen-icon.toml +15 -6
  35. package/data/sprites/lamp-icon.toml +9 -4
  36. package/data/sprites/letter-icon.toml +7 -4
  37. package/data/sprites/library-icon.toml +14 -3
  38. package/data/sprites/pan-icon.toml +7 -3
  39. package/data/sprites/person-icon.toml +6 -2
  40. package/data/sprites/poodle-icon.toml +1 -1
  41. package/data/sprites/portable-icon.toml +6 -4
  42. package/data/sprites/portrait-icon.toml +7 -4
  43. package/data/sprites/room-icon.toml +11 -2
  44. package/data/sprites/spider-icon.toml +9 -3
  45. package/data/sprites/study-icon.toml +10 -2
  46. package/package.json +5 -4
  47. package/src/adapters/p2p/webrtc-transport.mjs +146 -0
  48. package/src/domain/game-config.mjs +26 -5
  49. package/src/domain/grammar/ace.mjs +40 -4
  50. package/src/domain/grammar/lexicon-core.json +2 -1
  51. package/src/domain/grammar/lexicon.mjs +31 -0
  52. package/src/domain/memory/trust.mjs +6 -4
  53. package/src/domain/p2p/facts.mjs +81 -0
  54. package/src/domain/p2p/peer-id.mjs +32 -0
  55. package/src/domain/p2p/provenance-relabel.mjs +26 -0
  56. package/src/domain/p2p/sync-filter.mjs +31 -0
  57. package/src/domain/p2p/wire.mjs +123 -0
  58. package/src/domain/reference-pack.mjs +31 -7
  59. package/src/domain/spider-fly-world.mjs +1 -1
  60. package/src/domain/sprite-map.mjs +10 -2
  61. package/src/domain/sprite-templates.mjs +8 -6
  62. package/src/services/adventure-editor.mjs +10 -2
  63. package/src/services/adventure-viz.mjs +309 -73
  64. package/src/services/adventure.mjs +1034 -330
  65. package/src/services/chat-page-viz.mjs +1097 -16
  66. package/src/services/chat.mjs +99 -18
  67. package/src/services/code-explorer-viz.mjs +52 -14
  68. package/src/services/extract-facts.mjs +4 -7
  69. package/src/services/ingest-viz.mjs +54 -26
  70. package/src/services/ledger-viz.mjs +105 -56
  71. package/src/services/memory-panel-viz.mjs +24 -0
  72. package/src/services/mud-editor.mjs +313 -0
  73. package/src/services/mud-turn.mjs +264 -87
  74. package/src/services/mud-viz.mjs +2606 -384
  75. package/src/services/p2p-room.mjs +559 -0
  76. package/src/services/plan-viz.mjs +119 -61
  77. package/src/services/research-viz.mjs +151 -57
  78. package/src/services/spider-fly-turn.mjs +1 -1
  79. package/src/services/spider-fly-viz.mjs +67 -43
  80. package/src/services/sprite-catalog-viz.mjs +175 -51
  81. package/src/services/viz-theme.mjs +15 -0
  82. package/src/surfaces/web/memory-ask-browser.bundle.js +109 -109
  83. package/src/surfaces/web/mud-browser-entry.mjs +226 -18
  84. package/src/surfaces/web/p2p-browser-entry.mjs +39 -0
@@ -1,11 +1,13 @@
1
- // mud-browser-entry.mjs — the esbuild entry for mud.html's four-character,
1
+ // mud-browser-entry.mjs — the esbuild entry for mud.html's multi-character,
2
2
  // one-shared-world browser session (public/mud-browser.bundle.js), mirroring
3
3
  // adventure-browser-entry.mjs's own session-factory shape. The difference is
4
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.
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.
7
9
  //
8
- // What's shared across all four characters, and why: the store itself
10
+ // What's shared across every character, and why: the store itself
9
11
  // (memoryDir) — mole-1's dig must be visible to vole-1's very next look — and
10
12
  // ONE planHolder.state, seeded once as "mud-garden is already live" the same
11
13
  // way adventure-browser-entry.mjs seeds it, since "is the world open" is a
@@ -22,41 +24,52 @@
22
24
  // page's chat dock); `autoplayTick(k)` runs mud-turn.mjs's runMudTurn — one
23
25
  // whole scripted turn (investigate, walk toward known food, dig at the
24
26
  // 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
27
+ // SERIALIZING ticks across the characters when more than one window is
26
28
  // auto-playing at once — this file makes no ordering promise between two
27
29
  // concurrent calls into the same memoryDir, the same way two callers writing
28
30
  // into any shared store concurrently would need their own queue.
29
31
  import { runTurn } from "../../services/chat.mjs";
30
- import { createInMemoryStore, appendFacts, appendRule, loadMemory, readFactRows } from "../../adapters/memory/core.mjs";
32
+ import {
33
+ createInMemoryStore, appendFacts, appendRule, loadMemory, readFactRows, removeFacts,
34
+ } from "../../adapters/memory/core.mjs";
31
35
  import { parseEntities } from "../../domain/codegraph.mjs";
32
36
  import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
33
37
  import {
34
38
  foldWorldState, worldActionRows, worldDigestRows, roomAffordances,
35
39
  personKnowledgeLines, personKnownFoodLines,
40
+ diggableDirections, castInRoom, displayNameOf, isOutOfPlay, outOfPlayReasonOf, outOfPlayPhrase,
41
+ roomKindOf, isMudStatePredicate,
36
42
  } from "../../services/adventure.mjs";
43
+ import { waveFact, playedByFact, P2P_PREDICATES } from "../../domain/p2p/facts.mjs";
44
+ import { relatedForTerm } from "../../domain/skos-view.mjs";
37
45
  import { runMudTurn } from "../../services/mud-turn.mjs";
46
+ import { parseMudEditorText, planMudEditorSync } from "../../services/mud-editor.mjs";
47
+ import { mudSpeciesOf } from "../../domain/game-config.mjs";
38
48
  import { worldProvenanceTag } from "../../domain/worlds-pack.mjs";
39
49
  import { resolveSpriteForClass, SPRITE_REGISTRY, classAncestorChain } from "../../domain/sprite-map.mjs";
40
50
  import { resolveSpriteAsset } from "../../domain/sprite-templates.mjs";
41
51
 
42
- /** A live, shared mud world four characters can each act in. `worldPayload`
52
+ /** A live, shared mud world several characters can each act in. `worldPayload`
43
53
  * is `{ name, facts, rules, opening }` — the same shape adventure-browser-
44
54
  * entry.mjs's own worldPayload takes, read once at build time through the
45
55
  * real Node worlds-pack provider (see mud-viz.mjs's header for why: the
46
56
  * 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.
57
+ * cannot read). `characters` is the roster this page drives (e.g. the two
58
+ * ids pickMudRoster drew this reset). The world is opened for exactly that
59
+ * list (worldFactsForCast, below): more animals than mud-garden hand-authors
60
+ * are minted, fewer leaves the ones nobody is playing out of the world
61
+ * altogether.
50
62
  *
51
63
  * Returns `{ memoryDir, windows, snapshot }`. `windows` is a plain object
52
64
  * 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. */
65
+ * visitedRoomIds, turnsTaken, isOutOfPlay, outOfPlayReason }`. `snapshot()` is
66
+ * the one OMNISCIENT read this module exposes — the central world map's own
67
+ * data source, never a per-window one. */
56
68
  export async function createMudSession(worldPayload, { characters = [] } = {}) {
57
69
  const memoryDir = createInMemoryStore();
58
70
  const tag = worldProvenanceTag(worldPayload.name);
59
- await appendFacts(memoryDir, worldPayload.facts.map((f) => ({
71
+ const seedFacts = worldFactsForCast(worldPayload.facts, characters);
72
+ await appendFacts(memoryDir, seedFacts.map((f) => ({
60
73
  subject: f.subject, predicate: f.predicate, object: f.object, provenance: tag,
61
74
  })));
62
75
  for (const rule of worldPayload.rules) {
@@ -70,11 +83,19 @@ export async function createMudSession(worldPayload, { characters = [] } = {}) {
70
83
  // shipped "player" individual mud-garden deliberately has none of).
71
84
  const planHolder = { state: { adventure: { world: worldPayload.name } } };
72
85
  const graph = parseEntities({ individuals: [], objectProperties: [] });
86
+ // The world's own minted ids ("groundhog-1", "carrot-2") are declared as
87
+ // vocabulary inside the adventure lane itself, for the length of one world
88
+ // command — see adventure.mjs's own worldLexicon. This page hands over the
89
+ // plain core lexicon and lets the lane do it.
73
90
  const lexicon = loadLexicon();
74
91
 
75
- async function roomOf(character) {
92
+ async function readWorld() {
76
93
  const rows = readFactRows(await loadMemory(memoryDir));
77
- const state = foldWorldState(worldActionRows(rows));
94
+ return { rows, state: foldWorldState(worldActionRows(rows)) };
95
+ }
96
+
97
+ async function roomOf(character) {
98
+ const { state } = await readWorld();
78
99
  return state.placements.get(character)?.object ?? null;
79
100
  }
80
101
 
@@ -88,6 +109,11 @@ export async function createMudSession(worldPayload, { characters = [] } = {}) {
88
109
  let focus = null;
89
110
  let last = null;
90
111
  const visitedRoomIds = new Set();
112
+ // This character's OWN turns, not the page's shared tick counter: two
113
+ // windows playing at different speeds, or one paused while the other
114
+ // runs, have genuinely different counts, and showing the shared one under
115
+ // both animals says something untrue about each.
116
+ let turnsTaken = 0;
91
117
  const startRoom = await roomOf(character);
92
118
  if (startRoom) visitedRoomIds.add(startRoom);
93
119
 
@@ -115,6 +141,7 @@ export async function createMudSession(worldPayload, { characters = [] } = {}) {
115
141
  focus = result.focus;
116
142
  last = result.last;
117
143
  if ("planState" in result) planHolder.state = result.planState;
144
+ turnsTaken += 1;
118
145
  const here = await roomOf(character);
119
146
  if (here) visitedRoomIds.add(here);
120
147
  return { answer: result.answer, end: Boolean(result.end) };
@@ -130,6 +157,9 @@ export async function createMudSession(worldPayload, { characters = [] } = {}) {
130
157
  * dig-flourish triggers straight off `actions`. */
131
158
  async autoplayTick(k) {
132
159
  const result = await runMudTurn(character, { world: worldPayload.name, memoryDir, env: {}, graph, k });
160
+ // A turn that ended in starvation still happened, and still counts —
161
+ // only a DECLINED turn (no room to act in) leaves the tally alone.
162
+ if (result.room) turnsTaken += 1;
133
163
  if (result.roomAfter) visitedRoomIds.add(result.roomAfter);
134
164
  return result;
135
165
  },
@@ -137,6 +167,26 @@ export async function createMudSession(worldPayload, { characters = [] } = {}) {
137
167
  /** This character's own discovered-room history — real fog of war,
138
168
  * never merged with a sibling window's. */
139
169
  visitedRoomIds: () => [...visitedRoomIds],
170
+
171
+ /** How many turns THIS character has taken — its own scripted ticks and
172
+ * its own typed commands, and nobody else's. */
173
+ turnsTaken: () => turnsTaken,
174
+
175
+ /** True once this character's run has ended — a predator ate it, or its
176
+ * mass ran out. It takes no further turns and every command it gives
177
+ * declines, so a caller can stop ticking it and say so on screen. */
178
+ async isOutOfPlay() {
179
+ const { state } = await readWorld();
180
+ return isOutOfPlay(state, character);
181
+ },
182
+
183
+ /** WHICH ending it was — "eaten" or "starved" — or null while it is still
184
+ * playing. The two read nothing alike on screen, so a pane showing a
185
+ * fate needs the reason, not just the fact. */
186
+ async outOfPlayReason() {
187
+ const { state } = await readWorld();
188
+ return outOfPlayReasonOf(state, character);
189
+ },
140
190
  };
141
191
  }
142
192
 
@@ -150,7 +200,155 @@ export async function createMudSession(worldPayload, { characters = [] } = {}) {
150
200
  return { rows, state };
151
201
  }
152
202
 
153
- return { memoryDir, windows, snapshot };
203
+ /** The world editor's own store sync: parse `text` (mud-editor.mjs's own
204
+ * parseMudEditorText), plan the writes it implies, and apply them — scoped to
205
+ * THIS world's provenance tag only. Returns `{ unrecognized, added, removed }`.
206
+ *
207
+ * Two things this does that a fresh, unplayed world would not need.
208
+ * Retractions run only when the WHOLE document parsed cleanly, so a half-typed
209
+ * line is never read as "this fact is gone". And a fold-versioned write
210
+ * (a placement, an openness, a mass) is stamped `subject@turnN` at one past
211
+ * the world's own turn count, exactly the way every in-game action's commit
212
+ * writes: the fold takes the newest turn, so an untagged row would sit at turn
213
+ * zero and lose to the snapshot the last played turn already left behind —
214
+ * the edit would look accepted and change nothing. */
215
+ async function applyEdit(text) {
216
+ const allRows = readFactRows(await loadMemory(memoryDir));
217
+ const worldRows = allRows.filter((r) => typeof r.provenance === "string" && r.provenance.indexOf(tag) === 0);
218
+ const state = foldWorldState(worldRows);
219
+ const { triples, unrecognized } = parseMudEditorText(text);
220
+ const { toAppend, toRemoveIds } = planMudEditorSync(worldRows, state, triples);
221
+ const editTurn = state.turnCount + 1;
222
+ if (toAppend.length) {
223
+ await appendFacts(memoryDir, toAppend.map((f) => ({
224
+ subject: f.kind === "other" ? f.subject : `${f.subject}@turn${editTurn}`,
225
+ predicate: f.predicate,
226
+ object: f.object,
227
+ provenance: f.kind === "other" ? tag : `${tag}:turn${editTurn}`,
228
+ })));
229
+ }
230
+ const removed = unrecognized.length === 0 && toRemoveIds.length
231
+ ? (await removeFacts(memoryDir, toRemoveIds)).removed.length
232
+ : 0;
233
+ return { unrecognized, added: toAppend.length, removed };
234
+ }
235
+
236
+ // Wall-clock resolution is 1ms, and both writers below are content-addressed
237
+ // by (subject, predicate, object) — a second wave from the same character in
238
+ // the same room is the SAME fact id, so it only registers as a change if its
239
+ // provenance tag differs. Two writes inside one tick would share a timestamp,
240
+ // share a tag, and the second would silently vanish. p2p-room.mjs nudges its
241
+ // own clock for exactly this reason; this is the same nudge for the writes
242
+ // that happen before a room exists.
243
+ let lastWriteMs = -Infinity;
244
+ function stampNow() {
245
+ const nudged = Math.max(Date.now(), lastWriteMs + 1);
246
+ lastWriteMs = nudged;
247
+ return new Date(nudged).toISOString();
248
+ }
249
+
250
+ /** `character` waves in whichever room it currently stands in — an ordinary
251
+ * add-only fact, so a page with no network renders it exactly like a page
252
+ * sharing the world with three others. Returns the room waved in, or null
253
+ * when the character stands nowhere (out of play). Nothing is ever
254
+ * retracted: "currently waving" is a recency read over this fact's own
255
+ * provenance timestamp. */
256
+ async function wave(character) {
257
+ const here = await roomOf(character);
258
+ if (!here) return null;
259
+ await appendFacts(memoryDir, [waveFact(character, here, stampNow())]);
260
+ return here;
261
+ }
262
+
263
+ /** Claim `characters` for `peerId` — one add-only `mgx:playedBy` fact each.
264
+ * Claims never overwrite: two peers claiming the same animal both write,
265
+ * and every reader settles it the same way by taking the oldest claim. */
266
+ async function claimCharacters(characters, peerId) {
267
+ const at = stampNow();
268
+ const claims = (characters || []).map((character) => playedByFact(character, peerId, at));
269
+ if (claims.length) await appendFacts(memoryDir, claims);
270
+ return claims.length;
271
+ }
272
+
273
+ return { memoryDir, windows, snapshot, applyEdit, wave, claimCharacters };
274
+ }
275
+
276
+ /** `count` entries drawn at random from `roster`, in random order, without
277
+ * repeats — which animals this visit is played with. Called fresh on every
278
+ * reset, so the same page gives a different pairing each time and the world
279
+ * never reads as one fixed cast. `random` is injectable so a caller can pin
280
+ * the draw; the world engine itself still writes no randomness anywhere, and
281
+ * this picks the players, never anything the world folds. */
282
+ export function pickMudRoster(roster, { count = 2, random = Math.random } = {}) {
283
+ const pool = [...(roster || [])];
284
+ for (let i = pool.length - 1; i > 0; i -= 1) {
285
+ const j = Math.floor(random() * (i + 1));
286
+ [pool[i], pool[j]] = [pool[j], pool[i]];
287
+ }
288
+ return pool.slice(0, Math.min(count, pool.length));
289
+ }
290
+
291
+ /** `roster` grown to `size` ids by numbering more instances of the species it
292
+ * already names — "mole-2", "vole-3" — so a page can cast more animals than
293
+ * the world hand-authors individuals for. The authored ids come first and
294
+ * keep their own numbers; a minted id never collides with one. The species
295
+ * round-robins, so the extras stay spread across the roster's animals rather
296
+ * than piling ten moles into the garden. Pure. */
297
+ export function expandMudRoster(roster, size) {
298
+ const ids = [...(roster || [])];
299
+ if (!ids.length) return ids;
300
+ const used = new Set(ids);
301
+ const species = [...new Set(ids.map(mudSpeciesOf))];
302
+ for (let instance = 1; ids.length < size; instance += 1) {
303
+ for (const kind of species) {
304
+ if (ids.length >= size) break;
305
+ const id = `${kind}-${instance}`;
306
+ if (used.has(id)) continue;
307
+ used.add(id);
308
+ ids.push(id);
309
+ }
310
+ }
311
+ return ids;
312
+ }
313
+
314
+ /** The facts that place `characters` the world's own `facts` never placed —
315
+ * each one copied wholesale from an authored individual of the same species,
316
+ * with the subject swapped. Copying rather than composing is what keeps a
317
+ * minted animal an ORDINARY one: it arrives with the same type, the same
318
+ * starting room and the same mass mud-garden gives its own mole, and it
319
+ * picks up anything a later edit adds to that mole for free. A species the
320
+ * world authors nobody of is skipped rather than guessed at. Pure. */
321
+ export function mintedCharacterFacts(facts, characters) {
322
+ const rows = facts || [];
323
+ const placedIn = (subject) => rows.some((f) => f.subject === subject && f.predicate === "mgx:currently-in");
324
+ const minted = [];
325
+ for (const character of characters || []) {
326
+ if (placedIn(character) || minted.some((f) => f.subject === character)) continue;
327
+ const species = mudSpeciesOf(character);
328
+ const template = rows.find((f) => f.predicate === "mgx:currently-in" && mudSpeciesOf(f.subject) === species);
329
+ if (!template) continue;
330
+ for (const fact of rows) {
331
+ if (fact.subject !== template.subject) continue;
332
+ minted.push({ subject: character, predicate: fact.predicate, object: fact.object });
333
+ }
334
+ }
335
+ return minted;
336
+ }
337
+
338
+ /** `facts` opened for exactly `characters`: the playable animals nobody is
339
+ * playing are left out of the world, and the characters the world authors
340
+ * nobody for are minted in. The first half is what keeps the cast honest —
341
+ * an authored animal nobody drives stands in its starting room for the whole
342
+ * run, shows up in every room description and answers when talked to, all
343
+ * without ever taking a turn. The fox, a den's resident mouse and every prop
344
+ * are untouched: they are the world, not the cast. Pure. */
345
+ export function worldFactsForCast(facts, characters) {
346
+ const rows = facts || [];
347
+ const cast = new Set(characters || []);
348
+ const uncast = new Set(rows
349
+ .filter((f) => f.predicate === "rdf:type" && f.object === "adventurer" && !cast.has(f.subject))
350
+ .map((f) => f.subject));
351
+ return rows.filter((f) => !uncast.has(f.subject)).concat(mintedCharacterFacts(rows, characters));
154
352
  }
155
353
 
156
354
  // Re-exported so mud-viz.mjs's own inlined script never duplicates sprite
@@ -158,7 +356,17 @@ export async function createMudSession(worldPayload, { characters = [] } = {}) {
158
356
  // chat pills already need — the same reach-through-the-global posture
159
357
  // adventure-browser-entry.mjs's own globalThis.tmctAdventure takes.
160
358
  globalThis.tmctMud = {
161
- createMudSession, resolveSpriteForClass, SPRITE_REGISTRY, classAncestorChain, resolveSpriteAsset,
359
+ createMudSession, pickMudRoster, expandMudRoster, mintedCharacterFacts, worldFactsForCast,
360
+ resolveSpriteForClass, SPRITE_REGISTRY, classAncestorChain, resolveSpriteAsset,
162
361
  foldWorldState, worldActionRows, worldDigestRows, roomAffordances,
163
362
  personKnowledgeLines, personKnownFoodLines,
363
+ diggableDirections, castInRoom, displayNameOf, isOutOfPlay, outOfPlayReasonOf, outOfPlayPhrase,
364
+ roomKindOf,
365
+ // The shared-world reach-throughs: which predicates carry live world state,
366
+ // and the P2P layer's own four. mud.html hands both to `mudSyncableFacts`,
367
+ // which is written to take the check rather than import the engine itself.
368
+ isMudStatePredicate, P2P_PREDICATES,
369
+ // The edit mode's own reach-throughs: the SKOS neighbourhood and the is-a
370
+ // chain its cursor-suggestion pills read, neither of which is splice-safe.
371
+ relatedForTerm,
164
372
  };
@@ -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";