@polycode-projects/the-mechanical-code-talker 4.1.0 → 4.1.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.
@@ -549,7 +549,7 @@ function sparkCaptionHtml(stats) {
549
549
  * self-contained document with no external requests. Only
550
550
  * scripts/build-demo-site.mjs, which builds the sibling bundle itself
551
551
  * first, passes `true`. The dock's own runtime code below ALSO gates on
552
- * `typeof tmctLedger !== "undefined"` regardless — the two checks answer
552
+ * `typeof tmct !== "undefined"` regardless — the two checks answer
553
553
  * different questions (did this render even offer the reference; did the
554
554
  * browser actually manage to load it), and both must hold for the live
555
555
  * path to run. */
@@ -564,7 +564,7 @@ export function renderLedgerHtml({ rows, terms, edges, focus, contradictions, wo
564
564
  // really in this payload, otherwise a real term from this graph. Left as
565
565
  // the query-only wording even when the live bundle is offered — the dock's
566
566
  // own script swaps it for a teach-aware placeholder the moment it confirms
567
- // tmctLedger actually loaded (never claimed ahead of that confirmation).
567
+ // the live engine actually loaded (never claimed ahead of that confirmation).
568
568
  // The example term skips anything under 3 characters — the SAME floor the
569
569
  // dock's own miss-tips apply below — so a graph whose highest-degree term
570
570
  // is a short stopword-shaped fragment (init:large corpora carry plenty:
@@ -575,7 +575,7 @@ export function renderLedgerHtml({ rows, terms, edges, focus, contradictions, wo
575
575
  ? "who is the grandfather of ishmael"
576
576
  : (exampleTerm ? `ask the graph… e.g. what is ${exampleTerm.term}` : "ask the graph…");
577
577
  // The paste-and-drop ingest panel plus the JSONL export ride the LIVE
578
- // engine (window.tmctLedger): both teach into and read the dock's own
578
+ // engine (window.tmct): both teach into and read the dock's own
579
579
  // session store, so they appear only where that bundle is offered — the
580
580
  // demo site, never the CLI's self-contained tmct viz page.
581
581
  const ingestHtml = ledgerBundleAvailable
@@ -888,14 +888,13 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
888
888
  // exists it points at that session's store, which teach/research grow in
889
889
  // place, so a digest of a just-taught term reads the fresh facts.
890
890
  let getLivePayload = () => PAYLOAD;
891
- // Whichever loaded bundle carries the browser digest helper — the demo
892
- // ledger's own live engine (tmctLedger) or the committed memory-ask engine
893
- // the CLI viz page inlines (tmctMemoryAsk). Null before either loads, and the
894
- // focus card then keeps whatever server-computed digest it shipped.
891
+ // The browser digest helper, from whichever engine this page loaded — the
892
+ // demo ledger's live one or the committed query-only one the CLI viz page
893
+ // inlines. Both publish it in the same place now, so there is one lookup
894
+ // rather than two. Null before either loads, and the focus card then keeps
895
+ // whatever server-computed digest it shipped.
895
896
  const digestHelper = () =>
896
- (typeof tmctLedger !== "undefined" && tmctLedger && tmctLedger.digestTermFromPayloadBrowser)
897
- || (typeof tmctMemoryAsk !== "undefined" && tmctMemoryAsk && tmctMemoryAsk.digestTermFromPayloadBrowser)
898
- || null;
897
+ (typeof tmct !== "undefined" && tmct.page && tmct.page.digestTermFromPayloadBrowser) || null;
899
898
  // The digest for one term, computed live in the browser from the embedded
900
899
  // structure table, or null when no structures were embedded, no engine has
901
900
  // loaded, or the term holds nothing to compose.
@@ -1138,9 +1137,9 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
1138
1137
  }
1139
1138
 
1140
1139
  // ---- the chat dock: LIVE teach+ask over the sibling ledger-browser.bundle.js
1141
- // (window.tmctLedger) when the demo build offered it AND it actually
1142
- // loaded; falls back to the read-only tmctMemoryAsk engine below
1143
- // unchanged from before this page could teach at all — otherwise.
1140
+ // (window.tmct) when the demo build offered it AND it actually
1141
+ // loaded; falls back to the read-only engine below, which publishes the
1142
+ // same surface as a stand-in and says so through tmct.fallback.
1144
1143
  // bin/tmct.mjs's own \`tmct viz\` output never offers the live bundle
1145
1144
  // (renderLedgerHtml's own ledgerBundleAvailable defaults false there), so
1146
1145
  // this branch is simply never reachable on a CLI-generated page.
@@ -1148,7 +1147,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
1148
1147
  const createTicker = ${createTicker.toString()};
1149
1148
  const prefersReducedMotion = ${prefersReducedMotion.toString()};
1150
1149
  const chatForm = el("chatform");
1151
- if (chatForm && typeof tmctLedger !== "undefined" && typeof tmctLedger.createLedgerSession === "function") {
1150
+ if (chatForm && typeof tmct !== "undefined" && !tmct.fallback) {
1152
1151
  const log = el("chatlog");
1153
1152
  const chatqEl = el("chatq");
1154
1153
  chatqEl.placeholder = 'ask or teach the graph\\u2026 e.g. "blue is a peg"';
@@ -1183,7 +1182,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
1183
1182
  import("./vendor/wink.js"),
1184
1183
  winkTimeout(WINK_LOAD_TIMEOUT_MS, "wink vendor asset load timed out"),
1185
1184
  ]);
1186
- tmctLedger.registerWinkModel(() => ({ winkNLP: mod.winkNLP, model: mod.model }));
1185
+ tmct.page.registerWinkModel(() => ({ winkNLP: mod.winkNLP, model: mod.model }));
1187
1186
  } catch (err) {
1188
1187
  // eslint-disable-next-line no-console
1189
1188
  console.warn("tmct ledger: the wink vendor asset failed to load, continuing without the lemma/POS tier", err);
@@ -1196,7 +1195,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
1196
1195
  async function ensureSession() {
1197
1196
  if (session) return session;
1198
1197
  await tryLoadWink();
1199
- session = await tmctLedger.createLedgerSession({ seedPayload: PAYLOAD });
1198
+ session = await tmct.open({ seedPayload: PAYLOAD });
1200
1199
  // From here the live store is the source of truth for the digest, so a
1201
1200
  // digest of a term taught this session reads its fresh facts — the store
1202
1201
  // grows in place, so this one closure stays current.
@@ -1214,15 +1213,15 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
1214
1213
  chatqEl.disabled = true;
1215
1214
  withLock(async () => {
1216
1215
  try {
1217
- const s = await ensureSession();
1218
- const result = await s.turn(q);
1216
+ const live = await ensureSession();
1217
+ const result = await tmct.turn(q);
1219
1218
  const record = result.record;
1220
1219
  const taught = !!record && record.miss === false && (record.via === "assert" || record.via === "retract");
1221
1220
  const body = esc(result.answer).replace(/\\n/g, "<br>");
1222
1221
  pending.className = "a" + (taught ? " taught" : (record && record.miss ? " miss" : ""));
1223
1222
  pending.innerHTML = taught ? '<span class="tag">taught</span>' + body : body;
1224
1223
  if (taught) {
1225
- const fresh = tmctLedger.computeLedgerDataFromPayload(s.memoryDir.payload, {});
1224
+ const fresh = tmct.page.computeLedgerDataFromPayload(live.memoryDir.payload, {});
1226
1225
  applyLedgerData(fresh);
1227
1226
  } else if (!(record && record.miss)) {
1228
1227
  // Only a genuine answer (never a miss) tries to resolve a
@@ -1231,7 +1230,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
1231
1230
  // ordinary English word (e.g. "run", if the graph happens to
1232
1231
  // hold it), and resolveAnsweredTerm has no way to tell that
1233
1232
  // apart from the term genuinely being discussed.
1234
- const hit = resolveAnsweredTerm(result.answer, q, LEDGER.terms, tmctLedger.normFactTerm);
1233
+ const hit = resolveAnsweredTerm(result.answer, q, LEDGER.terms, tmct.page.normFactTerm);
1235
1234
  if (hit) refocusWithLabel(hit, q);
1236
1235
  }
1237
1236
  } catch {
@@ -1246,7 +1245,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
1246
1245
 
1247
1246
  // ---- ingest + export: bring your own text, take the graph away ---------
1248
1247
  // The ingest panel runs pasted/dropped/browsed text through the SAME dock
1249
- // session, one sentence at a time (tmctLedger.splitSentences, then
1248
+ // session, one sentence at a time (tmct.page.splitSentences, then
1250
1249
  // s.turn), keeping only the sentences the recognizer grounds — then
1251
1250
  // re-derives the whole ledger so the new facts can be examined in place.
1252
1251
  // Export serializes that same session store to canonical JSONL.
@@ -1282,13 +1281,13 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
1282
1281
  withLock(async () => {
1283
1282
  try {
1284
1283
  const s = await ensureSession();
1285
- const sentences = tmctLedger.splitSentences(text);
1284
+ const sentences = tmct.page.splitSentences(text);
1286
1285
  let grounded = 0;
1287
1286
  for (const sentence of sentences) {
1288
- const r = await s.turn(sentence);
1287
+ const r = await tmct.turn(sentence);
1289
1288
  if (r.record && r.record.miss === false && r.record.via === "assert") grounded += 1;
1290
1289
  }
1291
- const fresh = tmctLedger.computeLedgerDataFromPayload(s.memoryDir.payload, {});
1290
+ const fresh = tmct.page.computeLedgerDataFromPayload(s.memoryDir.payload, {});
1292
1291
  applyLedgerData(fresh);
1293
1292
  const skipped = sentences.length - grounded;
1294
1293
  statusEl.textContent = countLabel(sentences.length, "sentence")
@@ -1308,7 +1307,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
1308
1307
  withLock(async () => {
1309
1308
  try {
1310
1309
  const s = await ensureSession();
1311
- const jsonl = await tmctLedger.exportFactsJsonl(s.memoryDir);
1310
+ const jsonl = await tmct.page.exportFactsJsonl(s.memoryDir);
1312
1311
  const blob = new Blob([jsonl], { type: "application/x-ndjson" });
1313
1312
  const url = URL.createObjectURL(blob);
1314
1313
  const link = document.createElement("a");
@@ -1354,8 +1353,8 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
1354
1353
  const pending = addLine("a pending", "researching\\u2026");
1355
1354
  return withLock(async () => {
1356
1355
  try {
1357
- const s = await ensureSession();
1358
- const result = await s.turn(q);
1356
+ const live = await ensureSession();
1357
+ const result = await tmct.turn(q);
1359
1358
  const missed = !result.record || Boolean(result.record.miss);
1360
1359
  pending.className = "a" + (missed ? " miss" : "");
1361
1360
  pending.innerHTML = esc(result.answer).replace(/\\n/g, "<br>");
@@ -1363,7 +1362,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
1363
1362
  researchQueue = result.research;
1364
1363
  renderResearchControls();
1365
1364
  if (!missed) {
1366
- const fresh = tmctLedger.computeLedgerDataFromPayload(s.memoryDir.payload, {});
1365
+ const fresh = tmct.page.computeLedgerDataFromPayload(live.memoryDir.payload, {});
1367
1366
  applyLedgerData(fresh);
1368
1367
  }
1369
1368
  }
@@ -1406,9 +1405,11 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
1406
1405
  else researchTicker.play();
1407
1406
  });
1408
1407
  }
1409
- } else if (chatForm && typeof tmctMemoryAsk !== "undefined") {
1410
- const memHandle = tmctMemoryAsk.createInMemoryStore();
1411
- memHandle.payload = PAYLOAD;
1408
+ } else if (chatForm && typeof tmct !== "undefined") {
1409
+ // The query-only dock. One session over the page's own embedded payload,
1410
+ // and every line goes to tmct.ask the cascade that used to be chained
1411
+ // here by hand now lives behind that one call.
1412
+ const opened = tmct.open({ payload: PAYLOAD });
1412
1413
  const log = el("chatlog");
1413
1414
  const addLine = (cls, html) => {
1414
1415
  const d = document.createElement("div");
@@ -1423,20 +1424,16 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
1423
1424
  input.value = "";
1424
1425
  addLine("u", esc(q));
1425
1426
  (async () => {
1426
- let fact = null;
1427
- try { fact = await tmctMemoryAsk.factAnswer(memHandle, q, null, true, {}); } catch { fact = null; }
1428
- // runAsk's own cascade is factAnswer ?? factReadBack; chain the same
1429
- // way when the bundle exposes the second reader (relation chases
1430
- // "who is the grandfather of ishmael" — live there, not in factAnswer).
1431
- if (!(fact && fact.text) && typeof tmctMemoryAsk.factReadBack === "function") {
1432
- try { fact = await tmctMemoryAsk.factReadBack(memHandle, q, null, true, null); } catch { fact = null; }
1433
- }
1434
- if (fact && fact.text) {
1435
- addLine("a", esc(fact.text).replace(/\\n/g, "<br>"));
1427
+ await opened;
1428
+ let answer = null;
1429
+ let data = null;
1430
+ try { ({ answer, data } = await tmct.ask(q)); } catch { answer = null; data = null; }
1431
+ if (answer) {
1432
+ addLine("a", esc(answer).replace(/\\n/g, "<br>"));
1436
1433
  // Same formatting contract as chat's withGoalLine: capitalized,
1437
1434
  // full-stop-terminated, rendered only when the engine deduced one.
1438
- if (fact.goal) addLine("a goal", "Goal (inferred): " + esc(fact.goal.charAt(0).toUpperCase() + fact.goal.slice(1)) + ".");
1439
- const hit = resolveAnsweredTerm(fact.text, q, LEDGER.terms, tmctMemoryAsk.normFactTerm);
1435
+ if (data && data.goal) addLine("a goal", "Goal (inferred): " + esc(data.goal.charAt(0).toUpperCase() + data.goal.slice(1)) + ".");
1436
+ const hit = resolveAnsweredTerm(answer, q, LEDGER.terms, tmct.page.normFactTerm);
1440
1437
  if (hit) refocusWithLabel(hit, q);
1441
1438
  } else {
1442
1439
  const tips = LEDGER.terms.filter((t) => t.term.length >= 3).slice(0, 2)
@@ -184,7 +184,7 @@ export function burrowGraph(state, roomIds, root = "garden") {
184
184
  * allows that has no written exit yet. Above ground there is nothing to
185
185
  * tunnel sideways THROUGH, so a garden digs straight down and no other way;
186
186
  * underground, all six are open soil. The engine is the authority on this —
187
- * the page prefers `tmctMud.diggableDirections` whenever the bundle exposes
187
+ * the page prefers `tmct.page.diggableDirections` whenever the bundle exposes
188
188
  * one and only falls back to the rule below. Pure, self-contained
189
189
  * (roomKindForRoom is spliced alongside it). */
190
190
  export function diggableDirections(rows, state, here) {
@@ -1339,7 +1339,7 @@ function pageScript() {
1339
1339
  // world's ending sentence comes from the engine either way, never a second
1340
1340
  // wording of the same event.
1341
1341
  appendChat(character, "a", note
1342
- || (window.tmctMud.outOfPlayPhrase(character, fate) + ". It takes no more turns."));
1342
+ || (window.tmct.page.outOfPlayPhrase(character, fate) + ". It takes no more turns."));
1343
1343
  playFateScene(character, fate);
1344
1344
  }
1345
1345
 
@@ -1372,7 +1372,7 @@ function pageScript() {
1372
1372
  const rows = lastSnapshot.rows;
1373
1373
  const state = lastSnapshot.state;
1374
1374
  renderRoomView(character, rows, state, den, allRoomIds(rows),
1375
- window.tmctMud.castInRoom(rows, state, den, character));
1375
+ window.tmct.page.castInRoom(rows, state, den, character));
1376
1376
 
1377
1377
  const room = el(w + "-room");
1378
1378
  const lunger = room.querySelector('.floor-others .sprite-card[data-subject="' + predator + '"]');
@@ -1462,7 +1462,7 @@ function pageScript() {
1462
1462
  return { answer: "", end: false };
1463
1463
  });
1464
1464
  }
1465
- return serializeTick(function () { return session.windows[character].turn(line); }).then(async function (res) {
1465
+ return serializeTick(function () { return tmct.turn(line, { as: character }); }).then(async function (res) {
1466
1466
  appendChat(character, "a", res.answer);
1467
1467
  await broadcastLocalChange();
1468
1468
  renderSoon();
@@ -1556,8 +1556,8 @@ function pageScript() {
1556
1556
  }
1557
1557
 
1558
1558
  function spriteSvgFor(species, rows, instanceKey) {
1559
- if (window.tmctMud && window.tmctMud.resolveSpriteAsset) {
1560
- return window.tmctMud.resolveSpriteAsset(species, rows, [], DATA.spriteTemplates, window.tmctMud.SPRITE_REGISTRY, { instanceKey: instanceKey });
1559
+ if (window.tmct && window.tmct.page.resolveSpriteAsset) {
1560
+ return window.tmct.page.resolveSpriteAsset(species, rows, [], DATA.spriteTemplates, window.tmct.page.SPRITE_REGISTRY, { instanceKey: instanceKey });
1561
1561
  }
1562
1562
  return "";
1563
1563
  }
@@ -1570,10 +1570,10 @@ function pageScript() {
1570
1570
  // "animal": everything hung on this wall is a thing, and a thing with no
1571
1571
  // sprite should read as a plain parcel rather than a creature.
1572
1572
  function objectSvgFor(subject, rows) {
1573
- if (window.tmctMud && window.tmctMud.resolveSpriteAsset) {
1574
- return window.tmctMud.resolveSpriteAsset(
1573
+ if (window.tmct && window.tmct.page.resolveSpriteAsset) {
1574
+ return window.tmct.page.resolveSpriteAsset(
1575
1575
  subject, spriteAncestryRows(rows, subject), factsForSubject(rows, subject),
1576
- DATA.spriteTemplates, window.tmctMud.SPRITE_REGISTRY,
1576
+ DATA.spriteTemplates, window.tmct.page.SPRITE_REGISTRY,
1577
1577
  { rootFallback: "portable", instanceKey: subject },
1578
1578
  );
1579
1579
  }
@@ -1736,7 +1736,7 @@ function pageScript() {
1736
1736
  // than the pane offers to talk to.
1737
1737
  function roomCaptionFor(rows, state, here, character, roomMates) {
1738
1738
  const parts = [];
1739
- const view = window.tmctMud.worldDigestRows(rows, state, character);
1739
+ const view = window.tmct.page.worldDigestRows(rows, state, character);
1740
1740
  const lines = view.filter(function (r) { return r.subject.toLowerCase() === here.toLowerCase(); })
1741
1741
  .map(function (r) { return r.subject + " " + r.predicate + " " + r.object + "."; });
1742
1742
  parts.push(lines.length ? lines.join(" ") : "You are in the " + here + ".");
@@ -1755,7 +1755,7 @@ function pageScript() {
1755
1755
  // direction with neither draws nothing, so the ring never offers a command
1756
1756
  // the world would refuse.
1757
1757
  function diggableFor(rows, state, here) {
1758
- const fromEngine = window.tmctMud && window.tmctMud.diggableDirections;
1758
+ const fromEngine = window.tmct && window.tmct.page.diggableDirections;
1759
1759
  return fromEngine ? fromEngine(rows, state, here) : diggableDirections(rows, state, here);
1760
1760
  }
1761
1761
 
@@ -1782,7 +1782,7 @@ function pageScript() {
1782
1782
  // it. Exits appear in both the row and the compass ring on purpose: the ring
1783
1783
  // says WHERE a way out points, the row says what it is called.
1784
1784
  function affordancesFor(rows, state, here, character) {
1785
- const fromEngine = window.tmctMud && window.tmctMud.roomAffordances;
1785
+ const fromEngine = window.tmct && window.tmct.page.roomAffordances;
1786
1786
  return fromEngine ? fromEngine(rows, state, here, character) : [];
1787
1787
  }
1788
1788
 
@@ -1838,7 +1838,7 @@ function pageScript() {
1838
1838
  // can say "carrot" while every verb still resolves the distinct id
1839
1839
  // ("carrot-1"). itemLabel is the fallback for an id carrying no such fact.
1840
1840
  function labelForItem(rows, subject, roomIds) {
1841
- const fromEngine = window.tmctMud && window.tmctMud.displayNameOf;
1841
+ const fromEngine = window.tmct && window.tmct.page.displayNameOf;
1842
1842
  const name = fromEngine ? fromEngine(rows, subject) : null;
1843
1843
  return name && name !== subject ? name : itemLabel(subject, roomIds);
1844
1844
  }
@@ -1878,7 +1878,7 @@ function pageScript() {
1878
1878
  // the survey they annotate, and each one's own dot already carries its name.
1879
1879
  const keys = cast.map(function (c) {
1880
1880
  const room = state.placements.get(c) ? state.placements.get(c).object : "?";
1881
- const fate = window.tmctMud.outOfPlayReasonOf(state, c);
1881
+ const fate = window.tmct.page.outOfPlayReasonOf(state, c);
1882
1882
  const where = fate ? fate : "in " + esc(room);
1883
1883
  return '<span class="key-actor"><span class="key-dot" style="background:' + colorFor(c) + '"></span>'
1884
1884
  + esc(speciesOfCharacter(c)) + " " + where + "</span>";
@@ -1939,7 +1939,7 @@ function pageScript() {
1939
1939
  }
1940
1940
  }
1941
1941
  for (const character of everyone()) {
1942
- const reason = window.tmctMud.outOfPlayReasonOf(snap.state, character);
1942
+ const reason = window.tmct.page.outOfPlayReasonOf(snap.state, character);
1943
1943
  if (reason) markOutOfPlay(character, reason, null);
1944
1944
  }
1945
1945
  renderWorldMap(snap.rows, snap.state, roomIds);
@@ -1954,7 +1954,7 @@ function pageScript() {
1954
1954
  const place = snap.state.placements.get(character);
1955
1955
  const here = place ? place.object : null;
1956
1956
  if (!here) continue;
1957
- const roomMates = window.tmctMud.castInRoom(snap.rows, snap.state, here, character);
1957
+ const roomMates = window.tmct.page.castInRoom(snap.rows, snap.state, here, character);
1958
1958
  renderRoomView(character, snap.rows, snap.state, here, roomIds, roomMates);
1959
1959
  renderDirections(character, snap.rows, snap.state, here);
1960
1960
  renderChatPills(character, snap.rows, snap.state, here, roomIds);
@@ -2072,9 +2072,9 @@ function pageScript() {
2072
2072
  function renderSuggestionPills() {
2073
2073
  const box = el("editorPills");
2074
2074
  const term = wordBeforeCursor(el("editorText").value, el("editorText").selectionStart);
2075
- if (!term || !window.tmctMud) { box.innerHTML = ""; return; }
2076
- const related = window.tmctMud.relatedForTerm ? window.tmctMud.relatedForTerm(allStoreRows, term) : null;
2077
- const chain = window.tmctMud.classAncestorChain ? window.tmctMud.classAncestorChain(term, allStoreRows) : [];
2075
+ if (!term || !window.tmct) { box.innerHTML = ""; return; }
2076
+ const related = window.tmct.page.relatedForTerm ? window.tmct.page.relatedForTerm(allStoreRows, term) : null;
2077
+ const chain = window.tmct.page.classAncestorChain ? window.tmct.page.classAncestorChain(term, allStoreRows) : [];
2078
2078
  const seen = { };
2079
2079
  seen[term] = true;
2080
2080
  const out = [];
@@ -2113,7 +2113,7 @@ function pageScript() {
2113
2113
  const snap = await session.snapshot();
2114
2114
  allStoreRows = snap.rows;
2115
2115
  editRows = worldOnlyRows(snap.rows);
2116
- editState = window.tmctMud.foldWorldState(window.tmctMud.worldActionRows(editRows));
2116
+ editState = window.tmct.page.foldWorldState(window.tmct.page.worldActionRows(editRows));
2117
2117
  if (result.unrecognized.length) {
2118
2118
  const lines = result.unrecognized.map(function (u) { return u.line; }).join(", ");
2119
2119
  status.className = "edit-status pending";
@@ -2169,7 +2169,7 @@ function pageScript() {
2169
2169
  const snap = await session.snapshot();
2170
2170
  allStoreRows = snap.rows;
2171
2171
  editRows = worldOnlyRows(snap.rows);
2172
- editState = window.tmctMud.foldWorldState(window.tmctMud.worldActionRows(editRows));
2172
+ editState = window.tmct.page.foldWorldState(window.tmct.page.worldActionRows(editRows));
2173
2173
  el("editorText").value = renderMudEditorText(editRows, editState);
2174
2174
  el("editorStatus").className = "edit-status";
2175
2175
  el("editorStatus").textContent = "";
@@ -2249,8 +2249,8 @@ function pageScript() {
2249
2249
  // engine calls live world state, plus the P2P layer's own four predicates.
2250
2250
  // The check is handed over rather than imported, so the sync filter never
2251
2251
  // learns the world engine's private predicate names.
2252
- const extraPredicates = (window.tmctMud.P2P_PREDICATES || []).slice();
2253
- const isState = window.tmctMud.isMudStatePredicate;
2252
+ const extraPredicates = (window.tmct.page.P2P_PREDICATES || []).slice();
2253
+ const isState = window.tmct.page.isMudStatePredicate;
2254
2254
  room = mod.createP2pRoom({
2255
2255
  memoryDir: session.memoryDir,
2256
2256
  myPeerId: myPeerId,
@@ -2803,15 +2803,21 @@ function pageScript() {
2803
2803
  // so the roster belongs to the scenario rather than to the page.
2804
2804
  roster = rosterOf(scenario());
2805
2805
  rootRoom = rootRoomOf(scenario());
2806
- cast = window.tmctMud.pickMudRoster(roster, { count: chosenPlayerCount() });
2807
- const pool = window.tmctMud.expandMudRoster(roster, cast.length + npcCount)
2806
+ cast = window.tmct.page.pickMudRoster(roster, { count: chosenPlayerCount() });
2807
+ const pool = window.tmct.page.expandMudRoster(roster, cast.length + npcCount)
2808
2808
  .filter(function (id) { return cast.indexOf(id) === -1; });
2809
- npcs = window.tmctMud.pickMudRoster(pool, { count: npcCount });
2809
+ npcs = window.tmct.page.pickMudRoster(pool, { count: npcCount });
2810
2810
  slots = DATA.paneSlots.slice(0, cast.length);
2811
2811
  showPlayerCount(cast.length);
2812
2812
  showNpcCount(npcs.length);
2813
2813
  renderStage();
2814
- const opened = await window.tmctMud.createMudSession(scenario().worldPayload, { characters: everyone(), epoch: nextEpoch });
2814
+ // slotOf has to match cast from the same synchronous stretch that just
2815
+ // rebuilt the DOM for it \\u2014 a render triggered anywhere in the awaits
2816
+ // below (rebind's own re-sync can trigger one) must never see this
2817
+ // scenario's cast paired with the LAST scenario's pane ids, which is what
2818
+ // a null pane-element lookup in renderAll means when it happens.
2819
+ for (let i = 0; i < cast.length; i += 1) slotOf[cast[i]] = slots[i];
2820
+ const opened = await window.tmct.open(scenario().worldPayload, { characters: everyone(), epoch: nextEpoch });
2815
2821
  if (seq !== bootSeq) return;
2816
2822
  session = opened;
2817
2823
  if (liveRoom) {
@@ -2828,7 +2834,6 @@ function pageScript() {
2828
2834
  dropRoom("the link didn't survive the recast \\u2014 share again to link up.");
2829
2835
  }
2830
2836
  }
2831
- for (let i = 0; i < cast.length; i += 1) slotOf[cast[i]] = slots[i];
2832
2837
  bindPanes();
2833
2838
  for (const character of cast) {
2834
2839
  const w = paneIdFor(character);
@@ -665,7 +665,7 @@ const PLAN = ${embedded};
665
665
  mountPlan(PLAN);
666
666
 
667
667
  // ---- live re-solve: disk-count/max-depth controls + the chat-assert dock
668
- // over the sibling plan-browser.bundle.js (window.tmctPlan). Degrades
668
+ // over the sibling plan-browser.bundle.js (window.tmct). Degrades
669
669
  // honestly when the bundle failed to load or wasn't built alongside this
670
670
  // page (e.g. a plain renderPlanHtml() call with no bundle nearby) — the
671
671
  // baked-in replay above already stands on its own either way.
@@ -678,7 +678,7 @@ const PLAN = ${embedded};
678
678
  const chatqEl = document.getElementById("chatq");
679
679
  const chatpillsEl = document.getElementById("chatpills");
680
680
 
681
- const liveAvailable = typeof tmctPlan !== "undefined" && typeof tmctPlan.createPlanSession === "function";
681
+ const liveAvailable = typeof tmct !== "undefined" && typeof tmct.open === "function";
682
682
  if (!liveAvailable) {
683
683
  liveStatusEl.textContent = "live re-solve unavailable here — showing the baked-in replay only.";
684
684
  liveStatusEl.classList.add("isError");
@@ -712,7 +712,7 @@ const PLAN = ${embedded};
712
712
  import("./vendor/wink.js"),
713
713
  winkTimeout(WINK_LOAD_TIMEOUT_MS, "wink vendor asset load timed out"),
714
714
  ]);
715
- tmctPlan.registerWinkModel(() => ({ winkNLP: mod.winkNLP, model: mod.model }));
715
+ tmct.page.registerWinkModel(() => ({ winkNLP: mod.winkNLP, model: mod.model }));
716
716
  } catch (err) {
717
717
  // eslint-disable-next-line no-console
718
718
  console.warn("tmct plan: the wink vendor asset failed to load, continuing without the lemma/POS tier", err);
@@ -729,9 +729,9 @@ const PLAN = ${embedded};
729
729
  }
730
730
  function applyPlan(freshPlan) {
731
731
  if (!freshPlan) return;
732
- const { rendersAs, sizeOrder } = tmctPlan.renderInputsFromPlan(freshPlan);
733
- mountPlan(tmctPlan.planToPageData({ plan: freshPlan, rendersAs, sizeOrder }));
734
- if (pddlEl) pddlEl.textContent = tmctPlan.planToPddl(freshPlan);
732
+ const { rendersAs, sizeOrder } = tmct.page.renderInputsFromPlan(freshPlan);
733
+ mountPlan(tmct.page.planToPageData({ plan: freshPlan, rendersAs, sizeOrder }));
734
+ if (pddlEl) pddlEl.textContent = tmct.page.planToPddl(freshPlan);
735
735
  // The <title>/<h1> were baked from the INITIAL plan's own goal text —
736
736
  // a live re-solve toward a different goal (a fresh puzzle, or a
737
737
  // taught goal revision) must not leave them stating the old one.
@@ -742,7 +742,7 @@ const PLAN = ${embedded};
742
742
  async function ensureSession() {
743
743
  if (session) return session;
744
744
  await tryLoadWink();
745
- session = await tmctPlan.createPlanSession({
745
+ session = await tmct.open({
746
746
  diskCount: Math.max(1, Math.min(7, parseInt(diskCountEl.value, 10) || 3)),
747
747
  maxDepth: Math.max(1, parseInt(maxDepthEl.value, 10) || 300),
748
748
  });
@@ -757,7 +757,7 @@ const PLAN = ${embedded};
757
757
  liveStatusEl.textContent = "solving a " + n + "-disk puzzle…";
758
758
  liveStatusEl.classList.remove("isError");
759
759
  await tryLoadWink();
760
- session = await tmctPlan.createPlanSession({ diskCount: n, maxDepth: d });
760
+ session = await tmct.open({ diskCount: n, maxDepth: d });
761
761
  if (session.plan) {
762
762
  applyPlan(session.plan);
763
763
  liveStatusEl.textContent = "live — " + countLabel(n, "disk", "disks") + ", max depth " + d + ".";
@@ -777,7 +777,7 @@ const PLAN = ${embedded};
777
777
  withLock(async () => {
778
778
  const s = await ensureSession();
779
779
  const maxDepth = Math.max(1, parseInt(maxDepthEl.value, 10) || 300);
780
- const result = await s.turn(q, { maxDepth });
780
+ const result = await tmct.turn(q, { maxDepth });
781
781
  addChatLine("a", result.answer);
782
782
  if (result.plan) applyPlan(result.plan);
783
783
  });
@@ -2,7 +2,7 @@
2
2
  // document shaped exactly like ingest-viz.mjs/chat-page-viz.mjs's own
3
3
  // page-builders — one inlined <style> importing viz-theme.mjs's shared tokens,
4
4
  // behaviour as an inlined IIFE — running the research engine
5
- // (research-browser.bundle.js's globalThis.tmctResearch) by same-origin
5
+ // (research-browser.bundle.js's globalThis.tmct) by same-origin
6
6
  // relative paths.
7
7
  //
8
8
  // One in-memory graph grows three ways, each visible on the page:
@@ -392,7 +392,7 @@ ${DASH_DARK_CHROME_CSS}
392
392
  if (progressActive) statEngineEl.textContent = loadProgressLine(Object.values(progressParts));
393
393
  }
394
394
 
395
- const loadWink = loadWinkVendor({ register: (factory) => window.tmctResearch.registerWinkModel(factory) });
395
+ const loadWink = loadWinkVendor({ register: (factory) => window.tmct.page.registerWinkModel(factory) });
396
396
 
397
397
  let seedPayload = null;
398
398
  let seedFacts = 0;
@@ -406,8 +406,8 @@ ${DASH_DARK_CHROME_CSS}
406
406
  console.warn("tmct research: chat-seed.json unavailable — starting unseeded", err);
407
407
  }
408
408
  }
409
- function newSession() {
410
- return window.tmctResearch.createResearchSession({ seedPayload: cloneMemoryPayload(seedPayload), vocabSeeded: Boolean(seedPayload), digestStructures: DIGEST_STRUCTURES });
409
+ async function newSession() {
410
+ return window.tmct.open({ seedPayload: cloneMemoryPayload(seedPayload), vocabSeeded: Boolean(seedPayload), digestStructures: DIGEST_STRUCTURES });
411
411
  }
412
412
 
413
413
  // The curated reference pack provider, same fetch seam chat.html registers,
@@ -432,7 +432,7 @@ ${DASH_DARK_CHROME_CSS}
432
432
  };
433
433
 
434
434
  async function boot() {
435
- if (!window.tmctResearch) {
435
+ if (!window.tmct) {
436
436
  engineNoteEl.hidden = false;
437
437
  engineNoteEl.textContent = "The research engine did not load. Run npm run demo:build, then reload this page.";
438
438
  statSeedEl.textContent = "—";
@@ -441,8 +441,8 @@ ${DASH_DARK_CHROME_CSS}
441
441
  }
442
442
  const [winkStatus] = await Promise.all([loadWink(), fetchSeed()]);
443
443
  progressActive = false;
444
- window.tmctResearch.registerReferencePackProvider(fetchPackProvider);
445
- session = newSession();
444
+ window.tmct.page.registerReferencePackProvider(fetchPackProvider);
445
+ session = await newSession();
446
446
  const winkPart = winkStatus === "loaded" ? "wink-nlp loaded" : "wink-nlp unavailable, curated tiers only";
447
447
  statSeedEl.textContent = seedPayload ? seedFacts.toLocaleString() + " facts" : "no seed";
448
448
  statEngineEl.textContent = winkPart + " · ready";
@@ -465,7 +465,7 @@ ${DASH_DARK_CHROME_CSS}
465
465
  async function refresh() {
466
466
  if (!session) return;
467
467
  let snap;
468
- try { snap = await window.tmctResearch.researchSnapshot(session.memoryDir, session.sessionIds); }
468
+ try { snap = await window.tmct.page.researchSnapshot(session.memoryDir, session.sessionIds); }
469
469
  catch (err) { console.warn("tmct research: snapshot failed", err); return; }
470
470
  const total = (snap.sources || []).reduce((sum, source) => sum + (Number(source.count) || 0), 0);
471
471
  statFactsEl.textContent = total.toLocaleString();
@@ -626,15 +626,15 @@ ${DASH_DARK_CHROME_CSS}
626
626
  answerEl.className = "";
627
627
  answerEl.textContent = "thinking…";
628
628
  let res;
629
- try { res = await session.ask(q, { sources: allChecked ? null : checked }); }
630
- catch (err) { res = { text: "", miss: true }; }
631
- if (res.miss || !res.text) {
629
+ try { res = await tmct.ask(q, { sources: allChecked ? null : checked }); }
630
+ catch (err) { res = { answer: "", miss: true }; }
631
+ if (res.miss || !res.answer) {
632
632
  answerEl.className = "miss";
633
633
  const scope = allChecked ? "any checked source" : "the " + checked.length + " checked source" + (checked.length === 1 ? "" : "s");
634
634
  answerEl.textContent = "No grounded answer from " + scope + ". It abstains rather than guess.";
635
635
  } else {
636
636
  answerEl.className = "";
637
- answerEl.textContent = res.text;
637
+ answerEl.textContent = res.answer;
638
638
  }
639
639
  }
640
640
  el("askGo").addEventListener("click", ask);
@@ -705,7 +705,7 @@ ${DASH_DARK_CHROME_CSS}
705
705
  const note = el("teachNote");
706
706
  note.textContent = "…";
707
707
  let res;
708
- try { res = await session.turn(q); } catch { res = null; }
708
+ try { res = await tmct.turn(q); } catch { res = null; }
709
709
  if (res && res.record && res.record.via === "assert" && !res.record.miss) {
710
710
  note.textContent = "stored: " + (res.answer || "remembered.");
711
711
  el("teachInput").value = "";
@@ -786,7 +786,7 @@ ${DASH_DARK_CHROME_CSS}
786
786
  async function researchStep(line) {
787
787
  if (!session) return;
788
788
  let res;
789
- try { res = await session.turn(line); } catch { res = null; }
789
+ try { res = await tmct.turn(line); } catch { res = null; }
790
790
  if (res && res.research !== undefined) {
791
791
  researchQueue = res.research;
792
792
  renderResearchControls();
@@ -817,9 +817,9 @@ ${DASH_DARK_CHROME_CSS}
817
817
 
818
818
  // ---- tools --------------------------------------------------------------
819
819
  el("exportFacts").addEventListener("click", async () => {
820
- if (!session || !window.tmctResearch.exportFactsJsonl) return;
820
+ if (!session || !window.tmct.page.exportFactsJsonl) return;
821
821
  let jsonl;
822
- try { jsonl = await window.tmctResearch.exportFactsJsonl(session.memoryDir); }
822
+ try { jsonl = await window.tmct.page.exportFactsJsonl(session.memoryDir); }
823
823
  catch { return; }
824
824
  const blob = new Blob([jsonl], { type: "application/x-ndjson" });
825
825
  const url = URL.createObjectURL(blob);
@@ -832,7 +832,7 @@ ${DASH_DARK_CHROME_CSS}
832
832
  researchTicker.pause();
833
833
  researchQueue = null;
834
834
  checkedSources.clear();
835
- session = newSession();
835
+ session = await newSession();
836
836
  el("teachNote").textContent = "";
837
837
  el("ingestNote").textContent = "";
838
838
  el("researchNote").textContent = "";