@polycode-projects/the-mechanical-code-talker 2.7.3 → 2.7.5
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/package.json +2 -1
- package/src/adapters/memory/core.mjs +1 -0
- package/src/domain/router/drive.mjs +43 -17
- package/src/domain/router/planner.mjs +54 -4
- package/src/domain/router/resolver.mjs +88 -14
- package/src/domain/sprite-map.mjs +131 -0
- package/src/services/adventure.mjs +7 -0
- package/src/services/chat.mjs +25 -0
- package/src/services/spider-fly-turn.mjs +352 -0
- package/src/services/spider-fly-viz.mjs +492 -0
- package/src/services/spider-fly.mjs +491 -0
- package/src/services/viz-ticker.mjs +119 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +123 -85
- package/src/surfaces/web/spider-fly-browser-entry.mjs +152 -0
|
@@ -9675,6 +9675,8 @@ ${bodyText}` : graphText, tier });
|
|
|
9675
9675
|
return { id: `src:provider:${desc.name}`, type: "provider" };
|
|
9676
9676
|
case "corpus":
|
|
9677
9677
|
return { id: `src:corpus:${desc.name}`, type: "corpus" };
|
|
9678
|
+
case "corpusWeak":
|
|
9679
|
+
return { id: `src:corpus-weak:${desc.name}`, type: "corpusWeak" };
|
|
9678
9680
|
// One Source per pack article (the @revid stays in the article segment),
|
|
9679
9681
|
// so two facts from the same article corroborate nothing extra.
|
|
9680
9682
|
case "reference":
|
|
@@ -10580,6 +10582,94 @@ CREATE INDEX IF NOT EXISTS edges_by_prop ON edges(prop);
|
|
|
10580
10582
|
}
|
|
10581
10583
|
});
|
|
10582
10584
|
|
|
10585
|
+
// src/domain/skos-view.mjs
|
|
10586
|
+
function buildSkosConceptView(rows, { conceptBase = "concept:", relationMap = DEFAULT_RELATION_MAP } = {}) {
|
|
10587
|
+
const synonymPreds = new Set(relationMap.synonym || []);
|
|
10588
|
+
const relatedPreds = new Set(relationMap.related || []);
|
|
10589
|
+
const parent = /* @__PURE__ */ new Map();
|
|
10590
|
+
const ensure = (t) => {
|
|
10591
|
+
if (!parent.has(t)) parent.set(t, t);
|
|
10592
|
+
};
|
|
10593
|
+
const find = (x) => {
|
|
10594
|
+
let r = x;
|
|
10595
|
+
while (parent.get(r) !== r) r = parent.get(r);
|
|
10596
|
+
while (parent.get(x) !== r) {
|
|
10597
|
+
const next = parent.get(x);
|
|
10598
|
+
parent.set(x, r);
|
|
10599
|
+
x = next;
|
|
10600
|
+
}
|
|
10601
|
+
return r;
|
|
10602
|
+
};
|
|
10603
|
+
const union = (a, b) => {
|
|
10604
|
+
const ra = find(a), rb = find(b);
|
|
10605
|
+
if (ra === rb) return;
|
|
10606
|
+
if (ra < rb) parent.set(rb, ra);
|
|
10607
|
+
else parent.set(ra, rb);
|
|
10608
|
+
};
|
|
10609
|
+
const relatedRaw = [];
|
|
10610
|
+
for (const r of rows) {
|
|
10611
|
+
const p = r.predicate;
|
|
10612
|
+
if (!synonymPreds.has(p) && !relatedPreds.has(p)) continue;
|
|
10613
|
+
const s = normFactTerm(r.subject), o = normFactTerm(r.object);
|
|
10614
|
+
if (!s || !o) continue;
|
|
10615
|
+
ensure(s);
|
|
10616
|
+
ensure(o);
|
|
10617
|
+
if (synonymPreds.has(p)) union(s, o);
|
|
10618
|
+
else relatedRaw.push({ s, o });
|
|
10619
|
+
}
|
|
10620
|
+
const iriFor = (rep) => conceptBase + rep.replace(/ /g, "_");
|
|
10621
|
+
const componentTerms = /* @__PURE__ */ new Map();
|
|
10622
|
+
for (const t of parent.keys()) {
|
|
10623
|
+
const rep = find(t);
|
|
10624
|
+
if (!componentTerms.has(rep)) componentTerms.set(rep, /* @__PURE__ */ new Set());
|
|
10625
|
+
componentTerms.get(rep).add(t);
|
|
10626
|
+
}
|
|
10627
|
+
const concepts = [];
|
|
10628
|
+
for (const [rep, terms] of componentTerms) {
|
|
10629
|
+
const sorted = [...terms].sort();
|
|
10630
|
+
concepts.push({ id: iriFor(rep), prefLabel: rep, altLabels: sorted.filter((t) => t !== rep) });
|
|
10631
|
+
}
|
|
10632
|
+
concepts.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
10633
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10634
|
+
const related = [];
|
|
10635
|
+
for (const { s, o } of relatedRaw) {
|
|
10636
|
+
const cs = iriFor(find(s)), co = iriFor(find(o));
|
|
10637
|
+
if (cs === co) continue;
|
|
10638
|
+
const key = cs < co ? `${cs}\0${co}` : `${co}\0${cs}`;
|
|
10639
|
+
if (seen.has(key)) continue;
|
|
10640
|
+
seen.add(key);
|
|
10641
|
+
related.push({ subject: cs, object: co });
|
|
10642
|
+
}
|
|
10643
|
+
related.sort((a, b) => `${a.subject}${a.object}` < `${b.subject}${b.object}` ? -1 : 1);
|
|
10644
|
+
const conceptIdForTerm = (term) => {
|
|
10645
|
+
const t = normFactTerm(term);
|
|
10646
|
+
return parent.has(t) ? iriFor(find(t)) : null;
|
|
10647
|
+
};
|
|
10648
|
+
return { concepts, related, conceptIdForTerm, namespace: SKOS_NS };
|
|
10649
|
+
}
|
|
10650
|
+
function relatedForTerm(rows, term, options = {}) {
|
|
10651
|
+
const view = buildSkosConceptView(rows, options);
|
|
10652
|
+
const conceptId = view.conceptIdForTerm(term);
|
|
10653
|
+
if (!conceptId) return null;
|
|
10654
|
+
const byId = new Map(view.concepts.map((c) => [c.id, c]));
|
|
10655
|
+
const concept = byId.get(conceptId);
|
|
10656
|
+
const queried = normFactTerm(term);
|
|
10657
|
+
const synonyms = [concept.prefLabel, ...concept.altLabels].filter((label) => label !== queried);
|
|
10658
|
+
const related = view.related.filter((r) => r.subject === conceptId || r.object === conceptId).map((r) => byId.get(r.subject === conceptId ? r.object : r.subject)).filter(Boolean);
|
|
10659
|
+
return { conceptId, prefLabel: concept.prefLabel, altLabels: concept.altLabels, synonyms, related };
|
|
10660
|
+
}
|
|
10661
|
+
var SKOS_NS, DEFAULT_RELATION_MAP;
|
|
10662
|
+
var init_skos_view = __esm({
|
|
10663
|
+
"src/domain/skos-view.mjs"() {
|
|
10664
|
+
init_hash();
|
|
10665
|
+
SKOS_NS = "http://www.w3.org/2004/02/skos/core#";
|
|
10666
|
+
DEFAULT_RELATION_MAP = {
|
|
10667
|
+
synonym: ["mgx:synonym"],
|
|
10668
|
+
related: ["mgx:relatedTo", "mgx:similarTo"]
|
|
10669
|
+
};
|
|
10670
|
+
}
|
|
10671
|
+
});
|
|
10672
|
+
|
|
10583
10673
|
// adapter-stub-ask-nlp.mjs:../adapters/ask-nlp.mjs
|
|
10584
10674
|
var nlpAdapter;
|
|
10585
10675
|
var init_ask_nlp = __esm({
|
|
@@ -22968,91 +23058,7 @@ ${hint}` : ""}${cand}`;
|
|
|
22968
23058
|
|
|
22969
23059
|
// src/tools/handlers/tmct-related.mjs
|
|
22970
23060
|
init_config();
|
|
22971
|
-
|
|
22972
|
-
// src/domain/skos-view.mjs
|
|
22973
|
-
init_hash();
|
|
22974
|
-
var SKOS_NS = "http://www.w3.org/2004/02/skos/core#";
|
|
22975
|
-
var DEFAULT_RELATION_MAP = {
|
|
22976
|
-
synonym: ["mgx:synonym"],
|
|
22977
|
-
related: ["mgx:relatedTo", "mgx:similarTo"]
|
|
22978
|
-
};
|
|
22979
|
-
function buildSkosConceptView(rows, { conceptBase = "concept:", relationMap = DEFAULT_RELATION_MAP } = {}) {
|
|
22980
|
-
const synonymPreds = new Set(relationMap.synonym || []);
|
|
22981
|
-
const relatedPreds = new Set(relationMap.related || []);
|
|
22982
|
-
const parent = /* @__PURE__ */ new Map();
|
|
22983
|
-
const ensure = (t) => {
|
|
22984
|
-
if (!parent.has(t)) parent.set(t, t);
|
|
22985
|
-
};
|
|
22986
|
-
const find = (x) => {
|
|
22987
|
-
let r = x;
|
|
22988
|
-
while (parent.get(r) !== r) r = parent.get(r);
|
|
22989
|
-
while (parent.get(x) !== r) {
|
|
22990
|
-
const next = parent.get(x);
|
|
22991
|
-
parent.set(x, r);
|
|
22992
|
-
x = next;
|
|
22993
|
-
}
|
|
22994
|
-
return r;
|
|
22995
|
-
};
|
|
22996
|
-
const union = (a, b) => {
|
|
22997
|
-
const ra = find(a), rb = find(b);
|
|
22998
|
-
if (ra === rb) return;
|
|
22999
|
-
if (ra < rb) parent.set(rb, ra);
|
|
23000
|
-
else parent.set(ra, rb);
|
|
23001
|
-
};
|
|
23002
|
-
const relatedRaw = [];
|
|
23003
|
-
for (const r of rows) {
|
|
23004
|
-
const p = r.predicate;
|
|
23005
|
-
if (!synonymPreds.has(p) && !relatedPreds.has(p)) continue;
|
|
23006
|
-
const s = normFactTerm(r.subject), o = normFactTerm(r.object);
|
|
23007
|
-
if (!s || !o) continue;
|
|
23008
|
-
ensure(s);
|
|
23009
|
-
ensure(o);
|
|
23010
|
-
if (synonymPreds.has(p)) union(s, o);
|
|
23011
|
-
else relatedRaw.push({ s, o });
|
|
23012
|
-
}
|
|
23013
|
-
const iriFor = (rep) => conceptBase + rep.replace(/ /g, "_");
|
|
23014
|
-
const componentTerms = /* @__PURE__ */ new Map();
|
|
23015
|
-
for (const t of parent.keys()) {
|
|
23016
|
-
const rep = find(t);
|
|
23017
|
-
if (!componentTerms.has(rep)) componentTerms.set(rep, /* @__PURE__ */ new Set());
|
|
23018
|
-
componentTerms.get(rep).add(t);
|
|
23019
|
-
}
|
|
23020
|
-
const concepts = [];
|
|
23021
|
-
for (const [rep, terms] of componentTerms) {
|
|
23022
|
-
const sorted = [...terms].sort();
|
|
23023
|
-
concepts.push({ id: iriFor(rep), prefLabel: rep, altLabels: sorted.filter((t) => t !== rep) });
|
|
23024
|
-
}
|
|
23025
|
-
concepts.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
23026
|
-
const seen = /* @__PURE__ */ new Set();
|
|
23027
|
-
const related = [];
|
|
23028
|
-
for (const { s, o } of relatedRaw) {
|
|
23029
|
-
const cs = iriFor(find(s)), co = iriFor(find(o));
|
|
23030
|
-
if (cs === co) continue;
|
|
23031
|
-
const key = cs < co ? `${cs}\0${co}` : `${co}\0${cs}`;
|
|
23032
|
-
if (seen.has(key)) continue;
|
|
23033
|
-
seen.add(key);
|
|
23034
|
-
related.push({ subject: cs, object: co });
|
|
23035
|
-
}
|
|
23036
|
-
related.sort((a, b) => `${a.subject}${a.object}` < `${b.subject}${b.object}` ? -1 : 1);
|
|
23037
|
-
const conceptIdForTerm = (term) => {
|
|
23038
|
-
const t = normFactTerm(term);
|
|
23039
|
-
return parent.has(t) ? iriFor(find(t)) : null;
|
|
23040
|
-
};
|
|
23041
|
-
return { concepts, related, conceptIdForTerm, namespace: SKOS_NS };
|
|
23042
|
-
}
|
|
23043
|
-
function relatedForTerm(rows, term, options = {}) {
|
|
23044
|
-
const view = buildSkosConceptView(rows, options);
|
|
23045
|
-
const conceptId = view.conceptIdForTerm(term);
|
|
23046
|
-
if (!conceptId) return null;
|
|
23047
|
-
const byId = new Map(view.concepts.map((c) => [c.id, c]));
|
|
23048
|
-
const concept = byId.get(conceptId);
|
|
23049
|
-
const queried = normFactTerm(term);
|
|
23050
|
-
const synonyms = [concept.prefLabel, ...concept.altLabels].filter((label) => label !== queried);
|
|
23051
|
-
const related = view.related.filter((r) => r.subject === conceptId || r.object === conceptId).map((r) => byId.get(r.subject === conceptId ? r.object : r.subject)).filter(Boolean);
|
|
23052
|
-
return { conceptId, prefLabel: concept.prefLabel, altLabels: concept.altLabels, synonyms, related };
|
|
23053
|
-
}
|
|
23054
|
-
|
|
23055
|
-
// src/tools/handlers/tmct-related.mjs
|
|
23061
|
+
init_skos_view();
|
|
23056
23062
|
init_memory_fallthrough();
|
|
23057
23063
|
async function tmct_related(args, { config }) {
|
|
23058
23064
|
const term = requiredArg(args, "term");
|
|
@@ -23396,6 +23402,9 @@ ${JSON.stringify(envelope, null, 2)}`;
|
|
|
23396
23402
|
"game-inform": "inform"
|
|
23397
23403
|
});
|
|
23398
23404
|
|
|
23405
|
+
// src/services/chat.mjs
|
|
23406
|
+
init_skos_view();
|
|
23407
|
+
|
|
23399
23408
|
// src/domain/worlds-pack.mjs
|
|
23400
23409
|
var WORLD_NAME_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
23401
23410
|
var WORLD_RULE_KINDS = Object.freeze([
|
|
@@ -23509,6 +23518,35 @@ ${JSON.stringify(envelope, null, 2)}`;
|
|
|
23509
23518
|
init_core();
|
|
23510
23519
|
init_completions();
|
|
23511
23520
|
|
|
23521
|
+
// src/domain/spider-fly-world.mjs
|
|
23522
|
+
var WEB_HOME = Object.freeze({ x: 2, y: 2 });
|
|
23523
|
+
var DIRECTION_DELTA = Object.freeze({
|
|
23524
|
+
north: Object.freeze({ dx: 0, dy: -1 }),
|
|
23525
|
+
south: Object.freeze({ dx: 0, dy: 1 }),
|
|
23526
|
+
east: Object.freeze({ dx: 1, dy: 0 }),
|
|
23527
|
+
west: Object.freeze({ dx: -1, dy: 0 })
|
|
23528
|
+
});
|
|
23529
|
+
var SEED_TAXONOMY = Object.freeze([
|
|
23530
|
+
Object.freeze(["poodle", "dog"]),
|
|
23531
|
+
Object.freeze(["sheepdog", "dog"]),
|
|
23532
|
+
Object.freeze(["dog", "animal"]),
|
|
23533
|
+
Object.freeze(["spider", "arachnid"]),
|
|
23534
|
+
Object.freeze(["arachnid", "animal"]),
|
|
23535
|
+
Object.freeze(["fly", "insect"]),
|
|
23536
|
+
Object.freeze(["insect", "animal"])
|
|
23537
|
+
]);
|
|
23538
|
+
|
|
23539
|
+
// src/services/spider-fly.mjs
|
|
23540
|
+
init_planning();
|
|
23541
|
+
init_core();
|
|
23542
|
+
|
|
23543
|
+
// src/services/spider-fly-turn.mjs
|
|
23544
|
+
init_core();
|
|
23545
|
+
var SPIDER_FLY_TOLD_RE = new RegExp(
|
|
23546
|
+
"^@(spider|fly)(?:-(\\d+))?[,:]?\\s+the\\s+(spider|fly)(?:-(\\d+))?\\s+is\\s+(?:(north|south|east|west)|at\\s+(cell-\\d+-\\d+))[.!?\\s]*$",
|
|
23547
|
+
"i"
|
|
23548
|
+
);
|
|
23549
|
+
|
|
23512
23550
|
// src/services/chat-session.mjs
|
|
23513
23551
|
init_node_path();
|
|
23514
23552
|
init_node_fs();
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// spider-fly-browser-entry.mjs — the esbuild entry for the spider-and-fly
|
|
2
|
+
// full-screen/home-page pages (public/spider-fly-browser.bundle.js, built by
|
|
3
|
+
// scripts/build-spider-fly-bundle.mjs).
|
|
4
|
+
//
|
|
5
|
+
// Exposes ONE session factory over the real engine, `createSpiderFlySession`,
|
|
6
|
+
// mirroring chat-browser-entry.mjs's `createChatSession` shape exactly (same
|
|
7
|
+
// underlying runTurn, same minus-every-filesystem-side-effect posture — no
|
|
8
|
+
// transcript log, no sidecar, no graph upsert) with two additions specific to
|
|
9
|
+
// this game's rendering needs:
|
|
10
|
+
//
|
|
11
|
+
// - `session.tick()` runs ONE real engine turn directly
|
|
12
|
+
// (spider-fly.mjs's runSpiderFlyTick), unmediated by chat text, so the
|
|
13
|
+
// page's own ticker gets back the structured `{ turn, agents, ecology }`
|
|
14
|
+
// shape it needs to redraw the board and the HUD's goal lines. This is
|
|
15
|
+
// the "page's ticker calls a turn" half of the brief.
|
|
16
|
+
// - `session.turn(line)` runs the FULL chat turn engine (chat.mjs's
|
|
17
|
+
// runTurn — the exact dispatch the CLI and the home page's own chat run,
|
|
18
|
+
// with the spider-fly lane already wired in), so the in-page chat dock
|
|
19
|
+
// supports the addressed teach-frame ("@spider the fly is east"), the
|
|
20
|
+
// bare "tick" command, and any ordinary fallthrough question ("where is
|
|
21
|
+
// the spider") exactly as the CLI does. This is the "chat dock runs
|
|
22
|
+
// runTurn" half.
|
|
23
|
+
// - `session.snapshot()` is a READ-ONLY fold (spider-fly.mjs's own
|
|
24
|
+
// foldSpiderFlyState, no engine advance) so the page can resync agent
|
|
25
|
+
// positions after a CHAT-driven tick/address turn (whose reply is text,
|
|
26
|
+
// not the structured tick() shape) without double-advancing the turn.
|
|
27
|
+
// It carries no `.goal` — the chat reply text itself already narrates
|
|
28
|
+
// that turn's outcome; only a raw tick() refreshes the HUD's goal lines.
|
|
29
|
+
//
|
|
30
|
+
// tick(), turn() and snapshot() all read/write the SAME in-memory store
|
|
31
|
+
// (`memoryDir`), so the board and the chat dock never disagree about the
|
|
32
|
+
// game's state. A caller that lets a play button and a chat submit fire
|
|
33
|
+
// concurrently must serialize its own calls against this session — this
|
|
34
|
+
// module runs each call to completion but does not queue overlapping ones
|
|
35
|
+
// itself (see spider-fly-viz.mjs's own inlined `withLock` wrapper).
|
|
36
|
+
//
|
|
37
|
+
// The world bootstrap never touches the worlds-pack fetch/provider machinery
|
|
38
|
+
// spider-fly-turn.mjs's openSpiderFlyGame uses (that path needs a Node fs
|
|
39
|
+
// read or a registered fetch provider, neither of which this bundle carries):
|
|
40
|
+
// spider-fly-world.mjs's worldFactRows()/startSpiderFlyGame are already pure/
|
|
41
|
+
// in-memory, so the browser bootstraps the identical board directly from
|
|
42
|
+
// them. The world's rule rows (worldRuleRows) are skipped on purpose —
|
|
43
|
+
// spider-fly.mjs's own header comment confirms grid movement never reads
|
|
44
|
+
// them back (hand-written pathfinding over has-exit-* facts, not the taught
|
|
45
|
+
// action-rule DSL), so nothing here depends on them being loaded.
|
|
46
|
+
import { runTurn } from "../../services/chat.mjs";
|
|
47
|
+
import {
|
|
48
|
+
createInMemoryStore, normFactTerm, appendFacts, loadMemory, readFactRows,
|
|
49
|
+
} from "../../adapters/memory/core.mjs";
|
|
50
|
+
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
51
|
+
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
52
|
+
import {
|
|
53
|
+
worldFactRows, WORLD_NAME, WORLD_OPENING, cellId, parseCellId, DIRECTION_DELTA, visibleCells,
|
|
54
|
+
} from "../../domain/spider-fly-world.mjs";
|
|
55
|
+
import { foldSpiderFlyState, runSpiderFlyTick, startSpiderFlyGame, DEFAULT_VISION_RADIUS } from "../../services/spider-fly.mjs";
|
|
56
|
+
import { resolveSpriteForClass, SPRITE_REGISTRY } from "../../domain/sprite-map.mjs";
|
|
57
|
+
|
|
58
|
+
/** A live in-memory game the page's ticker and chat dock can both drive.
|
|
59
|
+
* Returns { memoryDir, sessionId, opening, initial, taxonomyRows, tick,
|
|
60
|
+
* turn, snapshot }. `initial` is the freshly-bootstrapped board's starting
|
|
61
|
+
* agents ({ [id]: { cell } }, turn 0, no goal computed yet — the CLI's own
|
|
62
|
+
* opener shows the same static starting board before any real tick runs).
|
|
63
|
+
* `taxonomyRows` is the world's static rdfs:subClassOf rows, for
|
|
64
|
+
* resolveSpriteForClass — immutable for the life of the session, so it is
|
|
65
|
+
* computed once here rather than re-read from memory on every render. */
|
|
66
|
+
export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
67
|
+
const memoryDir = createInMemoryStore();
|
|
68
|
+
const tag = `world:${WORLD_NAME}`;
|
|
69
|
+
const worldRows = [...worldFactRows()];
|
|
70
|
+
await appendFacts(memoryDir, worldRows.map((f) => ({
|
|
71
|
+
subject: f.subject, predicate: f.predicate, object: f.object, provenance: tag,
|
|
72
|
+
})));
|
|
73
|
+
const taxonomyRows = worldRows
|
|
74
|
+
.filter((f) => f.predicate === "rdfs:subClassOf")
|
|
75
|
+
.map((f) => ({ subject: f.subject, predicate: f.predicate, object: f.object }));
|
|
76
|
+
|
|
77
|
+
const { facts: startFacts } = await startSpiderFlyGame(memoryDir, { flyCount });
|
|
78
|
+
const initialAgents = {};
|
|
79
|
+
for (const f of startFacts) {
|
|
80
|
+
if (f.predicate !== "mgx:currently-in") continue;
|
|
81
|
+
initialAgents[f.subject] = { cell: f.object };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const graph = parseEntities({ individuals: [], objectProperties: [] });
|
|
85
|
+
const lexicon = loadLexicon();
|
|
86
|
+
const sessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
87
|
+
|
|
88
|
+
let focus = null;
|
|
89
|
+
let last = null;
|
|
90
|
+
let planState = { spiderFly: { turn: 0 } };
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
memoryDir,
|
|
94
|
+
sessionId,
|
|
95
|
+
opening: WORLD_OPENING,
|
|
96
|
+
initial: { turn: 0, agents: initialAgents },
|
|
97
|
+
taxonomyRows,
|
|
98
|
+
|
|
99
|
+
/** Run one real engine turn directly. Returns spider-fly.mjs's own
|
|
100
|
+
* { turn, agents, ecology } shape unmodified. */
|
|
101
|
+
async tick() {
|
|
102
|
+
const result = await runSpiderFlyTick(memoryDir);
|
|
103
|
+
planState = { spiderFly: { turn: result.turn } };
|
|
104
|
+
return result;
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
/** One dispatched chat turn — the SAME runTurn the CLI and the home
|
|
108
|
+
* page's own chat run, over this session's own memoryDir. A throwing
|
|
109
|
+
* runTurn must never kill the session — the page has no other chance
|
|
110
|
+
* to show this turn's answer. */
|
|
111
|
+
async turn(line) {
|
|
112
|
+
let result;
|
|
113
|
+
try {
|
|
114
|
+
result = await runTurn(line, {
|
|
115
|
+
config: null, source: null, graph, focus, last, memoryDir, sessionId,
|
|
116
|
+
env: {}, lexicon, vocabHint: "", planState,
|
|
117
|
+
});
|
|
118
|
+
} catch (e) {
|
|
119
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
120
|
+
return { answer: `Something went wrong answering that (${message}). Try rephrasing, or /help.`, end: false, record: null, plan: null };
|
|
121
|
+
}
|
|
122
|
+
focus = result.focus;
|
|
123
|
+
last = result.last;
|
|
124
|
+
if ("planState" in result) planState = result.planState;
|
|
125
|
+
return { answer: result.answer, end: Boolean(result.end), record: result.record ?? null, plan: result.plan ?? null };
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
/** A read-only fold of the CURRENT board — no engine advance, no goal
|
|
129
|
+
* lines (see the header comment: only a raw tick() recomputes those).
|
|
130
|
+
* Lets the page resync positions/turn count after a chat-driven tick. */
|
|
131
|
+
async snapshot() {
|
|
132
|
+
const rows = readFactRows(await loadMemory(memoryDir));
|
|
133
|
+
const state = foldSpiderFlyState(rows);
|
|
134
|
+
const agents = {};
|
|
135
|
+
for (const [id, place] of state.placements) {
|
|
136
|
+
if (state.removed.has(id)) continue;
|
|
137
|
+
agents[id] = { cell: place.cell };
|
|
138
|
+
}
|
|
139
|
+
return { turn: state.turnCount, agents };
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// cellId/parseCellId/DIRECTION_DELTA/visibleCells/DEFAULT_VISION_RADIUS are
|
|
145
|
+
// re-exported so the page's own rendering script (spider-fly-viz.mjs) never
|
|
146
|
+
// has to duplicate grid geometry or the vision-radius default: reconstructing
|
|
147
|
+
// a spider's remaining silk-thread path from its returned direction list, and
|
|
148
|
+
// computing the POV overlay's visible-cell mask, both need them.
|
|
149
|
+
globalThis.tmctSpiderFly = {
|
|
150
|
+
createSpiderFlySession, normFactTerm, resolveSpriteForClass, SPRITE_REGISTRY,
|
|
151
|
+
cellId, parseCellId, DIRECTION_DELTA, visibleCells, DEFAULT_VISION_RADIUS,
|
|
152
|
+
};
|