@polycode-projects/the-mechanical-code-talker 4.1.0 → 4.1.2
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/README.md +31 -18
- package/bin/tmct.mjs +3 -0
- package/data/templates/responses.jsonl +3 -0
- package/package.json +2 -1
- package/src/adapters/memory/core.mjs +1358 -196
- package/src/adapters/memory/inspect.mjs +11 -0
- package/src/adapters/memory/shacl.mjs +38 -0
- package/src/adapters/p2p/webrtc-transport.mjs +28 -5
- package/src/domain/ask-vocab.mjs +39 -0
- package/src/domain/ask.mjs +183 -34
- package/src/domain/grammar/assert.mjs +8 -2
- package/src/domain/hanoi-board.mjs +232 -0
- package/src/domain/ingest-facts.mjs +120 -0
- package/src/domain/interpret/normalize.mjs +49 -0
- package/src/domain/memory/compaction.mjs +284 -0
- package/src/domain/memory/resolution.mjs +171 -0
- package/src/domain/memory/trust.mjs +175 -5
- package/src/domain/memory-facts.mjs +139 -0
- package/src/domain/p2p/facts.mjs +21 -0
- package/src/domain/p2p/peer-id.mjs +15 -0
- package/src/domain/p2p/provenance-relabel.mjs +13 -2
- package/src/domain/p2p/sync-filter.mjs +5 -1
- package/src/domain/p2p/wire.mjs +7 -4
- package/src/domain/scene-compose.mjs +2 -2
- package/src/domain/sprite-facts.mjs +0 -0
- package/src/domain/sprite-request.mjs +1 -1
- package/src/services/adventure-viz.mjs +43 -39
- package/src/services/adventure.mjs +70 -44
- package/src/services/chat-page-viz.mjs +403 -332
- package/src/services/chat.mjs +274 -156
- package/src/services/code-explorer-viz.mjs +142 -55
- package/src/services/index.mjs +1 -1
- package/src/services/ingest-viz.mjs +153 -28
- package/src/services/ledger-viz.mjs +47 -47
- package/src/services/memory-panel-viz.mjs +8 -3
- package/src/services/mud-turn.mjs +11 -8
- package/src/services/mud-viz.mjs +474 -234
- package/src/services/p2p-room.mjs +110 -23
- package/src/services/plan-viz.mjs +72 -13
- package/src/services/research-viz.mjs +35 -24
- package/src/services/share-overlay-viz.mjs +623 -0
- package/src/services/spider-fly-viz.mjs +24 -24
- package/src/services/sprite-catalog-viz.mjs +307 -82
- package/src/surfaces/web/adventure-browser-entry.mjs +46 -25
- package/src/surfaces/web/chat-browser-entry.mjs +55 -9
- package/src/surfaces/web/code-explorer-browser-entry.mjs +30 -16
- package/src/surfaces/web/engine-surface.mjs +82 -0
- package/src/surfaces/web/ingest-browser-entry.mjs +81 -11
- package/src/surfaces/web/ledger-browser-entry.mjs +47 -14
- package/src/surfaces/web/memory-ask-browser-entry.mjs +55 -13
- package/src/surfaces/web/memory-ask-browser.bundle.js +149 -116
- package/src/surfaces/web/mud-browser-entry.mjs +77 -25
- package/src/surfaces/web/p2p-browser-entry.mjs +1 -1
- package/src/surfaces/web/plan-browser-entry.mjs +49 -11
- package/src/surfaces/web/research-browser-entry.mjs +33 -24
- package/src/surfaces/web/spider-fly-browser-entry.mjs +32 -13
- package/src/surfaces/web/sprites-browser-entry.mjs +51 -11
- package/src/surfaces/web/tmct-surface.mjs +159 -0
- package/src/surfaces/web/turn-session.mjs +16 -5
- package/src/tools/server.mjs +13 -6
|
@@ -145,13 +145,16 @@ const trustTierFor = (trust) => (trust >= 0.85 ? 3 : trust >= 0.5 ? 2 : 1);
|
|
|
145
145
|
* build-demo-site.mjs consumes directly). Returns
|
|
146
146
|
* { rows, terms, edges, focus, contradictions, worthALook, payload, meta }. */
|
|
147
147
|
export function computeLedgerDataFromPayload(payload, { focus, term, rowLimit = LEDGER_ROW_LIMIT_DEFAULT } = {}) {
|
|
148
|
-
const individuals = payload?.individuals || [];
|
|
149
|
-
const indById = new Map(individuals.map((i) => [i?.id, i]));
|
|
150
148
|
const factRows = readFactRows(payload);
|
|
151
149
|
|
|
152
150
|
const rows = factRows.map((r) => {
|
|
153
|
-
|
|
154
|
-
|
|
151
|
+
// A row is now a GROUP of one-or-more per-source assertion records
|
|
152
|
+
// (PLAN_FACT.md's re-key), so there is no single individual to join
|
|
153
|
+
// against by r.id any more — r.id is the group's own id, distinct from
|
|
154
|
+
// every member record's id. The newest assertion's own createdAt is what
|
|
155
|
+
// "when was this learned" means for a row the ledger already sorts
|
|
156
|
+
// newest-first.
|
|
157
|
+
const createdAt = (r.assertions || []).reduce((newest, a) => (a.createdAt > newest ? a.createdAt : newest), "");
|
|
155
158
|
return {
|
|
156
159
|
id: r.id, s: r.subject, p: r.predicate, o: r.object,
|
|
157
160
|
phrase: phraseFor(r.predicate),
|
|
@@ -549,7 +552,7 @@ function sparkCaptionHtml(stats) {
|
|
|
549
552
|
* self-contained document with no external requests. Only
|
|
550
553
|
* scripts/build-demo-site.mjs, which builds the sibling bundle itself
|
|
551
554
|
* first, passes `true`. The dock's own runtime code below ALSO gates on
|
|
552
|
-
* `typeof
|
|
555
|
+
* `typeof tmct !== "undefined"` regardless — the two checks answer
|
|
553
556
|
* different questions (did this render even offer the reference; did the
|
|
554
557
|
* browser actually manage to load it), and both must hold for the live
|
|
555
558
|
* path to run. */
|
|
@@ -564,7 +567,7 @@ export function renderLedgerHtml({ rows, terms, edges, focus, contradictions, wo
|
|
|
564
567
|
// really in this payload, otherwise a real term from this graph. Left as
|
|
565
568
|
// the query-only wording even when the live bundle is offered — the dock's
|
|
566
569
|
// own script swaps it for a teach-aware placeholder the moment it confirms
|
|
567
|
-
//
|
|
570
|
+
// the live engine actually loaded (never claimed ahead of that confirmation).
|
|
568
571
|
// The example term skips anything under 3 characters — the SAME floor the
|
|
569
572
|
// dock's own miss-tips apply below — so a graph whose highest-degree term
|
|
570
573
|
// is a short stopword-shaped fragment (init:large corpora carry plenty:
|
|
@@ -575,7 +578,7 @@ export function renderLedgerHtml({ rows, terms, edges, focus, contradictions, wo
|
|
|
575
578
|
? "who is the grandfather of ishmael"
|
|
576
579
|
: (exampleTerm ? `ask the graph… e.g. what is ${exampleTerm.term}` : "ask the graph…");
|
|
577
580
|
// The paste-and-drop ingest panel plus the JSONL export ride the LIVE
|
|
578
|
-
// engine (window.
|
|
581
|
+
// engine (window.tmct): both teach into and read the dock's own
|
|
579
582
|
// session store, so they appear only where that bundle is offered — the
|
|
580
583
|
// demo site, never the CLI's self-contained tmct viz page.
|
|
581
584
|
const ingestHtml = ledgerBundleAvailable
|
|
@@ -888,14 +891,13 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
888
891
|
// exists it points at that session's store, which teach/research grow in
|
|
889
892
|
// place, so a digest of a just-taught term reads the fresh facts.
|
|
890
893
|
let getLivePayload = () => PAYLOAD;
|
|
891
|
-
//
|
|
892
|
-
// ledger's
|
|
893
|
-
//
|
|
894
|
-
//
|
|
894
|
+
// The browser digest helper, from whichever engine this page loaded — the
|
|
895
|
+
// demo ledger's live one or the committed query-only one the CLI viz page
|
|
896
|
+
// inlines. Both publish it in the same place now, so there is one lookup
|
|
897
|
+
// rather than two. Null before either loads, and the focus card then keeps
|
|
898
|
+
// whatever server-computed digest it shipped.
|
|
895
899
|
const digestHelper = () =>
|
|
896
|
-
(typeof
|
|
897
|
-
|| (typeof tmctMemoryAsk !== "undefined" && tmctMemoryAsk && tmctMemoryAsk.digestTermFromPayloadBrowser)
|
|
898
|
-
|| null;
|
|
900
|
+
(typeof tmct !== "undefined" && tmct.page && tmct.page.digestTermFromPayloadBrowser) || null;
|
|
899
901
|
// The digest for one term, computed live in the browser from the embedded
|
|
900
902
|
// structure table, or null when no structures were embedded, no engine has
|
|
901
903
|
// loaded, or the term holds nothing to compose.
|
|
@@ -1138,9 +1140,9 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1138
1140
|
}
|
|
1139
1141
|
|
|
1140
1142
|
// ---- the chat dock: LIVE teach+ask over the sibling ledger-browser.bundle.js
|
|
1141
|
-
// (window.
|
|
1142
|
-
// loaded; falls back to the read-only
|
|
1143
|
-
//
|
|
1143
|
+
// (window.tmct) when the demo build offered it AND it actually
|
|
1144
|
+
// loaded; falls back to the read-only engine below, which publishes the
|
|
1145
|
+
// same surface as a stand-in and says so through tmct.fallback.
|
|
1144
1146
|
// bin/tmct.mjs's own \`tmct viz\` output never offers the live bundle
|
|
1145
1147
|
// (renderLedgerHtml's own ledgerBundleAvailable defaults false there), so
|
|
1146
1148
|
// this branch is simply never reachable on a CLI-generated page.
|
|
@@ -1148,7 +1150,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1148
1150
|
const createTicker = ${createTicker.toString()};
|
|
1149
1151
|
const prefersReducedMotion = ${prefersReducedMotion.toString()};
|
|
1150
1152
|
const chatForm = el("chatform");
|
|
1151
|
-
if (chatForm && typeof
|
|
1153
|
+
if (chatForm && typeof tmct !== "undefined" && !tmct.fallback) {
|
|
1152
1154
|
const log = el("chatlog");
|
|
1153
1155
|
const chatqEl = el("chatq");
|
|
1154
1156
|
chatqEl.placeholder = 'ask or teach the graph\\u2026 e.g. "blue is a peg"';
|
|
@@ -1183,7 +1185,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1183
1185
|
import("./vendor/wink.js"),
|
|
1184
1186
|
winkTimeout(WINK_LOAD_TIMEOUT_MS, "wink vendor asset load timed out"),
|
|
1185
1187
|
]);
|
|
1186
|
-
|
|
1188
|
+
tmct.page.registerWinkModel(() => ({ winkNLP: mod.winkNLP, model: mod.model }));
|
|
1187
1189
|
} catch (err) {
|
|
1188
1190
|
// eslint-disable-next-line no-console
|
|
1189
1191
|
console.warn("tmct ledger: the wink vendor asset failed to load, continuing without the lemma/POS tier", err);
|
|
@@ -1196,7 +1198,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1196
1198
|
async function ensureSession() {
|
|
1197
1199
|
if (session) return session;
|
|
1198
1200
|
await tryLoadWink();
|
|
1199
|
-
session = await
|
|
1201
|
+
session = await tmct.open({ seedPayload: PAYLOAD });
|
|
1200
1202
|
// From here the live store is the source of truth for the digest, so a
|
|
1201
1203
|
// digest of a term taught this session reads its fresh facts — the store
|
|
1202
1204
|
// grows in place, so this one closure stays current.
|
|
@@ -1214,15 +1216,15 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1214
1216
|
chatqEl.disabled = true;
|
|
1215
1217
|
withLock(async () => {
|
|
1216
1218
|
try {
|
|
1217
|
-
const
|
|
1218
|
-
const result = await
|
|
1219
|
+
const live = await ensureSession();
|
|
1220
|
+
const result = await tmct.turn(q);
|
|
1219
1221
|
const record = result.record;
|
|
1220
1222
|
const taught = !!record && record.miss === false && (record.via === "assert" || record.via === "retract");
|
|
1221
1223
|
const body = esc(result.answer).replace(/\\n/g, "<br>");
|
|
1222
1224
|
pending.className = "a" + (taught ? " taught" : (record && record.miss ? " miss" : ""));
|
|
1223
1225
|
pending.innerHTML = taught ? '<span class="tag">taught</span>' + body : body;
|
|
1224
1226
|
if (taught) {
|
|
1225
|
-
const fresh =
|
|
1227
|
+
const fresh = tmct.page.computeLedgerDataFromPayload(live.memoryDir.payload, {});
|
|
1226
1228
|
applyLedgerData(fresh);
|
|
1227
1229
|
} else if (!(record && record.miss)) {
|
|
1228
1230
|
// Only a genuine answer (never a miss) tries to resolve a
|
|
@@ -1231,7 +1233,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1231
1233
|
// ordinary English word (e.g. "run", if the graph happens to
|
|
1232
1234
|
// hold it), and resolveAnsweredTerm has no way to tell that
|
|
1233
1235
|
// apart from the term genuinely being discussed.
|
|
1234
|
-
const hit = resolveAnsweredTerm(result.answer, q, LEDGER.terms,
|
|
1236
|
+
const hit = resolveAnsweredTerm(result.answer, q, LEDGER.terms, tmct.page.normFactTerm);
|
|
1235
1237
|
if (hit) refocusWithLabel(hit, q);
|
|
1236
1238
|
}
|
|
1237
1239
|
} catch {
|
|
@@ -1246,7 +1248,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1246
1248
|
|
|
1247
1249
|
// ---- ingest + export: bring your own text, take the graph away ---------
|
|
1248
1250
|
// The ingest panel runs pasted/dropped/browsed text through the SAME dock
|
|
1249
|
-
// session, one sentence at a time (
|
|
1251
|
+
// session, one sentence at a time (tmct.page.splitSentences, then
|
|
1250
1252
|
// s.turn), keeping only the sentences the recognizer grounds — then
|
|
1251
1253
|
// re-derives the whole ledger so the new facts can be examined in place.
|
|
1252
1254
|
// Export serializes that same session store to canonical JSONL.
|
|
@@ -1282,13 +1284,13 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1282
1284
|
withLock(async () => {
|
|
1283
1285
|
try {
|
|
1284
1286
|
const s = await ensureSession();
|
|
1285
|
-
const sentences =
|
|
1287
|
+
const sentences = tmct.page.splitSentences(text);
|
|
1286
1288
|
let grounded = 0;
|
|
1287
1289
|
for (const sentence of sentences) {
|
|
1288
|
-
const r = await
|
|
1290
|
+
const r = await tmct.turn(sentence);
|
|
1289
1291
|
if (r.record && r.record.miss === false && r.record.via === "assert") grounded += 1;
|
|
1290
1292
|
}
|
|
1291
|
-
const fresh =
|
|
1293
|
+
const fresh = tmct.page.computeLedgerDataFromPayload(s.memoryDir.payload, {});
|
|
1292
1294
|
applyLedgerData(fresh);
|
|
1293
1295
|
const skipped = sentences.length - grounded;
|
|
1294
1296
|
statusEl.textContent = countLabel(sentences.length, "sentence")
|
|
@@ -1308,7 +1310,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1308
1310
|
withLock(async () => {
|
|
1309
1311
|
try {
|
|
1310
1312
|
const s = await ensureSession();
|
|
1311
|
-
const jsonl = await
|
|
1313
|
+
const jsonl = await tmct.page.exportFactsJsonl(s.memoryDir);
|
|
1312
1314
|
const blob = new Blob([jsonl], { type: "application/x-ndjson" });
|
|
1313
1315
|
const url = URL.createObjectURL(blob);
|
|
1314
1316
|
const link = document.createElement("a");
|
|
@@ -1354,8 +1356,8 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1354
1356
|
const pending = addLine("a pending", "researching\\u2026");
|
|
1355
1357
|
return withLock(async () => {
|
|
1356
1358
|
try {
|
|
1357
|
-
const
|
|
1358
|
-
const result = await
|
|
1359
|
+
const live = await ensureSession();
|
|
1360
|
+
const result = await tmct.turn(q);
|
|
1359
1361
|
const missed = !result.record || Boolean(result.record.miss);
|
|
1360
1362
|
pending.className = "a" + (missed ? " miss" : "");
|
|
1361
1363
|
pending.innerHTML = esc(result.answer).replace(/\\n/g, "<br>");
|
|
@@ -1363,7 +1365,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1363
1365
|
researchQueue = result.research;
|
|
1364
1366
|
renderResearchControls();
|
|
1365
1367
|
if (!missed) {
|
|
1366
|
-
const fresh =
|
|
1368
|
+
const fresh = tmct.page.computeLedgerDataFromPayload(live.memoryDir.payload, {});
|
|
1367
1369
|
applyLedgerData(fresh);
|
|
1368
1370
|
}
|
|
1369
1371
|
}
|
|
@@ -1406,9 +1408,11 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1406
1408
|
else researchTicker.play();
|
|
1407
1409
|
});
|
|
1408
1410
|
}
|
|
1409
|
-
} else if (chatForm && typeof
|
|
1410
|
-
|
|
1411
|
-
|
|
1411
|
+
} else if (chatForm && typeof tmct !== "undefined") {
|
|
1412
|
+
// The query-only dock. One session over the page's own embedded payload,
|
|
1413
|
+
// and every line goes to tmct.ask — the cascade that used to be chained
|
|
1414
|
+
// here by hand now lives behind that one call.
|
|
1415
|
+
const opened = tmct.open({ payload: PAYLOAD });
|
|
1412
1416
|
const log = el("chatlog");
|
|
1413
1417
|
const addLine = (cls, html) => {
|
|
1414
1418
|
const d = document.createElement("div");
|
|
@@ -1423,20 +1427,16 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1423
1427
|
input.value = "";
|
|
1424
1428
|
addLine("u", esc(q));
|
|
1425
1429
|
(async () => {
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
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>"));
|
|
1430
|
+
await opened;
|
|
1431
|
+
let answer = null;
|
|
1432
|
+
let data = null;
|
|
1433
|
+
try { ({ answer, data } = await tmct.ask(q)); } catch { answer = null; data = null; }
|
|
1434
|
+
if (answer) {
|
|
1435
|
+
addLine("a", esc(answer).replace(/\\n/g, "<br>"));
|
|
1436
1436
|
// Same formatting contract as chat's withGoalLine: capitalized,
|
|
1437
1437
|
// full-stop-terminated, rendered only when the engine deduced one.
|
|
1438
|
-
if (
|
|
1439
|
-
const hit = resolveAnsweredTerm(
|
|
1438
|
+
if (data && data.goal) addLine("a goal", "Goal (inferred): " + esc(data.goal.charAt(0).toUpperCase() + data.goal.slice(1)) + ".");
|
|
1439
|
+
const hit = resolveAnsweredTerm(answer, q, LEDGER.terms, tmct.page.normFactTerm);
|
|
1440
1440
|
if (hit) refocusWithLabel(hit, q);
|
|
1441
1441
|
} else {
|
|
1442
1442
|
const tips = LEDGER.terms.filter((t) => t.term.length >= 3).slice(0, 2)
|
|
@@ -75,12 +75,17 @@ export async function clearSiteAssetCaches() {
|
|
|
75
75
|
|
|
76
76
|
/** Fetch `url` reading the body as a stream, reporting (loadedBytes,
|
|
77
77
|
* totalBytes) after every chunk — total is 0 when the response carries no
|
|
78
|
-
* Content-Length
|
|
79
|
-
*
|
|
78
|
+
* Content-Length, OR when Content-Encoding is set: the stream this function
|
|
79
|
+
* reads is always the DECOMPRESSED body (the browser decompresses before
|
|
80
|
+
* handing it to a reader), but Content-Length names the compressed wire
|
|
81
|
+
* size — reporting that as "total" against decompressed "loaded" bytes
|
|
82
|
+
* makes progress read as over 100% almost immediately. Resolves to a Blob
|
|
83
|
+
* of the whole body. Falls back to a single-shot blob() read when the
|
|
84
|
+
* runtime has no streaming body reader. */
|
|
80
85
|
export async function fetchWithProgress(url, onProgress) {
|
|
81
86
|
const res = await fetch(url);
|
|
82
87
|
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
83
|
-
const total = Number(res.headers.get("content-length")) || 0;
|
|
88
|
+
const total = res.headers.get("content-encoding") ? 0 : Number(res.headers.get("content-length")) || 0;
|
|
84
89
|
if (!res.body || !res.body.getReader) {
|
|
85
90
|
const blob = await res.blob();
|
|
86
91
|
onProgress(blob.size, total || blob.size);
|
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
foldWorldState, worldActionRows, runWorldCommand, recordTold, recordExamined,
|
|
56
56
|
recordMassDrain, personKnowledgeLines, objectClassChain, diggableDirections,
|
|
57
57
|
isOutOfPlay, outOfPlayReasonOf, outOfPlayPhrase, massDrainPerTurnOf,
|
|
58
|
-
parseSnapshotSubject,
|
|
58
|
+
parseSnapshotSubject, characterTestimonyTag,
|
|
59
59
|
} from "./adventure.mjs";
|
|
60
60
|
|
|
61
61
|
const FOOD_CLASS = "food";
|
|
@@ -267,6 +267,9 @@ export async function runMudTurn(character, {
|
|
|
267
267
|
const opened = await readWorld(memoryDir);
|
|
268
268
|
const room = opened.state.placements.get(character)?.object ?? null;
|
|
269
269
|
const turn = k ?? opened.state.turnCount + 1;
|
|
270
|
+
// The run this turn belongs to, stamped onto the testimony it writes so a
|
|
271
|
+
// recast's first turns outrank whatever the replaced run had to say.
|
|
272
|
+
const epoch = opened.state.epoch;
|
|
270
273
|
const actions = [];
|
|
271
274
|
const notes = [];
|
|
272
275
|
const learnedBefore = knownTopics(opened.rows, opened.state, character);
|
|
@@ -300,7 +303,7 @@ export async function runMudTurn(character, {
|
|
|
300
303
|
notes.push(`MUD — ${step}: ${reason}`);
|
|
301
304
|
};
|
|
302
305
|
|
|
303
|
-
await investigateRoom({ character, turn, room, memoryDir, cache, actions, notes, runCommand, recordSkip });
|
|
306
|
+
await investigateRoom({ character, turn, epoch, room, memoryDir, cache, actions, notes, runCommand, recordSkip });
|
|
304
307
|
|
|
305
308
|
const walked = await readWorld(memoryDir);
|
|
306
309
|
const walkedRoom = walked.state.placements.get(character)?.object ?? room;
|
|
@@ -397,7 +400,7 @@ async function exploreUnvisited({ character, memoryDir, runCommand, recordSkip }
|
|
|
397
400
|
* same empty greeting every turn for the rest of the run. The talk and the
|
|
398
401
|
* examine write testimony, which never folds into the playable state; only
|
|
399
402
|
* the manipulation touches the world. */
|
|
400
|
-
async function investigateRoom({ character, turn, room, memoryDir, cache, recordSkip, runCommand, actions, notes }) {
|
|
403
|
+
async function investigateRoom({ character, turn, epoch = 0, room, memoryDir, cache, recordSkip, runCommand, actions, notes }) {
|
|
401
404
|
const { rows, state } = await readWorld(memoryDir);
|
|
402
405
|
const roomMates = castIn(state, room, character);
|
|
403
406
|
const alreadyKnown = knownTopics(rows, state, character);
|
|
@@ -420,7 +423,7 @@ async function investigateRoom({ character, turn, room, memoryDir, cache, record
|
|
|
420
423
|
// drops out of worthSpeakingTo, and drops back in the moment either side
|
|
421
424
|
// learns a food the other has not heard of.
|
|
422
425
|
if (!alreadyKnown.has(teller)) {
|
|
423
|
-
await recordExamined(memoryDir, { observer: character, thing: teller, k: turn, cache });
|
|
426
|
+
await recordExamined(memoryDir, { observer: character, thing: teller, k: turn, epoch, cache });
|
|
424
427
|
alreadyKnown.add(teller);
|
|
425
428
|
}
|
|
426
429
|
if (!told) {
|
|
@@ -433,13 +436,13 @@ async function investigateRoom({ character, turn, room, memoryDir, cache, record
|
|
|
433
436
|
});
|
|
434
437
|
notes.push(`MUD — talk: ${character} greeted ${teller}; ${teller} knows of no food to share`);
|
|
435
438
|
} else {
|
|
436
|
-
await recordTold(memoryDir, { asker: character, teller, thing: told, k: turn, cache });
|
|
439
|
+
await recordTold(memoryDir, { asker: character, teller, thing: told, k: turn, epoch, cache });
|
|
437
440
|
alreadyKnown.add(told);
|
|
438
441
|
actions.push({
|
|
439
442
|
step: "investigate", kind: "ask", teller, thing: told, miss: false,
|
|
440
443
|
text: `the ${character} asks the ${teller} about food, and hears about the ${told}.`,
|
|
441
444
|
});
|
|
442
|
-
notes.push(`MUD — ask: ${teller} told ${character} about ${told}; written as
|
|
445
|
+
notes.push(`MUD — ask: ${teller} told ${character} about ${told}; written as ${characterTestimonyTag(teller, turn, { epoch })}`);
|
|
443
446
|
}
|
|
444
447
|
}
|
|
445
448
|
|
|
@@ -452,13 +455,13 @@ async function investigateRoom({ character, turn, room, memoryDir, cache, record
|
|
|
452
455
|
if (!examined) {
|
|
453
456
|
recordSkip("investigate", "nothing unexamined stands here", "");
|
|
454
457
|
} else {
|
|
455
|
-
await recordExamined(memoryDir, { observer: character, thing: examined, k: turn, cache });
|
|
458
|
+
await recordExamined(memoryDir, { observer: character, thing: examined, k: turn, epoch, cache });
|
|
456
459
|
alreadyKnown.add(examined);
|
|
457
460
|
actions.push({
|
|
458
461
|
step: "investigate", kind: "examine", thing: examined, miss: false,
|
|
459
462
|
text: `the ${character} examines the ${examined}.`,
|
|
460
463
|
});
|
|
461
|
-
notes.push(`MUD — examine: ${character} looked at ${examined}; written as
|
|
464
|
+
notes.push(`MUD — examine: ${character} looked at ${examined}; written as ${characterTestimonyTag(character, turn, { epoch })}`);
|
|
462
465
|
}
|
|
463
466
|
|
|
464
467
|
await manipulateSomething({ character, turn, room, memoryDir, runCommand, recordSkip });
|