@polycode-projects/the-mechanical-code-talker 2.7.12 → 2.7.14
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/corpus/worlds/manifest.json +5 -5
- package/corpus/worlds/shards/ashcombe-hall.jsonl.gz +0 -0
- package/corpus/worlds/src/ashcombe-hall.jsonl +1 -0
- package/package.json +1 -1
- package/src/domain/ask.mjs +27 -3
- package/src/domain/interpret/normalize.mjs +1 -1
- package/src/domain/interpret/strategies/keywords.mjs +15 -2
- package/src/domain/spider-fly-world.mjs +13 -0
- package/src/domain/sprite-map.mjs +49 -1
- package/src/services/adventure-autoplay.mjs +192 -0
- package/src/services/adventure-viz.mjs +262 -0
- package/src/services/chat.mjs +420 -37
- package/src/services/spider-fly-viz.mjs +39 -9
- package/src/services/spider-fly.mjs +236 -68
- package/src/surfaces/web/adventure-browser-entry.mjs +88 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +69 -15
- package/src/surfaces/web/spider-fly-browser-entry.mjs +10 -8
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// adventure-browser-entry.mjs — the esbuild entry for the adventure's
|
|
2
|
+
// full-screen/home-page pages (public/adventure-browser.bundle.js, built by
|
|
3
|
+
// scripts/build-adventure-bundle.mjs), mirroring
|
|
4
|
+
// spider-fly-browser-entry.mjs's own session-factory shape.
|
|
5
|
+
//
|
|
6
|
+
// Unlike spider-fly's board, Ashcombe Hall's own facts+rules cannot be
|
|
7
|
+
// regenerated in-browser from a pure JS module — its canonical definition is
|
|
8
|
+
// a Node-only JSONL corpus source, read through an fs/gzip provider the
|
|
9
|
+
// browser cannot run. So this session takes the world as data
|
|
10
|
+
// (`worldPayload`, `{ name, facts, rules, opening }`), embedded into the page
|
|
11
|
+
// at build time by scripts/build-demo-site.mjs's own read through the real
|
|
12
|
+
// worlds-pack provider (see adventure-viz.mjs's header for the full
|
|
13
|
+
// rationale) — the bootstrap below then just appends it, exactly the shape
|
|
14
|
+
// openAdventure() itself writes for a real chat session.
|
|
15
|
+
//
|
|
16
|
+
// This session exposes ONLY a raw autoplay tick and a read-only snapshot —
|
|
17
|
+
// no chat dock. Every state-changing command adventure.mjs's own
|
|
18
|
+
// runWorldCommand issues (go/take/open/...) already re-narrates itself
|
|
19
|
+
// through the extractive completions digest on every turn (the
|
|
20
|
+
// "auto-relook"), so this bundle carries the same wink-nlp/completions
|
|
21
|
+
// dependency chain chat.mjs's own runTurn does; a second, lighter path was
|
|
22
|
+
// not available to duck under it. Nothing here calls runTurn, though — the
|
|
23
|
+
// one entry point exercised is adventureTurn itself, via
|
|
24
|
+
// adventure-autoplay.mjs, which is the "auto-play is a caller of the
|
|
25
|
+
// existing interpreter, never a second one" contract this whole feature
|
|
26
|
+
// rests on.
|
|
27
|
+
import {
|
|
28
|
+
createInMemoryStore, appendFacts, appendRule, loadMemory, readFactRows,
|
|
29
|
+
} from "../../adapters/memory/core.mjs";
|
|
30
|
+
import { foldWorldState, worldDigestRows } from "../../services/adventure.mjs";
|
|
31
|
+
import { runAdventureAutoplayTick } from "../../services/adventure-autoplay.mjs";
|
|
32
|
+
import { resolveSpriteForClass, SPRITE_REGISTRY } from "../../domain/sprite-map.mjs";
|
|
33
|
+
|
|
34
|
+
/** A live in-memory adventure this page's ticker drives one auto-play tick
|
|
35
|
+
* at a time. Returns `{ memoryDir, autoplayTick, snapshot }`.
|
|
36
|
+
* `worldPayload.facts`/`.rules` seed the store exactly the way
|
|
37
|
+
* openAdventure() itself does for a real session; `planHolder.state` is set
|
|
38
|
+
* the same way, so adventureTurn treats every subsequent call as a live,
|
|
39
|
+
* already-open world rather than a fresh opening line. */
|
|
40
|
+
export async function createAdventureSession(worldPayload) {
|
|
41
|
+
const memoryDir = createInMemoryStore();
|
|
42
|
+
const tag = `world:${worldPayload.name}`;
|
|
43
|
+
await appendFacts(memoryDir, worldPayload.facts.map((f) => ({
|
|
44
|
+
subject: f.subject, predicate: f.predicate, object: f.object, provenance: tag,
|
|
45
|
+
})));
|
|
46
|
+
for (const rule of worldPayload.rules) {
|
|
47
|
+
await appendRule(memoryDir, { name: rule.name, kind: rule.ruleKind, slots: rule.slots, provenance: tag });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const planHolder = { state: { adventure: { world: worldPayload.name } } };
|
|
51
|
+
const sessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
52
|
+
let exposedRoomIds = new Set();
|
|
53
|
+
const openingRows = readFactRows(await loadMemory(memoryDir));
|
|
54
|
+
const openingHere = foldWorldState(openingRows).placements.get("player")?.object ?? null;
|
|
55
|
+
if (openingHere) exposedRoomIds = new Set([openingHere]);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
memoryDir,
|
|
59
|
+
|
|
60
|
+
/** One auto-play tick: infer the goal, execute exactly one move through
|
|
61
|
+
* adventureTurn (adventure-autoplay.mjs's own contract), thread the
|
|
62
|
+
* exposed-room set forward. Returns runAdventureAutoplayTick's own
|
|
63
|
+
* `{ turn, goal, plan, done, stalled }` unmodified. */
|
|
64
|
+
async autoplayTick() {
|
|
65
|
+
const result = await runAdventureAutoplayTick(memoryDir, {
|
|
66
|
+
exposedRoomIds, planHolder, sessionId, env: {},
|
|
67
|
+
});
|
|
68
|
+
exposedRoomIds = result.exposedRoomIds;
|
|
69
|
+
return result;
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
/** A read-only fold of the current room — no engine advance — for the
|
|
73
|
+
* page's own redraw after boot and after every tick. */
|
|
74
|
+
async snapshot() {
|
|
75
|
+
const rows = readFactRows(await loadMemory(memoryDir));
|
|
76
|
+
const state = foldWorldState(rows);
|
|
77
|
+
const here = state.placements.get("player")?.object ?? null;
|
|
78
|
+
return { rows, state, here, turn: state.turnCount };
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Re-exported so the page's own rendering script (adventure-viz.mjs) never
|
|
84
|
+
// has to duplicate sprite resolution or the digest reader — the same posture
|
|
85
|
+
// spider-fly-browser-entry.mjs's own globalThis.tmctSpiderFly re-export takes.
|
|
86
|
+
globalThis.tmctAdventure = {
|
|
87
|
+
createAdventureSession, resolveSpriteForClass, SPRITE_REGISTRY, worldDigestRows,
|
|
88
|
+
};
|
|
@@ -3071,6 +3071,7 @@ ${shown.join("\n")}${tail}`;
|
|
|
3071
3071
|
"nothing",
|
|
3072
3072
|
"one",
|
|
3073
3073
|
"any",
|
|
3074
|
+
"anywhere",
|
|
3074
3075
|
"last",
|
|
3075
3076
|
// temporal filler ("when was X last touched")
|
|
3076
3077
|
"usually",
|
|
@@ -3370,9 +3371,10 @@ ${shown.join("\n")}${tail}`;
|
|
|
3370
3371
|
fuzzyVerb = { from: lcWords[at], to: fuzzyWords[at] };
|
|
3371
3372
|
}
|
|
3372
3373
|
}
|
|
3373
|
-
if (!verbHit
|
|
3374
|
+
if (!verbHit) {
|
|
3374
3375
|
for (let i = 0; i < lcWords.length; i += 1) {
|
|
3375
3376
|
const k = PASSIVE_PARTICIPLE_TO_KIND[lcWords[i]];
|
|
3377
|
+
if (k && lcWords[i] === "used" && lcWords[i + 1] === "for") continue;
|
|
3376
3378
|
if (k && lcWords.slice(0, i).some((w) => PASSIVE_AUX.has(w))) {
|
|
3377
3379
|
verbHit = { kind: k, start: i, end: i + 1 };
|
|
3378
3380
|
break;
|
|
@@ -6195,14 +6197,23 @@ ${options2}
|
|
|
6195
6197
|
}
|
|
6196
6198
|
}
|
|
6197
6199
|
}
|
|
6198
|
-
if (nearest)
|
|
6200
|
+
if (nearest) {
|
|
6201
|
+
pool = [...pool, nearest];
|
|
6202
|
+
if (branches) {
|
|
6203
|
+
const branchResult = traverse(graph, parsed, { pinnedObjMatch: nearest });
|
|
6204
|
+
branches = [...branches, { candidate: nearest, result: branchResult, rendered: render(parsed, branchResult, graph) }];
|
|
6205
|
+
}
|
|
6206
|
+
}
|
|
6199
6207
|
}
|
|
6200
6208
|
const noun = pool.length && pool.every((i) => i.class === "Commit") ? "commit" : "module";
|
|
6201
6209
|
const shown = pool.slice(0, OVERFLOW_CAP).map((i) => i.label);
|
|
6202
6210
|
const extra2 = pool.length > OVERFLOW_CAP ? `, \u2026and ${pool.length - OVERFLOW_CAP} more` : "";
|
|
6203
6211
|
const lead = `"${parsed.object}" matches more than one ${noun} ambiguously \u2014 did you mean ${listJoin(shown)}${extra2}? Try one of those. If you're not sure, narrow it to one name.`;
|
|
6212
|
+
const term = String(parsed.object || "");
|
|
6213
|
+
const termRe = term ? new RegExp(`\\b${escapeRegex(term)}\\b`, "gi") : null;
|
|
6214
|
+
const branchText = (b) => termRe ? b.rendered.content.replace(termRe, b.candidate.label) : b.rendered.content;
|
|
6204
6215
|
const content = branches && branches.length ? `${lead}
|
|
6205
|
-
${branches.map((b, i) => `${i + 1}) ${b.candidate.label}: ${b
|
|
6216
|
+
${branches.map((b, i) => `${i + 1}) ${b.candidate.label}: ${branchText(b)}`).join("\n")}` : lead;
|
|
6206
6217
|
return {
|
|
6207
6218
|
content,
|
|
6208
6219
|
miss: false,
|
|
@@ -23574,6 +23585,7 @@ ${JSON.stringify(envelope, null, 2)}`;
|
|
|
23574
23585
|
// src/services/spider-fly.mjs
|
|
23575
23586
|
init_planning();
|
|
23576
23587
|
init_core();
|
|
23588
|
+
init_hash();
|
|
23577
23589
|
|
|
23578
23590
|
// src/services/spider-fly-turn.mjs
|
|
23579
23591
|
init_core();
|
|
@@ -23982,7 +23994,7 @@ ${JSON.stringify(envelope, null, 2)}`;
|
|
|
23982
23994
|
const w = String(word || "").trim();
|
|
23983
23995
|
if (/[a-z]ies$/i.test(w)) return `${w.slice(0, -3)}y`;
|
|
23984
23996
|
if (/(ses|xes|zes|ches|shes)$/i.test(w)) return w.slice(0, -2);
|
|
23985
|
-
if (/[a-z]s$/i.test(w) && !/ss$/i.test(w)) return w.slice(0, -1);
|
|
23997
|
+
if (/[a-z]s$/i.test(w) && !/(?:ss|ous)$/i.test(w)) return w.slice(0, -1);
|
|
23986
23998
|
return w;
|
|
23987
23999
|
}
|
|
23988
24000
|
var TEACH_ADVERB_SKIP_SRC = "(?:(?:usually|often|sometimes|rarely|always|typically|generally|occasionally|frequently|normally|regularly|commonly|mostly|currently|still|also|really|actually)\\s+)?";
|
|
@@ -24700,7 +24712,11 @@ ${shown.map(renderFactLine).join("\n")}`,
|
|
|
24700
24712
|
const subj = factTermVariants(normFactTerm2, subjTerm);
|
|
24701
24713
|
const obj = factTermVariants(normFactTerm2, objTerm);
|
|
24702
24714
|
const hit2 = facts.find((f) => f.predicate === compPredicate && subj.has(f.subject) && obj.has(f.object));
|
|
24703
|
-
if (hit2)
|
|
24715
|
+
if (hit2) {
|
|
24716
|
+
const reversed = facts.find((f) => f.predicate === compPredicate && subj.has(f.object) && obj.has(f.subject));
|
|
24717
|
+
const caveat = reversed ? ` \u2014 though you also told me the opposite: ${renderFactLine(reversed)}. Both are stored; I won't silently pick one.` : "";
|
|
24718
|
+
return { text: `yes \u2014 ${renderFactLine(hit2)}${caveat}`, replace: true };
|
|
24719
|
+
}
|
|
24704
24720
|
const known = facts.filter((f) => f.predicate === compPredicate && (subj.has(f.subject) || subj.has(f.object)));
|
|
24705
24721
|
const shown = known.length ? ` I do know: ${known.slice(0, 3).map(renderFactLine).join("; ")}.` : "";
|
|
24706
24722
|
return {
|
|
@@ -24780,19 +24796,25 @@ ${shown.map(renderFactLine).join("\n")}`,
|
|
|
24780
24796
|
);
|
|
24781
24797
|
const hit2 = hasHit(subj);
|
|
24782
24798
|
if (hit2) return { text: `yes \u2014 ${renderFactLine(hit2)}`, replace: true };
|
|
24783
|
-
let
|
|
24784
|
-
const liftChain = [];
|
|
24799
|
+
let frontier = [{ terms: subj, chain: [] }];
|
|
24785
24800
|
const liftSeen = /* @__PURE__ */ new Set();
|
|
24786
24801
|
for (let hop = 0; hop < 4; hop += 1) {
|
|
24787
|
-
const
|
|
24788
|
-
|
|
24789
|
-
|
|
24790
|
-
|
|
24791
|
-
|
|
24792
|
-
|
|
24793
|
-
|
|
24802
|
+
const nextFrontier = [];
|
|
24803
|
+
for (const { terms, chain } of frontier) {
|
|
24804
|
+
const steps = facts.filter((f) => ISA_PREDICATES2.has(f.predicate) && terms.has(f.subject) && !liftSeen.has(f.object));
|
|
24805
|
+
for (const step of steps) {
|
|
24806
|
+
if (liftSeen.has(step.object)) continue;
|
|
24807
|
+
liftSeen.add(step.object);
|
|
24808
|
+
const nextChain = [...chain, step];
|
|
24809
|
+
const lifted = hasHit(factTermVariants(normFactTerm2, step.object));
|
|
24810
|
+
if (lifted) {
|
|
24811
|
+
return { text: `yes \u2014 ${[...nextChain.map(renderFactLine), renderFactLine(lifted)].join("; ")}`, replace: true };
|
|
24812
|
+
}
|
|
24813
|
+
nextFrontier.push({ terms: factTermVariants(normFactTerm2, step.object), chain: nextChain });
|
|
24814
|
+
}
|
|
24794
24815
|
}
|
|
24795
|
-
|
|
24816
|
+
if (!nextFrontier.length) break;
|
|
24817
|
+
frontier = nextFrontier;
|
|
24796
24818
|
}
|
|
24797
24819
|
return null;
|
|
24798
24820
|
}
|
|
@@ -25310,6 +25332,38 @@ ${shown.join("\n")}${extra}`, replace: true, ...rest.length ? { pending: { items
|
|
|
25310
25332
|
}
|
|
25311
25333
|
const polarityReply = isaPolarityReply(hit2, negHit || directDisjoint);
|
|
25312
25334
|
if (polarityReply) return polarityReply;
|
|
25335
|
+
if (disjointRows.length) {
|
|
25336
|
+
const ancestryOf = (seed) => {
|
|
25337
|
+
const closure = new Set(seed);
|
|
25338
|
+
let frontier = new Set(seed);
|
|
25339
|
+
for (let hop = 0; hop < 8 && frontier.size; hop += 1) {
|
|
25340
|
+
const next = /* @__PURE__ */ new Set();
|
|
25341
|
+
for (const [a, b] of mixedSubClassEdges) {
|
|
25342
|
+
if (frontier.has(a) && !closure.has(b)) next.add(b);
|
|
25343
|
+
}
|
|
25344
|
+
if (!next.size) break;
|
|
25345
|
+
for (const t of next) closure.add(t);
|
|
25346
|
+
frontier = next;
|
|
25347
|
+
}
|
|
25348
|
+
return closure;
|
|
25349
|
+
};
|
|
25350
|
+
const objectAncestry = ancestryOf(objVariants);
|
|
25351
|
+
const objViolation = disjointGateViolations.find((vv) => subjCandidates.has(vv.subject) && objectAncestry.has(vv.object) && !ancestryOf([vv.viaClass]).has(vv.object) && !ancestryOf([vv.object]).has(vv.viaClass));
|
|
25352
|
+
if (objViolation) {
|
|
25353
|
+
const posFact = isa.filter((f) => subjCandidates.has(f.subject) && f.object === objViolation.viaClass).sort(byTrust)[0];
|
|
25354
|
+
const disjointFact = disjointRows.find((f) => f.subject === objViolation.viaClass && f.object === objViolation.object || f.subject === objViolation.object && f.object === objViolation.viaClass);
|
|
25355
|
+
const objectNeedsLift = !objVariants.has(objViolation.object);
|
|
25356
|
+
const objFact = objectNeedsLift ? isa.filter((f) => objVariants.has(f.subject) && f.object === objViolation.object).sort(byTrust)[0] : null;
|
|
25357
|
+
if (posFact && disjointFact && (!objectNeedsLift || objFact)) {
|
|
25358
|
+
const kindEcho = stripTrailingDiscourseTag(isaAsk[2]).trim();
|
|
25359
|
+
const chain = [posFact, ...objFact ? [objFact] : []].map(renderFactLine).join("; ");
|
|
25360
|
+
return {
|
|
25361
|
+
text: `no \u2014 ${chain}; and ${factPhrase(disjointFact)}${disjointFact.provenance ? ` (source: ${disjointFact.provenance})` : ""} \u2014 so ${isaSubject} can never be ${indefiniteArticleFor(kindEcho)} ${kindEcho}.`,
|
|
25362
|
+
replace: true
|
|
25363
|
+
};
|
|
25364
|
+
}
|
|
25365
|
+
}
|
|
25366
|
+
}
|
|
25313
25367
|
const ent = await resolveEntity(graph, isaSubject);
|
|
25314
25368
|
if (ent) {
|
|
25315
25369
|
const bridgeSubjects = /* @__PURE__ */ new Map();
|
|
@@ -52,7 +52,7 @@ import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
|
52
52
|
import {
|
|
53
53
|
worldFactRows, WORLD_NAME, WORLD_OPENING, cellId, parseCellId, DIRECTION_DELTA, visibleCells,
|
|
54
54
|
} from "../../domain/spider-fly-world.mjs";
|
|
55
|
-
import { foldSpiderFlyState, runSpiderFlyTick, startSpiderFlyGame, DEFAULT_VISION_RADIUS } from "../../services/spider-fly.mjs";
|
|
55
|
+
import { foldSpiderFlyState, runSpiderFlyTick, startSpiderFlyGame, liveWebs, DEFAULT_VISION_RADIUS } from "../../services/spider-fly.mjs";
|
|
56
56
|
import { resolveSpriteForClass, SPRITE_REGISTRY } from "../../domain/sprite-map.mjs";
|
|
57
57
|
|
|
58
58
|
/** A live in-memory game the page's ticker and chat dock can both drive.
|
|
@@ -77,8 +77,8 @@ export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
|
77
77
|
const { facts: startFacts } = await startSpiderFlyGame(memoryDir, { flyCount });
|
|
78
78
|
const initialAgents = {};
|
|
79
79
|
for (const f of startFacts) {
|
|
80
|
-
if (f.predicate
|
|
81
|
-
initialAgents[f.subject] = {
|
|
80
|
+
if (f.predicate === "mgx:currently-in") initialAgents[f.subject] = { ...initialAgents[f.subject], cell: f.object };
|
|
81
|
+
else if (f.predicate === "mgx:mass") initialAgents[f.subject] = { ...initialAgents[f.subject], mass: Number(f.object) };
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
const graph = parseEntities({ individuals: [], objectProperties: [] });
|
|
@@ -93,7 +93,7 @@ export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
|
93
93
|
memoryDir,
|
|
94
94
|
sessionId,
|
|
95
95
|
opening: WORLD_OPENING,
|
|
96
|
-
initial: { turn: 0, agents: initialAgents },
|
|
96
|
+
initial: { turn: 0, agents: initialAgents, activeWebs: [] },
|
|
97
97
|
taxonomyRows,
|
|
98
98
|
|
|
99
99
|
/** Run one real engine turn directly. Returns spider-fly.mjs's own
|
|
@@ -127,16 +127,18 @@ export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
|
127
127
|
|
|
128
128
|
/** A read-only fold of the CURRENT board — no engine advance, no goal
|
|
129
129
|
* lines (see the header comment: only a raw tick() recomputes those).
|
|
130
|
-
* Lets the page resync positions/turn count after a
|
|
130
|
+
* Lets the page resync positions/turn count/mass/active webs after a
|
|
131
|
+
* chat-driven tick. Web individuals are never listed as agents (that's
|
|
132
|
+
* spider-1/fly-1/... only) — they surface only through activeWebs. */
|
|
131
133
|
async snapshot() {
|
|
132
134
|
const rows = readFactRows(await loadMemory(memoryDir));
|
|
133
135
|
const state = foldSpiderFlyState(rows);
|
|
134
136
|
const agents = {};
|
|
135
137
|
for (const [id, place] of state.placements) {
|
|
136
|
-
if (state.removed.has(id)) continue;
|
|
137
|
-
agents[id] = { cell: place.cell };
|
|
138
|
+
if (state.removed.has(id) || /^web-\d+$/.test(id)) continue;
|
|
139
|
+
agents[id] = { cell: place.cell, mass: state.mass.get(id)?.value ?? null };
|
|
138
140
|
}
|
|
139
|
-
return { turn: state.turnCount, agents };
|
|
141
|
+
return { turn: state.turnCount, agents, activeWebs: liveWebs(state.webs, state.turnCount) };
|
|
140
142
|
},
|
|
141
143
|
};
|
|
142
144
|
}
|