@polycode-projects/the-mechanical-code-talker 2.9.0 → 2.9.3
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 +50 -28
- package/bin/tmct.mjs +176 -31
- package/corpus/LICENSES.json +7 -0
- package/corpus/sprites/src/sprite-facts.jsonl +1033 -0
- package/corpus/worlds/manifest.json +9 -9
- package/corpus/worlds/shards/ashcombe-hall.jsonl.gz +0 -0
- package/corpus/worlds/shards/spider-fly.jsonl.gz +0 -0
- package/corpus/worlds/src/ashcombe-hall.jsonl +3 -0
- package/corpus/worlds/src/spider-fly.jsonl +17 -0
- package/package.json +37 -34
- package/src/adapters/corpus/wikipedia-live.mjs +145 -0
- package/src/adapters/memory/core.mjs +77 -12
- package/src/adapters/toml-config.mjs +6 -5
- package/src/domain/cli-verbs.mjs +4 -2
- package/src/domain/hanoi-lesson.mjs +10 -0
- package/src/domain/reference-pack.mjs +102 -0
- package/src/domain/spider-fly-world.mjs +54 -1
- package/src/domain/sprite-facts.mjs +0 -0
- package/src/services/adventure-viz.mjs +208 -66
- package/src/services/chat-page-viz.mjs +366 -37
- package/src/services/chat-session.mjs +38 -22
- package/src/services/chat.mjs +199 -20
- package/src/services/fold.mjs +28 -44
- package/src/services/import-file.mjs +7 -6
- package/src/services/init.mjs +10 -5
- package/src/services/ledger-viz.mjs +25 -34
- package/src/services/plan-viz.mjs +22 -26
- package/src/services/sessions.mjs +15 -3
- package/src/services/spider-fly-viz.mjs +52 -49
- package/src/services/sprite-catalog-viz.mjs +187 -3
- package/src/services/viz-theme.mjs +8 -0
- package/src/surfaces/web/adventure-browser-entry.mjs +23 -10
- package/src/surfaces/web/chat-browser-entry.mjs +17 -2
- package/src/surfaces/web/idb-persist.mjs +115 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +155 -25927
- package/src/surfaces/web/sprites-browser-entry.mjs +68 -0
- package/src/tools/memory-fallthrough.mjs +11 -4
|
@@ -70,3 +70,105 @@ export function cleanMissReferenceTerm(term, lexicon = loadLexicon()) {
|
|
|
70
70
|
if (!entry) return null;
|
|
71
71
|
return normFactTerm(entry.lemma);
|
|
72
72
|
}
|
|
73
|
+
|
|
74
|
+
// ---- the live Wikipedia supplement (opt-in): the pure half -----------------
|
|
75
|
+
|
|
76
|
+
export const LIVE_PACK_NAME = "wikipedia-live";
|
|
77
|
+
|
|
78
|
+
/** The provenance tag a fact stored from a LIVE Wikipedia article carries —
|
|
79
|
+
* the same reference:<pack>:<article>@<revid> shape the shipped pack uses,
|
|
80
|
+
* so memory/trust.mjs parses it with no change: kind "reference", pack
|
|
81
|
+
* "wikipedia-live". */
|
|
82
|
+
export function liveProvenanceTag(article) {
|
|
83
|
+
return `reference:${LIVE_PACK_NAME}:${article.title}@${article.revid}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The cited answer for a clean miss the live lookup could ground: the
|
|
87
|
+
* article's summary with its title, the LIVE source named, the licence and
|
|
88
|
+
* the revision-pinned URL always visible. */
|
|
89
|
+
export function renderLiveReferenceAnswer(term, article) {
|
|
90
|
+
return `${term} — ${article.summary} (source: live Wikipedia article "${article.title}", `
|
|
91
|
+
+ `English Wikipedia, CC BY-SA 4.0 — ${article.url}?oldid=${article.revid})`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The pure half of the LIVE clean-miss gate — same fold, shape check and
|
|
95
|
+
* RELATION_TERM exclusion as cleanMissReferenceTerm, but WITHOUT the
|
|
96
|
+
* lexicon-membership wall: a word the lexicon has never met is exactly what
|
|
97
|
+
* a live lookup is for. A known lexicon entry still resolves to its lemma
|
|
98
|
+
* (so "otters" and "otter" share one key); an unknown word keys on its own
|
|
99
|
+
* folded form. Capped at three words — longer phrases are never a clean
|
|
100
|
+
* vocabulary miss. */
|
|
101
|
+
export function cleanMissLiveTerm(term, lexicon = loadLexicon()) {
|
|
102
|
+
const t = normFactTerm(term);
|
|
103
|
+
if (!t || !/^[a-z][a-z' -]*$/.test(t)) return null;
|
|
104
|
+
if (RELATION_TERM[t]) return null;
|
|
105
|
+
if (t.split(/\s+/).length > 3) return null;
|
|
106
|
+
const entry = lookupNoun(lexicon, t);
|
|
107
|
+
return entry ? normFactTerm(entry.lemma) : t;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ---- summary/isa extraction, shared by the pack build and the live lookup --
|
|
111
|
+
|
|
112
|
+
export const SUMMARY_CHAR_CAP = 400;
|
|
113
|
+
|
|
114
|
+
/** Whole leading sentences of `text` up to `cap` chars; a first sentence
|
|
115
|
+
* longer than the cap is hard-cut at it. */
|
|
116
|
+
export function sentencesUpTo(text, cap) {
|
|
117
|
+
const parts = String(text ?? "").split(/(?<=[.!?])\s+/);
|
|
118
|
+
let out = "";
|
|
119
|
+
for (const part of parts) {
|
|
120
|
+
const candidate = out ? `${out} ${part}` : part;
|
|
121
|
+
if (candidate.length > cap) break;
|
|
122
|
+
out = candidate;
|
|
123
|
+
}
|
|
124
|
+
return out || String(text ?? "").slice(0, cap);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const ISA_RE = /^[A-Z][^.!?]{0,80}? (?:is|are) (?:(a|an|the) )?([a-z][a-z -]{1,60})/;
|
|
128
|
+
|
|
129
|
+
// Words that end the noun phrase: a relative clause or a trailing modifier
|
|
130
|
+
// carries detail, not the category ("a place where ships shelter" → place).
|
|
131
|
+
const ISA_CLAUSE_CUT = new Set([
|
|
132
|
+
"that", "which", "where", "who", "whose", "whom", "when", "used", "found",
|
|
133
|
+
"made", "with", "in", "on", "for", "from", "by", "to", "and", "or",
|
|
134
|
+
]);
|
|
135
|
+
// Classifier heads carry no category of their own ("a member of the cat
|
|
136
|
+
// family" names family membership, not what the thing is) — no isa beats a
|
|
137
|
+
// generic one, because a stored subClassOf must stay meaningful.
|
|
138
|
+
const ISA_GENERIC_HEADS = new Set([
|
|
139
|
+
"type", "kind", "sort", "form", "class", "variety", "group", "part",
|
|
140
|
+
"piece", "member", "way", "term", "name", "word", "thing", "example",
|
|
141
|
+
"family", "genus", "species", "unit", "series", "set", "list", "number",
|
|
142
|
+
"amount",
|
|
143
|
+
]);
|
|
144
|
+
const ISA_ARTICLES = new Set(["a", "an", "the"]);
|
|
145
|
+
|
|
146
|
+
/** The isa lemma of a lead's first sentence, or null. The copula must sit in
|
|
147
|
+
* the first sentence; an of-chain resolves to its final noun ("a kind of
|
|
148
|
+
* dog" → dog); a bare-plural predicate counts only when the head is an
|
|
149
|
+
* inflected plural the lexicon folds ("are animals that…" → animal, but
|
|
150
|
+
* "is light" stays null); a generic classifier head is no isa at all. */
|
|
151
|
+
export function isaOf(plain, lexicon) {
|
|
152
|
+
const m = String(plain ?? "").match(ISA_RE);
|
|
153
|
+
if (!m) return null;
|
|
154
|
+
const bare = !m[1];
|
|
155
|
+
const window = [];
|
|
156
|
+
for (const word of m[2].trim().split(/\s+/)) {
|
|
157
|
+
if (ISA_CLAUSE_CUT.has(word)) break;
|
|
158
|
+
window.push(word);
|
|
159
|
+
if (window.length >= 6) break;
|
|
160
|
+
}
|
|
161
|
+
const lastOf = window.lastIndexOf("of");
|
|
162
|
+
let headWords = lastOf >= 0 ? window.slice(lastOf + 1) : window;
|
|
163
|
+
while (headWords.length && ISA_ARTICLES.has(headWords[0])) headWords = headWords.slice(1);
|
|
164
|
+
if (!headWords.length) return null;
|
|
165
|
+
const headToken = headWords[headWords.length - 1].split("-").pop();
|
|
166
|
+
if (!headToken || ISA_GENERIC_HEADS.has(headToken)) return null;
|
|
167
|
+
const entry = lookupNoun(lexicon, headToken);
|
|
168
|
+
if (!entry) return null;
|
|
169
|
+
const lemma = normFactTerm(entry.lemma);
|
|
170
|
+
// A bare predicate ("is light") is only trusted when the head is an
|
|
171
|
+
// inflected plural the lexicon folded back to its lemma.
|
|
172
|
+
if (bare && (!/s$/.test(headToken) || normFactTerm(headToken) === lemma)) return null;
|
|
173
|
+
return lemma;
|
|
174
|
+
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// spider-fly-world.mjs — the one pure definition of the spider/fly 10x10
|
|
2
2
|
// grid: cell naming, Chebyshev geometry, the fixed web block, the seed
|
|
3
3
|
// taxonomy, and the static fact/rule/meta rows the shipped world carries.
|
|
4
|
-
// Pure
|
|
4
|
+
// Pure — the only import is the sibling pure-data game-config.mjs, so the
|
|
5
|
+
// structural self-description facts below quote the same shipped defaults
|
|
6
|
+
// the engine actually runs. Both
|
|
5
7
|
// scripts/gen-spider-fly-world.mjs (writes corpus/worlds/src/spider-fly.jsonl)
|
|
6
8
|
// and src/services/spider-fly.mjs (the runtime) read the SAME grid/web
|
|
7
9
|
// constants from here, so the shipped world and the engine that plays it can
|
|
8
10
|
// never drift apart.
|
|
9
11
|
|
|
12
|
+
import { DEFAULT_GAME_CONFIG } from "./game-config.mjs";
|
|
13
|
+
|
|
10
14
|
export const WORLD_NAME = "spider-fly";
|
|
11
15
|
export const GRID_SIZE = 10;
|
|
12
16
|
|
|
@@ -147,6 +151,55 @@ export function* worldFactRows() {
|
|
|
147
151
|
for (const [subject, superclass] of SEED_TAXONOMY) {
|
|
148
152
|
yield { world: WORLD_NAME, kind: "fact", subject, predicate: "rdfs:subClassOf", object: superclass };
|
|
149
153
|
}
|
|
154
|
+
yield* structuralFactRows();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const pluralize = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
158
|
+
|
|
159
|
+
/** The board/web/agent structure the page's own controls and copy surface,
|
|
160
|
+
* restated as askable facts — so "what is the board?" / "what is the vision
|
|
161
|
+
* radius?" ground instead of missing. Every number is either grid geometry
|
|
162
|
+
* (fixed here) or a shipped tuning default (game-config.mjs); the tunable
|
|
163
|
+
* ones say "by default" because a live session's sliders or tmct.toml can
|
|
164
|
+
* move them, and a fact that a slider drag falsifies would be a lie.
|
|
165
|
+
* Predicates stick to the curated/readable families the fact reader
|
|
166
|
+
* verbalizes cleanly ("has", "is", "covers", "lasts for", "starts with",
|
|
167
|
+
* "loses", "lays", "arrives", "hatches after/into"). */
|
|
168
|
+
export function* structuralFactRows() {
|
|
169
|
+
const fact = (subject, predicate, object) => ({ world: WORLD_NAME, kind: "fact", subject, predicate, object });
|
|
170
|
+
const knobs = DEFAULT_GAME_CONFIG.spiderFly;
|
|
171
|
+
let webCellCount = 0;
|
|
172
|
+
for (let y = 1; y <= GRID_SIZE; y += 1) {
|
|
173
|
+
for (let x = 1; x <= GRID_SIZE; x += 1) {
|
|
174
|
+
if (isInWebBlock(x, y)) webCellCount += 1;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
yield fact("board", "rdf:type", "grid");
|
|
178
|
+
yield fact("board", "mgx:hasA", pluralize(GRID_SIZE, "row"));
|
|
179
|
+
yield fact("board", "mgx:hasA", pluralize(GRID_SIZE, "column"));
|
|
180
|
+
yield fact("board", "mgx:hasA", pluralize(GRID_SIZE * GRID_SIZE, "cell"));
|
|
181
|
+
yield fact("web", "mgx:cover", `${pluralize(webCellCount, "cell")} around ${cellId(WEB_HOME.x, WEB_HOME.y)}`);
|
|
182
|
+
yield fact("web", "mgx:last-for", `${pluralize(knobs.webDurationTurns, "turn")} by default when a spider builds one`);
|
|
183
|
+
yield fact("spider", "mgx:hasA", `vision radius of ${pluralize(knobs.spiderVisionRadius, "cell")} by default`);
|
|
184
|
+
yield fact("spider", "mgx:start-with", `mass ${knobs.spiderInitialMass} by default`);
|
|
185
|
+
yield fact("spider", "mgx:lose", `${knobs.spiderMassDecrementPerTurn} mass per turn by default`);
|
|
186
|
+
yield fact("spider", "mgx:lay", `one egg at mass ${knobs.eggLayMassThreshold} by default`);
|
|
187
|
+
yield fact("fly", "mgx:hasA", `vision radius of ${pluralize(knobs.flyVisionRadius, "cell")} by default`);
|
|
188
|
+
yield fact("fly", "mgx:start-with", `mass ${knobs.flyInitialMass} by default`);
|
|
189
|
+
yield fact("fly", "mgx:lose", `${knobs.flyMassDecrementPerTurn} mass per turn by default`);
|
|
190
|
+
yield fact("fly", "mgx:arrive", `every ${pluralize(knobs.flySpawnIntervalTurns, "turn")} at the edge of the board by default`);
|
|
191
|
+
yield fact("egg", "mgx:hatch-after", `${pluralize(knobs.eggHatchDelayTurns, "turn")} by default`);
|
|
192
|
+
yield fact("egg", "mgx:hatch-into", `${pluralize(knobs.eggHatchCount, "spider")} by default`);
|
|
193
|
+
// The page's own slider label is "vision radius", so that exact term must
|
|
194
|
+
// describe too, not just the per-class rows above. One combined row while
|
|
195
|
+
// the two class defaults agree, one row per class if they ever split —
|
|
196
|
+
// never a combined claim the config doesn't actually make.
|
|
197
|
+
if (knobs.spiderVisionRadius === knobs.flyVisionRadius) {
|
|
198
|
+
yield fact("vision radius", "mgx:hasProperty", `${pluralize(knobs.spiderVisionRadius, "cell")} for both the spider and the fly by default`);
|
|
199
|
+
} else {
|
|
200
|
+
yield fact("vision radius", "mgx:hasProperty", `${pluralize(knobs.spiderVisionRadius, "cell")} for the spider by default`);
|
|
201
|
+
yield fact("vision radius", "mgx:hasProperty", `${pluralize(knobs.flyVisionRadius, "cell")} for the fly by default`);
|
|
202
|
+
}
|
|
150
203
|
}
|
|
151
204
|
|
|
152
205
|
/** The world's one meta row (its opening line). */
|
|
Binary file
|