@polycode-projects/the-mechanical-code-talker 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/corpus/sprites/src/sprite-facts.jsonl +375 -8
- package/package.json +1 -1
- package/src/adapters/memory/core.mjs +20 -0
- package/src/domain/ask-vocab.mjs +71 -0
- package/src/domain/ask.mjs +168 -0
- package/src/domain/game-config.mjs +11 -0
- package/src/domain/mud-facts.mjs +15 -0
- package/src/domain/router/drive.mjs +35 -9
- package/src/domain/router/registry.mjs +24 -4
- package/src/domain/router/resolver.mjs +102 -40
- package/src/domain/scene-compose.mjs +117 -0
- package/src/domain/spider-fly-world.mjs +36 -0
- package/src/domain/sprite-facts.mjs +0 -0
- package/src/domain/sprite-request.mjs +156 -0
- package/src/domain/sprite-templates.mjs +161 -14
- package/src/services/adventure-editor.mjs +8 -14
- package/src/services/adventure-viz.mjs +90 -121
- package/src/services/adventure.mjs +97 -35
- package/src/services/chat-page-viz.mjs +32 -16
- package/src/services/chat.mjs +101 -33
- package/src/services/code-explorer-viz.mjs +51 -49
- package/src/services/ingest-viz.mjs +15 -57
- package/src/services/ledger-viz.mjs +47 -27
- package/src/services/memory-panel-viz.mjs +38 -0
- package/src/services/mud-editor.mjs +10 -15
- package/src/services/mud-turn.mjs +6 -6
- package/src/services/mud-viz.mjs +87 -198
- package/src/services/p2p-room.mjs +90 -23
- package/src/services/plan-pddl.mjs +3 -1
- package/src/services/plan-viz.mjs +4 -3
- package/src/services/research-viz.mjs +10 -52
- package/src/services/spider-fly-turn.mjs +14 -22
- package/src/services/spider-fly-viz.mjs +79 -118
- package/src/services/spider-fly.mjs +69 -11
- package/src/services/sprite-catalog-viz.mjs +271 -221
- package/src/services/viz-boot.mjs +71 -0
- package/src/services/viz-room-graph.mjs +203 -0
- package/src/services/viz-theme.mjs +75 -1
- package/src/services/viz-ticker.mjs +22 -0
- package/src/surfaces/web/adventure-browser-entry.mjs +49 -33
- package/src/surfaces/web/chat-browser-entry.mjs +30 -105
- package/src/surfaces/web/code-explorer-browser-entry.mjs +168 -24
- package/src/surfaces/web/ingest-browser-entry.mjs +3 -13
- package/src/surfaces/web/ledger-browser-entry.mjs +7 -47
- package/src/surfaces/web/memory-ask-browser.bundle.js +128 -125
- package/src/surfaces/web/memory-stats.mjs +11 -0
- package/src/surfaces/web/mud-browser-entry.mjs +28 -28
- package/src/surfaces/web/plan-browser-entry.mjs +22 -40
- package/src/surfaces/web/research-browser-entry.mjs +26 -41
- package/src/surfaces/web/spider-fly-browser-entry.mjs +45 -28
- package/src/surfaces/web/sprites-browser-entry.mjs +14 -27
- package/src/surfaces/web/turn-session.mjs +120 -0
- package/src/tools/definitions.mjs +30 -0
- package/src/tools/handlers/index.mjs +6 -3
- package/src/tools/handlers/kit.mjs +19 -2
- package/src/tools/handlers/tmct-ask.mjs +11 -6
- package/src/tools/handlers/tmct-ingest.mjs +5 -1
- package/src/tools/handlers/tmct-related.mjs +4 -4
- package/src/tools/handlers/tmct-sprite.mjs +147 -0
- package/src/tools/memory-fallthrough.mjs +9 -2
- package/src/tools/server.mjs +25 -1
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
// a title bar (graph source, Open graph…/Open repo…), an explorer sidebar
|
|
3
3
|
// reading each import/call/contains edge back as a plain sentence, a chat
|
|
4
4
|
// centre over the same graph with a rail of suggested questions, and a status
|
|
5
|
-
// bar carrying the graph's own counts.
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
5
|
+
// bar carrying the graph's own counts. Clicking a term asks the engine what
|
|
6
|
+
// relates to it (askRelatedFacts, over tmct_ask) and renders the answer, so the
|
|
7
|
+
// sidebar and the chat put the same question to the same place.
|
|
8
|
+
//
|
|
9
|
+
// The chat session seeds BOTH the loaded code graph and chat.html's
|
|
10
|
+
// general-knowledge bands (./chat-seed.json, fetched lazily at runtime), so one
|
|
11
|
+
// conversation answers "what is a queue" and "what imports src/core/model.mjs"
|
|
12
|
+
// alike — and degrades to graph-only when the seed asset is unavailable.
|
|
10
13
|
//
|
|
11
14
|
// The derivations are pure so the shell, the packaging scripts, and the unit
|
|
12
15
|
// tests all share one code path; renderCodeExplorerHtml builds one
|
|
@@ -18,24 +21,14 @@
|
|
|
18
21
|
|
|
19
22
|
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml, embedJson, embedScriptText } from "./viz-theme.mjs";
|
|
20
23
|
import { generateCodeHints } from "../domain/code-explorer-hints.mjs";
|
|
24
|
+
import { phraseForRelation } from "../domain/ask-vocab.mjs";
|
|
25
|
+
import { fetchWithProgress } from "./memory-panel-viz.mjs";
|
|
21
26
|
|
|
22
|
-
// Third-person verb for each stored relation kind, symbol grain folded onto
|
|
23
|
-
// coarse sibling
|
|
24
|
-
//
|
|
25
|
-
const EDGE_PHRASE = new Map([
|
|
26
|
-
["imports", "imports"],
|
|
27
|
-
["calls", "calls"], ["callsSymbol", "calls"],
|
|
28
|
-
["contains", "contains"],
|
|
29
|
-
["defines", "defines"],
|
|
30
|
-
["inherits", "inherits from"],
|
|
31
|
-
["tests", "tests"],
|
|
32
|
-
["touches", "touches"], ["touchesSymbol", "touches"],
|
|
33
|
-
["cochange", "co-changes with"],
|
|
34
|
-
["reexports", "re-exports"],
|
|
35
|
-
]);
|
|
36
|
-
|
|
27
|
+
// Third-person verb for each stored relation kind, symbol grain folded onto
|
|
28
|
+
// its coarse sibling — derived from ask-vocab.mjs's own RELATIONS table
|
|
29
|
+
// rather than a second hand-curated relation-verb table.
|
|
37
30
|
export function edgePhrase(kind) {
|
|
38
|
-
return
|
|
31
|
+
return phraseForRelation(kind);
|
|
39
32
|
}
|
|
40
33
|
|
|
41
34
|
// Both packagers (build-electron-app.mjs, build-demo-site.mjs) place
|
|
@@ -133,11 +126,7 @@ const CLIENT_JS = String.raw`
|
|
|
133
126
|
};
|
|
134
127
|
var session = null;
|
|
135
128
|
|
|
136
|
-
|
|
137
|
-
return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) {
|
|
138
|
-
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
|
|
139
|
-
});
|
|
140
|
-
}
|
|
129
|
+
var esc = ${escapeHtml.toString()};
|
|
141
130
|
|
|
142
131
|
function renderStats(data) {
|
|
143
132
|
var s = data.ledger.stats;
|
|
@@ -155,11 +144,37 @@ const CLIENT_JS = String.raw`
|
|
|
155
144
|
els.factTotal.textContent = (edges + seedState.facts).toLocaleString();
|
|
156
145
|
}
|
|
157
146
|
|
|
158
|
-
|
|
147
|
+
// "What relates to the focus", put to the engine. askRelatedFacts asks
|
|
148
|
+
// tmct_ask one question per relation kind the loaded graph carries, in both
|
|
149
|
+
// directions, and hands back the answers' own typed rows — the same ask()
|
|
150
|
+
// this page's chat turns reach, so the panel and the conversation answer one
|
|
151
|
+
// question one way. Null on the static page, which has no engine to ask.
|
|
152
|
+
function askRelated(focus) {
|
|
153
|
+
if (!api || !api.askRelatedFacts || !focus) return null;
|
|
154
|
+
try {
|
|
155
|
+
return api.askRelatedFacts(DATA.payload, focus);
|
|
156
|
+
} catch (e) {
|
|
157
|
+
console.warn("tmct code explorer: the related-facts ask failed, splitting the row list instead", e);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function rowKey(r) { return JSON.stringify([r.s, r.kind, r.o]); }
|
|
163
|
+
|
|
164
|
+
function renderFocusRows(data, related) {
|
|
159
165
|
var focus = data.focus;
|
|
160
166
|
var rows = data.ledger.rows;
|
|
161
|
-
|
|
162
|
-
|
|
167
|
+
// The engine's answer whenever it grounded one. The row list's own split is
|
|
168
|
+
// what is left when there is no engine (the static page) or when every
|
|
169
|
+
// question came back parsed as something else — an identifier that is
|
|
170
|
+
// itself a relation verb reads as a question about the verb.
|
|
171
|
+
var grounded = Boolean(related && related.grounded);
|
|
172
|
+
var near = grounded ? related.rows : rows.filter(function (r) { return r.s === focus || r.o === focus; });
|
|
173
|
+
var nearKeys = {};
|
|
174
|
+
near.forEach(function (r) { nearKeys[rowKey(r)] = true; });
|
|
175
|
+
// Whatever the neighbourhood did not already name, in degree order: a bulk
|
|
176
|
+
// view of the rest of the graph, which is a fold and not a question.
|
|
177
|
+
var rest = rows.filter(function (r) { return !nearKeys[rowKey(r)]; });
|
|
163
178
|
var ordered = near.concat(rest);
|
|
164
179
|
els.ledger.innerHTML = ordered.map(function (r) {
|
|
165
180
|
var hot = (r.s === focus || r.o === focus) ? " row-focus" : "";
|
|
@@ -168,7 +183,9 @@ const CLIENT_JS = String.raw`
|
|
|
168
183
|
+ '<span class="verb">' + esc(r.phrase) + '</span> '
|
|
169
184
|
+ '<button class="term" data-term="' + esc(r.o) + '">' + esc(r.o) + '</button>'
|
|
170
185
|
+ '</li>';
|
|
171
|
-
}).join("") ||
|
|
186
|
+
}).join("") || (grounded
|
|
187
|
+
? '<li class="row muted">nothing in this graph relates to ' + esc(focus) + '.</li>'
|
|
188
|
+
: '<li class="row muted">no edges in this graph.</li>');
|
|
172
189
|
els.focus.textContent = focus || "—";
|
|
173
190
|
}
|
|
174
191
|
|
|
@@ -188,7 +205,7 @@ const CLIENT_JS = String.raw`
|
|
|
188
205
|
|
|
189
206
|
function mountView(data) {
|
|
190
207
|
renderStats(data);
|
|
191
|
-
renderFocusRows(data);
|
|
208
|
+
renderFocusRows(data, askRelated(data.focus));
|
|
192
209
|
renderHints(data);
|
|
193
210
|
}
|
|
194
211
|
|
|
@@ -233,23 +250,7 @@ const CLIENT_JS = String.raw`
|
|
|
233
250
|
function seedNote(text) { if (els.seedStatus) els.seedStatus.textContent = text; }
|
|
234
251
|
function mbText(n) { return (n / 1048576).toFixed(1); }
|
|
235
252
|
|
|
236
|
-
|
|
237
|
-
var res = await fetch(url);
|
|
238
|
-
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
239
|
-
var total = Number(res.headers.get("content-length")) || 0;
|
|
240
|
-
if (!res.body || !res.body.getReader) return res.text();
|
|
241
|
-
var reader = res.body.getReader();
|
|
242
|
-
var chunks = [];
|
|
243
|
-
var loaded = 0;
|
|
244
|
-
for (;;) {
|
|
245
|
-
var step = await reader.read();
|
|
246
|
-
if (step.done) break;
|
|
247
|
-
chunks.push(step.value);
|
|
248
|
-
loaded += step.value.byteLength;
|
|
249
|
-
onProgress(loaded, total);
|
|
250
|
-
}
|
|
251
|
-
return new Blob(chunks).text();
|
|
252
|
-
}
|
|
253
|
+
var fetchWithProgress = ${fetchWithProgress.toString()};
|
|
253
254
|
|
|
254
255
|
async function loadSeed() {
|
|
255
256
|
try {
|
|
@@ -258,9 +259,10 @@ const CLIENT_JS = String.raw`
|
|
|
258
259
|
seedNote("loading general knowledge…");
|
|
259
260
|
text = await window.tmctDesktop.readSeed();
|
|
260
261
|
} else {
|
|
261
|
-
|
|
262
|
+
var seedBlob = await fetchWithProgress("./chat-seed.json" + SEED_QUERY, function (loaded, total) {
|
|
262
263
|
seedNote("loading general knowledge… " + mbText(loaded) + (total ? " of " + mbText(total) : "") + " MB");
|
|
263
264
|
});
|
|
265
|
+
text = await seedBlob.text();
|
|
264
266
|
}
|
|
265
267
|
if (text) {
|
|
266
268
|
seedState.payload = JSON.parse(text);
|
|
@@ -26,46 +26,20 @@
|
|
|
26
26
|
// input. scripts/build-demo-site.mjs calls it directly and writes the result
|
|
27
27
|
// to public/ingest.html, after ingest-browser.bundle.js already exists.
|
|
28
28
|
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml } from "./viz-theme.mjs";
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
bandLabelFor,
|
|
31
|
+
statsSummaryLine,
|
|
32
|
+
clearSiteAssetCaches,
|
|
33
|
+
fetchWithProgress,
|
|
34
|
+
renderStatsPanelInto,
|
|
35
|
+
loadProgressLine,
|
|
36
|
+
factTripleParts,
|
|
37
|
+
} from "./memory-panel-viz.mjs";
|
|
38
|
+
import { loadWinkVendor } from "./viz-boot.mjs";
|
|
39
|
+
import { cloneMemoryPayload } from "../adapters/memory/core.mjs";
|
|
30
40
|
|
|
31
41
|
const DEFAULT_TITLE = "the-mechanical-code-talker — ingest";
|
|
32
42
|
|
|
33
|
-
/** One grounded fact as a canonical triple line — subject, predicate, object,
|
|
34
|
-
* each in its own cell so the pane can align them. Pure and
|
|
35
|
-
* `.toString()`-splice safe (no outer refs): the inline script splices this
|
|
36
|
-
* in and calls it per row. */
|
|
37
|
-
export function factTripleParts(fact) {
|
|
38
|
-
return {
|
|
39
|
-
subject: String((fact && fact.subject) || ""),
|
|
40
|
-
predicate: String((fact && fact.predicate) || ""),
|
|
41
|
-
object: String((fact && fact.object) || ""),
|
|
42
|
-
provenance: String((fact && fact.provenance) || ""),
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** The boot statusline while the big assets stream in — the same aggregator
|
|
47
|
-
* chat-page-viz.mjs's own loadProgressLine is (kept as this page's own copy
|
|
48
|
-
* rather than a shared import — the two pages' boot lines diverge slightly
|
|
49
|
-
* and neither is a collaborator the other calls). `parts` is an array of
|
|
50
|
-
* { loaded, total } byte counts (total 0 when the response carried no
|
|
51
|
-
* Content-Length); with no usable total the line shows loaded bytes alone
|
|
52
|
-
* rather than inventing a denominator. Self-contained, `.toString()`-splice
|
|
53
|
-
* safe. */
|
|
54
|
-
export function loadProgressLine(parts) {
|
|
55
|
-
const mb = (n) => (n / 1048576).toFixed(1);
|
|
56
|
-
let loaded = 0;
|
|
57
|
-
let total = 0;
|
|
58
|
-
let totalKnown = true;
|
|
59
|
-
for (const p of parts || []) {
|
|
60
|
-
loaded += (p && p.loaded) || 0;
|
|
61
|
-
if (p && p.total > 0) total += p.total;
|
|
62
|
-
else totalKnown = false;
|
|
63
|
-
}
|
|
64
|
-
return totalKnown && total > 0
|
|
65
|
-
? "loading the engine… " + mb(loaded) + " MB / " + mb(total) + " MB"
|
|
66
|
-
: "loading the engine… " + mb(loaded) + " MB";
|
|
67
|
-
}
|
|
68
|
-
|
|
69
43
|
/** The self-contained ingest page. Pure — the same output for the same
|
|
70
44
|
* `title` every time; every piece of state (the session, each grounded fact)
|
|
71
45
|
* is computed live in the browser once the sibling ingest bundle loads. */
|
|
@@ -440,30 +414,14 @@ ${THEME_TOKENS_CSS}
|
|
|
440
414
|
console.warn("tmct ingest: chat-seed.json unavailable — starting unseeded", err);
|
|
441
415
|
}
|
|
442
416
|
}
|
|
443
|
-
|
|
444
|
-
if (!seedPayload) return null;
|
|
445
|
-
try { return structuredClone(seedPayload); } catch { return JSON.parse(JSON.stringify(seedPayload)); }
|
|
446
|
-
}
|
|
417
|
+
const cloneMemoryPayload = ${cloneMemoryPayload.toString()};
|
|
447
418
|
function newSession() {
|
|
448
|
-
return window.tmctIngest.createIngestSession({ seedPayload:
|
|
419
|
+
return window.tmctIngest.createIngestSession({ seedPayload: cloneMemoryPayload(seedPayload), vocabSeeded: Boolean(seedPayload) });
|
|
449
420
|
}
|
|
450
421
|
|
|
451
422
|
// ---- engine boot ---------------------------------------------------------
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
let settled = false;
|
|
455
|
-
const timeout = new Promise((_, reject) => setTimeout(() => { if (!settled) reject(new Error("wink load stalled")); }, WINK_LOAD_TIMEOUT_MS));
|
|
456
|
-
try {
|
|
457
|
-
const mod = await Promise.race([import("./vendor/wink.js"), timeout]);
|
|
458
|
-
settled = true;
|
|
459
|
-
window.tmctIngest.registerWinkModel(() => ({ winkNLP: mod.winkNLP, model: mod.model }));
|
|
460
|
-
return "loaded";
|
|
461
|
-
} catch (err) {
|
|
462
|
-
settled = true;
|
|
463
|
-
console.warn("tmct ingest: the wink vendor asset failed to load; the recognizer needs it to split and parse sentences", err);
|
|
464
|
-
return "unavailable";
|
|
465
|
-
}
|
|
466
|
-
}
|
|
423
|
+
const loadWinkVendor = ${loadWinkVendor.toString()};
|
|
424
|
+
const tryLoadWink = loadWinkVendor({ register: (factory) => window.tmctIngest.registerWinkModel(factory) });
|
|
467
425
|
|
|
468
426
|
// The deploy's own version, read off the service worker file the build
|
|
469
427
|
// already stamps — the only same-origin place the number exists at runtime
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
// doesn't do: it reads the checked-in chat-dock engine bundle.
|
|
16
16
|
|
|
17
17
|
import { loadMemory, readFactRows, findContradictions, normFactTerm } from "../adapters/memory/core.mjs";
|
|
18
|
-
import { THEME_TOKENS_CSS, MONO_STACK, escapeHtml, embedJson } from "./viz-theme.mjs";
|
|
18
|
+
import { THEME_TOKENS_CSS, MONO_STACK, escapeHtml, embedJson, countLabel, TOKENS } from "./viz-theme.mjs";
|
|
19
19
|
import { createTicker, prefersReducedMotion } from "./viz-ticker.mjs";
|
|
20
|
+
import { bfsLevels } from "../domain/planning.mjs";
|
|
21
|
+
import { pluralOf } from "../domain/inflect.mjs";
|
|
20
22
|
import { readFile } from "node:fs/promises";
|
|
21
23
|
import { fileURLToPath } from "node:url";
|
|
22
24
|
import { dirname, join } from "node:path";
|
|
@@ -33,23 +35,33 @@ const DASH_SANS_STACK = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
|
33
35
|
// themes — a deliberate departure from THEME_TOKENS_CSS's light/dark switch,
|
|
34
36
|
// which every other generated page still follows. --taught/--corpus/--entail/
|
|
35
37
|
// --alert stay the shared semantic provenance tokens; here they're pinned to
|
|
36
|
-
// their dark-theme-friendly values
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
|
|
38
|
+
// their dark-theme-friendly values, read straight off viz-theme.mjs's own
|
|
39
|
+
// exported TOKENS.dark table so this page's palette can never drift from the
|
|
40
|
+
// one every other generated page's dark mode uses. The tier alphas (.35/.65/1)
|
|
41
|
+
// match TOKENS's own tier scale; the four "soft" tints use higher alphas than
|
|
42
|
+
// THEME_TOKENS_CSS's shared soft tokens on purpose — this dashboard chrome
|
|
43
|
+
// wants a more visible tint against its always-dark background than the
|
|
44
|
+
// light/dark-switching pages need, so those four alphas stay page-specific.
|
|
45
|
+
function hexRgb(hex) {
|
|
46
|
+
const n = parseInt(hex.slice(1), 16);
|
|
47
|
+
return `${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}`;
|
|
48
|
+
}
|
|
49
|
+
const DASH_DARK_CHROME_CSS = (() => {
|
|
50
|
+
const t = TOKENS.dark;
|
|
51
|
+
const [taught, corpus, entail] = [hexRgb(t.taught), hexRgb(t.corpus), hexRgb(t.entail)];
|
|
52
|
+
return `
|
|
42
53
|
:root { color-scheme: dark; }
|
|
43
54
|
body {
|
|
44
|
-
--bg:
|
|
45
|
-
--taught:
|
|
46
|
-
--taught-t1: rgba(
|
|
47
|
-
--corpus-t1: rgba(
|
|
48
|
-
--entail-t1: rgba(
|
|
49
|
-
--taught-soft: rgba(
|
|
50
|
-
--entail-soft: rgba(
|
|
55
|
+
--bg: ${t.bg}; --ink: ${t.ink}; --muted: ${t.muted}; --line: ${t.line}; --card: ${t.card};
|
|
56
|
+
--taught: ${t.taught}; --corpus: ${t.corpus}; --entail: ${t.entail}; --alert: ${t.alert};
|
|
57
|
+
--taught-t1: rgba(${taught},.35); --taught-t2: rgba(${taught},.65); --taught-t3: rgba(${taught},1);
|
|
58
|
+
--corpus-t1: rgba(${corpus},.35); --corpus-t2: rgba(${corpus},.65); --corpus-t3: rgba(${corpus},1);
|
|
59
|
+
--entail-t1: rgba(${entail},.35); --entail-t2: rgba(${entail},.65); --entail-t3: rgba(${entail},1);
|
|
60
|
+
--taught-soft: rgba(${taught},.14); --corpus-soft: rgba(${corpus},.14);
|
|
61
|
+
--entail-soft: rgba(${entail},.16); --alert-soft: rgba(${hexRgb(t.alert)},.16);
|
|
51
62
|
}
|
|
52
63
|
`;
|
|
64
|
+
})();
|
|
53
65
|
|
|
54
66
|
/** Read the checked-in browser memory-ask-engine bundle
|
|
55
67
|
* (`src/surfaces/web/memory-ask-browser.bundle.js`) — the real memory-graph answer engine
|
|
@@ -453,7 +465,7 @@ function dashboardHtml(stats, { fresh = false } = {}) {
|
|
|
453
465
|
<div class="tile">
|
|
454
466
|
<span class="tile-label">facts.total</span>
|
|
455
467
|
<span class="tile-value">${total}</span>
|
|
456
|
-
<span class="tile-sub">${terms
|
|
468
|
+
<span class="tile-sub">${countLabel(terms, "term")} tracked</span>
|
|
457
469
|
</div>
|
|
458
470
|
<div class="tile tile-tier">
|
|
459
471
|
<span class="tile-label">facts.by-tier</span>
|
|
@@ -467,7 +479,7 @@ function dashboardHtml(stats, { fresh = false } = {}) {
|
|
|
467
479
|
<div class="tile${qualityCls}">
|
|
468
480
|
<span class="tile-label">data.quality</span>
|
|
469
481
|
<span class="tile-value">${s.contradictionCount || 0}</span>
|
|
470
|
-
<span class="tile-sub"
|
|
482
|
+
<span class="tile-sub">${s.contradictionCount === 1 ? "term" : pluralOf("term")} with more than one answer</span>
|
|
471
483
|
</div>
|
|
472
484
|
</div>
|
|
473
485
|
<div class="barpanels">
|
|
@@ -545,7 +557,7 @@ export function renderLedgerHtml({ rows, terms, edges, focus, contradictions, wo
|
|
|
545
557
|
const ledgerJson = embedJson({ rows: rows || [], terms: terms || [], edges: edges || [], focus: focus || null, contradictions: contradictions || [], worthALook: worthALook || null, meta: meta || { shown: 0, total: 0, truncated: false }, focusDigest: focusDigest || null, digestStructures: Array.isArray(digestStructures) ? digestStructures : [] });
|
|
546
558
|
const payloadJson = embedJson(payload || { individuals: [], objectProperties: [] });
|
|
547
559
|
const shown = meta?.shown ?? (rows || []).length;
|
|
548
|
-
const title = `tmct ledger — ${shown
|
|
560
|
+
const title = `tmct ledger — ${countLabel(shown, "fact")}${focus ? ` (focus: ${escapeHtml(focus)})` : ""}`;
|
|
549
561
|
const bundleStr = typeof memoryAskBundle === "string" ? memoryAskBundle : "";
|
|
550
562
|
const hasMemChat = bundleStr.length > 0;
|
|
551
563
|
// The placeholder is honest: the canonical exchange only when its terms are
|
|
@@ -833,6 +845,11 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
833
845
|
// escapeHtml/pct by their real names) resolve without a second copy.
|
|
834
846
|
const escapeHtml = esc;
|
|
835
847
|
const pct = ${pct.toString()};
|
|
848
|
+
// pluralOf underlies countLabel's own default-parameter lookup — spliced
|
|
849
|
+
// ahead of it so that lookup resolves the same way it does node-side.
|
|
850
|
+
const pluralOf = ${pluralOf.toString()};
|
|
851
|
+
const countLabel = ${countLabel.toString()};
|
|
852
|
+
const bfsLevels = ${bfsLevels.toString()};
|
|
836
853
|
const tierTileHtml = ${tierTileHtml.toString()};
|
|
837
854
|
const microbarsHtml = ${microbarsHtml.toString()};
|
|
838
855
|
const dashboardHtml = ${dashboardHtml.toString()};
|
|
@@ -965,7 +982,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
965
982
|
: "";
|
|
966
983
|
let html = '<div class="focuscard"><div class="term">' + esc(focus) + "</div>" +
|
|
967
984
|
'<div class="klass">' + (klass ? esc(klass.phrase + " " + klass.o) : "no class recorded") + "</div>" +
|
|
968
|
-
'<div class="stats">' + all.length + "
|
|
985
|
+
'<div class="stats">' + countLabel(all.length, "fact") + " · " + countLabel(srcs.size, "source") +
|
|
969
986
|
(dates.length ? " · first " + esc(dates[0].slice(0, 10)) + " · last " + esc(dates[dates.length - 1].slice(0, 10)) : "") + "</div>" + digestHtml + "</div>";
|
|
970
987
|
const bracketed = new Set();
|
|
971
988
|
for (const fam of FAMS) {
|
|
@@ -1028,13 +1045,16 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1028
1045
|
if (!focus) return;
|
|
1029
1046
|
const color = { taught: cssVar("--taught"), corpus: cssVar("--corpus"), entailed: cssVar("--entail"), entail: cssVar("--entail") };
|
|
1030
1047
|
const lineC = cssVar("--line"), inkC = cssVar("--ink");
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1048
|
+
const neighborsOf = (term) => {
|
|
1049
|
+
const out = [];
|
|
1050
|
+
for (const e of LEDGER.edges) {
|
|
1051
|
+
if (e.s === term) out.push(e.o);
|
|
1052
|
+
else if (e.o === term) out.push(e.s);
|
|
1053
|
+
}
|
|
1054
|
+
return out;
|
|
1055
|
+
};
|
|
1056
|
+
const [hop1Level, hop2Level] = bfsLevels(focus, neighborsOf, { maxDepth: 2 });
|
|
1057
|
+
const hop1 = new Set(hop1Level || []), hop2 = new Set(hop2Level || []);
|
|
1038
1058
|
const cx = w / 2, cy = h / 2, r1 = 42, r2 = 68;
|
|
1039
1059
|
const pos = { [focus]: { x: cx, y: cy } };
|
|
1040
1060
|
const place = (set, r) => { const a = [...set].sort(); a.forEach((t, i) => { const ang = (2 * Math.PI * i) / a.length - Math.PI / 2; pos[t] = { x: cx + r * Math.cos(ang), y: cy + r * Math.sin(ang) }; }); };
|
|
@@ -1271,7 +1291,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1271
1291
|
const fresh = tmctLedger.computeLedgerDataFromPayload(s.memoryDir.payload, {});
|
|
1272
1292
|
applyLedgerData(fresh);
|
|
1273
1293
|
const skipped = sentences.length - grounded;
|
|
1274
|
-
statusEl.textContent = sentences.length
|
|
1294
|
+
statusEl.textContent = countLabel(sentences.length, "sentence")
|
|
1275
1295
|
+ ", " + grounded + " added" + (skipped ? ", " + skipped + " skipped" : "");
|
|
1276
1296
|
} catch {
|
|
1277
1297
|
statusEl.textContent = "something went wrong ingesting that";
|
|
@@ -1323,7 +1343,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1323
1343
|
researchPlayBtn.setAttribute("aria-pressed", String(st.playing));
|
|
1324
1344
|
researchStatusEl.textContent = !researchQueue ? ""
|
|
1325
1345
|
: researchQueue.complete
|
|
1326
|
-
? 'research "' + researchQueue.topic + '" complete \\u2014 ' + researchQueue.done.length
|
|
1346
|
+
? 'research "' + researchQueue.topic + '" complete \\u2014 ' + countLabel(researchQueue.done.length, "topic")
|
|
1327
1347
|
: researchQueue.done.length + " done \\u00b7 " + researchQueue.pending.length + " queued";
|
|
1328
1348
|
}
|
|
1329
1349
|
|
|
@@ -181,3 +181,41 @@ export function renderStatsPanelInto(panelEl, stats, opts) {
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
+
|
|
185
|
+
/** The boot statusline while the big engine assets stream in — byte-identical
|
|
186
|
+
* across research-viz.mjs and ingest-viz.mjs, each commenting that it uses
|
|
187
|
+
* "the same aggregator" as the other. `parts` is an array of { loaded, total }
|
|
188
|
+
* byte counts (total 0 when a response carried no Content-Length); with no
|
|
189
|
+
* usable total the line shows loaded bytes alone rather than inventing a
|
|
190
|
+
* denominator. Self-contained, `.toString()`-splice safe. */
|
|
191
|
+
export function loadProgressLine(parts) {
|
|
192
|
+
const mb = (n) => (n / 1048576).toFixed(1);
|
|
193
|
+
let loaded = 0;
|
|
194
|
+
let total = 0;
|
|
195
|
+
let totalKnown = true;
|
|
196
|
+
for (const p of parts || []) {
|
|
197
|
+
loaded += (p && p.loaded) || 0;
|
|
198
|
+
if (p && p.total > 0) total += p.total;
|
|
199
|
+
else totalKnown = false;
|
|
200
|
+
}
|
|
201
|
+
return totalKnown && total > 0
|
|
202
|
+
? "loading the engine… " + mb(loaded) + " MB / " + mb(total) + " MB"
|
|
203
|
+
: "loading the engine… " + mb(loaded) + " MB";
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** One learned/grounded fact as its three canonical cells, plus whichever of
|
|
207
|
+
* the two per-page provenance fields the caller's fact objects carry: `source`
|
|
208
|
+
* (research.html's source key, e.g. "taught"/"seed:conceptnet" — used for the
|
|
209
|
+
* tone dot) and `provenance` (ingest.html's raw ' | '-joined tag string —
|
|
210
|
+
* shown verbatim). Both default to "" when the fact object doesn't carry
|
|
211
|
+
* that field, so either page reads back only the one it populated. Pure,
|
|
212
|
+
* `.toString()`-splice safe. */
|
|
213
|
+
export function factTripleParts(fact) {
|
|
214
|
+
return {
|
|
215
|
+
subject: String((fact && fact.subject) || ""),
|
|
216
|
+
predicate: String((fact && fact.predicate) || ""),
|
|
217
|
+
object: String((fact && fact.object) || ""),
|
|
218
|
+
source: String((fact && fact.source) || ""),
|
|
219
|
+
provenance: String((fact && fact.provenance) || ""),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
@@ -12,11 +12,14 @@
|
|
|
12
12
|
// point is "change what this world is made of" has to speak the vocabulary the
|
|
13
13
|
// world is actually written in, and neither table is a superset of the other.
|
|
14
14
|
//
|
|
15
|
-
//
|
|
16
|
-
// adventure-editor.mjs and the
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
15
|
+
// Every export here but wordBeforeCursor is written locally, and is
|
|
16
|
+
// `.toString()`-splice-safe, the same discipline adventure-editor.mjs and the
|
|
17
|
+
// *-viz render-glue functions hold — mud-viz.mjs splices these straight into
|
|
18
|
+
// mud.html's inline script, which captures a function's own source text and
|
|
19
|
+
// nothing it closes over. A splice-safe function carries its whole dependency
|
|
20
|
+
// graph inside its own body. wordBeforeCursor is a re-export of viz-theme.mjs's
|
|
21
|
+
// shared implementation, itself self-contained and splice-safe the same way —
|
|
22
|
+
// this module no longer keeps its own copy of it.
|
|
20
23
|
//
|
|
21
24
|
// Two predicate families sync differently, for the reason adventure-editor.mjs's
|
|
22
25
|
// own header sets out:
|
|
@@ -34,6 +37,8 @@
|
|
|
34
37
|
// that fails to parse this keystroke must never be read as "this fact is
|
|
35
38
|
// gone".
|
|
36
39
|
|
|
40
|
+
export { wordBeforeCursor } from "./viz-theme.mjs";
|
|
41
|
+
|
|
37
42
|
const PLACEMENT_KIND = "placement";
|
|
38
43
|
const OPENNESS_KIND = "openness";
|
|
39
44
|
const MASS_KIND = "mass";
|
|
@@ -301,13 +306,3 @@ export function planMudEditorSync(rows, state, triples) {
|
|
|
301
306
|
}
|
|
302
307
|
return { toAppend, toRemoveIds };
|
|
303
308
|
}
|
|
304
|
-
|
|
305
|
-
/** The word immediately before `cursorPos` in `text` — a run of letters, digits
|
|
306
|
-
* and hyphens, the shape this vocabulary's own terms take ("underground-space",
|
|
307
|
-
* "mole-1"). Empty when the cursor sits after whitespace or punctuation with no
|
|
308
|
-
* word directly behind it. Pure. */
|
|
309
|
-
export function wordBeforeCursor(text, cursorPos) {
|
|
310
|
-
const head = String(text || "").slice(0, cursorPos);
|
|
311
|
-
const m = head.match(/[A-Za-z][A-Za-z0-9-]*$/);
|
|
312
|
-
return m ? m[0].toLowerCase() : "";
|
|
313
|
-
}
|
|
@@ -50,10 +50,12 @@ import { fnv1a32 } from "../domain/hash.mjs";
|
|
|
50
50
|
import { bfsLevels } from "../domain/planning.mjs";
|
|
51
51
|
import { loadMemory, readFactRows } from "../adapters/memory/core.mjs";
|
|
52
52
|
import { DEFAULT_GAME_CONFIG, mudMassDrainPerTurn } from "../domain/game-config.mjs";
|
|
53
|
+
import { predatorSubjects } from "../domain/mud-facts.mjs";
|
|
53
54
|
import {
|
|
54
55
|
foldWorldState, worldActionRows, runWorldCommand, recordTold, recordExamined,
|
|
55
56
|
recordMassDrain, personKnowledgeLines, objectClassChain, diggableDirections,
|
|
56
57
|
isOutOfPlay, outOfPlayReasonOf, outOfPlayPhrase, massDrainPerTurnOf,
|
|
58
|
+
parseSnapshotSubject,
|
|
57
59
|
} from "./adventure.mjs";
|
|
58
60
|
|
|
59
61
|
const FOOD_CLASS = "food";
|
|
@@ -61,7 +63,6 @@ const LATERAL_DIRECTIONS = ["north", "south", "east", "west"];
|
|
|
61
63
|
// Deep enough to cross a whole burrow without letting one character's
|
|
62
64
|
// pathfinder walk a whole grown world every tick.
|
|
63
65
|
const WALK_SEARCH_DEPTH = 8;
|
|
64
|
-
const SNAPSHOT_RE = /^(.+)@turn(\d+)$/;
|
|
65
66
|
|
|
66
67
|
const EXIT_TOWARD_FOOD_CHANCE = 0.5;
|
|
67
68
|
const EDGE_FOLLOW_DIG_CHANCE = 0.25;
|
|
@@ -208,8 +209,8 @@ function roomsStoodIn(rows, character) {
|
|
|
208
209
|
const visited = new Set();
|
|
209
210
|
for (const row of worldActionRows(rows)) {
|
|
210
211
|
if (row.predicate !== "mgx:currently-in") continue;
|
|
211
|
-
const snapshot =
|
|
212
|
-
if ((snapshot ? snapshot
|
|
212
|
+
const snapshot = parseSnapshotSubject(row.subject);
|
|
213
|
+
if ((snapshot ? snapshot.base : row.subject) !== character) continue;
|
|
213
214
|
visited.add(row.object);
|
|
214
215
|
}
|
|
215
216
|
return visited;
|
|
@@ -220,9 +221,8 @@ function roomsStoodIn(rows, character) {
|
|
|
220
221
|
* funnel straight to the den, since the unvisited room nearest the burrow is
|
|
221
222
|
* exactly where the fox lives. The den stays reachable by the food gamble
|
|
222
223
|
* below, which is a gamble and is meant to be. */
|
|
223
|
-
const predatorRooms = (rows, state) => new Set((rows
|
|
224
|
-
.
|
|
225
|
-
.map((r) => state.placements.get(r.subject)?.object)
|
|
224
|
+
const predatorRooms = (rows, state) => new Set(predatorSubjects(rows)
|
|
225
|
+
.map((subject) => state.placements.get(subject)?.object)
|
|
226
226
|
.filter(Boolean));
|
|
227
227
|
|
|
228
228
|
/**
|