@polycode-projects/the-mechanical-code-talker 4.1.9 → 5.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.
@@ -56,6 +56,7 @@ const SNAPSHOT_RE = /^(.+)@turn(\d+)$/;
56
56
  // elsewhere in the document can never silently retract it.
57
57
  const EDITABLE_OTHER_PREDICATES = [
58
58
  "rdf:type", "rdfs:subClassOf", "mgx:display-name",
59
+ "mgx:model", "mgx:rotation",
59
60
  "mgx:is-container", "mgx:is-predator", "mgx:is-origin",
60
61
  "mgx:dig-spawns", "mgx:den-spawns", "mgx:den-resident",
61
62
  "mgx:dig-reach", "mgx:dig-spawn-max", "mgx:den-chance-in", "mgx:den-resident-chance-in",
@@ -68,6 +69,33 @@ const TRUE_ONLY_FLAG_PREDICATES = ["mgx:is-container", "mgx:is-predator", "mgx:i
68
69
 
69
70
  // ---- rendering ---------------------------------------------------------------
70
71
 
72
+ /** A GRID world's folded state in the shape this module's own renderer and
73
+ * differ read. A room world folds a placement into `{ predicate, object }`,
74
+ * because a thing can be carried, hidden or fixed as well as stood in. A grid
75
+ * world folds it into `{ cell, turn, epoch }`: on a board there is one way to
76
+ * be somewhere, and the extra fields carry the (epoch, turn) rank instead.
77
+ *
78
+ * Handed a grid fold directly, `renderMudEditorText` reads `place.predicate`
79
+ * as undefined and renders no placement sentence at all, and
80
+ * `planMudEditorSync` reads every placement line as a change and re-appends
81
+ * it. One translation here keeps both honest, and keeps a second copy of the
82
+ * mapping out of each page that needs it.
83
+ *
84
+ * `masses` comes across too, since a grid fold names it `mass`. There is no
85
+ * openness on a board, so that map is empty. Pure, self-contained (spliced by
86
+ * `.toString()` alongside the two functions it feeds). */
87
+ export function gridWorldEditorState(state) {
88
+ const placements = new Map();
89
+ for (const [subject, place] of (state && state.placements) || new Map()) {
90
+ if (place && place.cell) placements.set(subject, { predicate: "mgx:currently-in", object: place.cell });
91
+ }
92
+ const masses = new Map();
93
+ for (const [subject, mass] of (state && state.mass) || new Map()) {
94
+ if (mass && mass.value !== undefined) masses.set(subject, { value: mass.value });
95
+ }
96
+ return { placements, masses, openness: new Map() };
97
+ }
98
+
71
99
  /** The whole burrow's editable facts as plain sentences, one per line, sorted by
72
100
  * (subject, predicate, object) so two edits apart produce a reviewable diff
73
101
  * rather than a reshuffle. Placement and openness come from the FOLDED state, so
@@ -125,6 +153,8 @@ export function renderMudEditorText(rows, state) {
125
153
  if (p === "rdf:type") { push(s, p, o, `${cap(s)} ${typePhraseFor(o)} ${o}.`); continue; }
126
154
  if (p === "rdfs:subClassOf") { push(s, p, o, `${cap(s)} is a kind of ${o}.`); continue; }
127
155
  if (p === "mgx:display-name") { push(s, p, o, `${cap(s)} is shown as ${o}.`); continue; }
156
+ if (p === "mgx:model") { push(s, p, o, `${cap(s)} is modelled as ${o}.`); continue; }
157
+ if (p === "mgx:rotation") { push(s, p, o, `${cap(s)} is turned ${o} degrees.`); continue; }
128
158
  if (p === "mgx:is-container" && o === "true") { push(s, p, o, `${cap(s)} is a container.`); continue; }
129
159
  if (p === "mgx:is-predator" && o === "true") { push(s, p, o, `${cap(s)} hunts the other animals.`); continue; }
130
160
  if (p === "mgx:is-origin" && o === "true") { push(s, p, o, `${cap(s)} is where the burrow starts.`); continue; }
@@ -179,6 +209,14 @@ const LINE_PATTERNS = [
179
209
  build: (m) => ({ subject: LOW(m[1]), predicate: "mgx:hasMass", object: m[2] }) },
180
210
  { re: /^(.+?)\s+is\s+shown\s+as\s+(.+?)\.?$/i,
181
211
  build: (m) => ({ subject: LOW(m[1]), predicate: "mgx:display-name", object: LOW(m[2]) }) },
212
+ // The two facts a 3D view needs and no other sentence in this table can
213
+ // say: which mesh draws a thing, and how far round it is turned. Kept
214
+ // apart from "is shown as" (a plain reading name, which every surface uses)
215
+ // because a renderer asking for the mesh must never be handed the name.
216
+ { re: /^(.+?)\s+is\s+modelled\s+as\s+(.+?)\.?$/i,
217
+ build: (m) => ({ subject: LOW(m[1]), predicate: "mgx:model", object: LOW(m[2]) }) },
218
+ { re: /^(.+?)\s+is\s+turned\s+(-?[\d.]+)\s+degrees?\.?$/i,
219
+ build: (m) => ({ subject: LOW(m[1]), predicate: "mgx:rotation", object: m[2] }) },
182
220
  { re: /^(.+?)\s+loses\s+([\d.]+)\s+mass\s+a\s+turn\.?$/i,
183
221
  build: (m) => ({ subject: LOW(m[1]), predicate: "mgx:mass-drain-per-turn", object: m[2] }) },
184
222
  // The dig knobs. "at most N things" is tried before the plain "turns up X" so
@@ -306,3 +344,45 @@ export function planMudEditorSync(rows, state, triples) {
306
344
  }
307
345
  return { toAppend, toRemoveIds };
308
346
  }
347
+
348
+ /** The additive half of planMudEditorSync, for ONE already-parsed triple: the
349
+ * rows a single taught sentence implies, and never a retraction. A whole
350
+ * document says what the world contains, so a fact missing from it has gone;
351
+ * one sentence only ever says what it says, so nothing it leaves out is
352
+ * evidence of anything. Re-asserting a fact the world already holds appends
353
+ * nothing, and `reason` says which of the two happened. Pure. */
354
+ export function planTaughtMudTriple(rows, state, triple) {
355
+ if (!triple?.subject || !triple?.object) return { toAppend: [], reason: "nothing parsed" };
356
+ if (triple.kind === PLACEMENT_KIND) {
357
+ const current = state?.placements?.get(triple.subject);
358
+ if (current && current.predicate === triple.predicate && current.object === triple.object) {
359
+ return { toAppend: [], reason: `${triple.subject} is already ${triple.predicate} ${triple.object}` };
360
+ }
361
+ return {
362
+ toAppend: [triple],
363
+ reason: current
364
+ ? `${triple.subject} moves from ${current.object} to ${triple.object}`
365
+ : `${triple.subject} is placed ${triple.predicate} ${triple.object}`,
366
+ };
367
+ }
368
+ if (triple.kind === OPENNESS_KIND) {
369
+ const current = state?.openness?.get(triple.subject);
370
+ const wantOpen = triple.object === "true";
371
+ if (current && current.open === wantOpen) {
372
+ return { toAppend: [], reason: `${triple.subject} is already ${wantOpen ? "open" : "closed"}` };
373
+ }
374
+ return { toAppend: [triple], reason: `${triple.subject} becomes ${wantOpen ? "open" : "closed"}` };
375
+ }
376
+ if (triple.kind === MASS_KIND) {
377
+ const current = state?.masses?.get(triple.subject);
378
+ if (current && Number(current.value) === Number(triple.object)) {
379
+ return { toAppend: [], reason: `${triple.subject} already weighs ${triple.object}` };
380
+ }
381
+ return { toAppend: [triple], reason: `${triple.subject} weighs ${triple.object}` };
382
+ }
383
+ const key = tripleKey(triple);
384
+ if (editableMudOtherRows(rows).some((r) => tripleKey(r) === key)) {
385
+ return { toAppend: [], reason: `the world already says ${key}` };
386
+ }
387
+ return { toAppend: [triple], reason: `the world gains ${key}` };
388
+ }