@polycode-projects/the-mechanical-code-talker 2.8.1 → 2.8.4
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/domain/game-config.mjs +10 -4
- package/src/domain/hanoi-lesson.mjs +53 -0
- package/src/domain/spider-fly-world.mjs +16 -0
- package/src/services/adventure-editor.mjs +361 -0
- package/src/services/adventure-viz.mjs +422 -51
- package/src/services/ledger-viz.mjs +239 -3
- package/src/services/plan-pddl.mjs +245 -0
- package/src/services/plan-viz.mjs +324 -67
- package/src/services/spider-fly-turn.mjs +120 -3
- package/src/services/spider-fly-viz.mjs +341 -22
- package/src/services/spider-fly.mjs +337 -143
- package/src/surfaces/web/adventure-browser-entry.mjs +34 -3
- package/src/surfaces/web/memory-ask-browser.bundle.js +10 -4
- package/src/surfaces/web/plan-browser-entry.mjs +114 -0
- package/src/surfaces/web/spider-fly-browser-entry.mjs +33 -1
|
@@ -31,14 +31,16 @@
|
|
|
31
31
|
// like a real CLI session.
|
|
32
32
|
import { runTurn } from "../../services/chat.mjs";
|
|
33
33
|
import {
|
|
34
|
-
createInMemoryStore, appendFacts, appendRule, loadMemory, readFactRows,
|
|
34
|
+
createInMemoryStore, appendFacts, appendRule, loadMemory, readFactRows, removeFacts,
|
|
35
35
|
} from "../../adapters/memory/core.mjs";
|
|
36
36
|
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
37
37
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
38
38
|
import { foldWorldState, worldDigestRows, roomAffordances } from "../../services/adventure.mjs";
|
|
39
39
|
import { runAdventureAutoplayTick, exposedFacts } from "../../services/adventure-autoplay.mjs";
|
|
40
|
-
import {
|
|
40
|
+
import { parseWorldEditorText, planWorldEditorSync } from "../../services/adventure-editor.mjs";
|
|
41
|
+
import { resolveSpriteForClass, SPRITE_REGISTRY, classAncestorChain } from "../../domain/sprite-map.mjs";
|
|
41
42
|
import { resolveSpriteAsset } from "../../domain/sprite-templates.mjs";
|
|
43
|
+
import { relatedForTerm } from "../../domain/skos-view.mjs";
|
|
42
44
|
|
|
43
45
|
/** A live in-memory adventure this page's ticker AND chat dock can both
|
|
44
46
|
* drive. Returns `{ memoryDir, autoplayTick, turn, snapshot }`.
|
|
@@ -137,6 +139,30 @@ export async function createAdventureSession(worldPayload) {
|
|
|
137
139
|
const here = state.placements.get("player")?.object ?? null;
|
|
138
140
|
return { rows, state, here, turn: state.turnCount, visitedRoomIds: [...visitedRoomIds] };
|
|
139
141
|
},
|
|
142
|
+
|
|
143
|
+
/** The world editor's own store sync: parse `text` (adventure-editor.mjs's
|
|
144
|
+
* own parseWorldEditorText), plan the implied writes (planWorldEditorSync),
|
|
145
|
+
* and apply them — scoped to THIS world's own provenance tag only, never
|
|
146
|
+
* the default persona's background corpus that shares the same live
|
|
147
|
+
* memory store (an unscoped diff would read every unrelated background
|
|
148
|
+
* fact as "not in this text" and try to retract it). Retractions
|
|
149
|
+
* (removeFacts) only ever run when the WHOLE document parsed cleanly —
|
|
150
|
+
* see adventure-editor.mjs's own header for why a typo must never be
|
|
151
|
+
* read as "this fact is gone". Returns `{ unrecognized, added, removed }`. */
|
|
152
|
+
async applyEdit(text) {
|
|
153
|
+
const allRows = readFactRows(await loadMemory(memoryDir));
|
|
154
|
+
const worldRows = allRows.filter((r) => typeof r.provenance === "string" && r.provenance.indexOf(tag) === 0);
|
|
155
|
+
const state = foldWorldState(worldRows);
|
|
156
|
+
const { triples, unrecognized } = parseWorldEditorText(text, worldRows);
|
|
157
|
+
const { toAppend, toRemoveIds } = planWorldEditorSync(worldRows, state, triples);
|
|
158
|
+
if (toAppend.length) {
|
|
159
|
+
await appendFacts(memoryDir, toAppend.map((f) => ({ subject: f.subject, predicate: f.predicate, object: f.object, provenance: tag })));
|
|
160
|
+
}
|
|
161
|
+
const removedCount = unrecognized.length === 0 && toRemoveIds.length ? (await removeFacts(memoryDir, toRemoveIds)).removed.length : 0;
|
|
162
|
+
const here = foldWorldState(readFactRows(await loadMemory(memoryDir))).placements.get("player")?.object ?? null;
|
|
163
|
+
if (here) visitedRoomIds.add(here);
|
|
164
|
+
return { unrecognized, added: toAppend.length, removed: removedCount };
|
|
165
|
+
},
|
|
140
166
|
};
|
|
141
167
|
}
|
|
142
168
|
|
|
@@ -145,8 +171,13 @@ export async function createAdventureSession(worldPayload) {
|
|
|
145
171
|
// affordances the chat dock's own pills read from, or (foldWorldState,
|
|
146
172
|
// exposedFacts) the exposure-filtered fold the goal-status panel mirrors —
|
|
147
173
|
// the same posture spider-fly-browser-entry.mjs's own
|
|
148
|
-
// globalThis.tmctSpiderFly re-export takes.
|
|
174
|
+
// globalThis.tmctSpiderFly re-export takes. `relatedForTerm`/
|
|
175
|
+
// `classAncestorChain` back the edit mode's own cursor-suggestion pills
|
|
176
|
+
// (adventure-viz.mjs's suggestionsForTerm mirrors this same pairing against
|
|
177
|
+
// the global, the same reach-through-the-global pattern captionFor/pillsFor
|
|
178
|
+
// already use for their own adventure.mjs calls).
|
|
149
179
|
globalThis.tmctAdventure = {
|
|
150
180
|
createAdventureSession, resolveSpriteForClass, SPRITE_REGISTRY, resolveSpriteAsset,
|
|
151
181
|
worldDigestRows, roomAffordances, foldWorldState, exposedFacts,
|
|
182
|
+
relatedForTerm, classAncestorChain,
|
|
152
183
|
};
|
|
@@ -23594,10 +23594,13 @@ ${JSON.stringify(envelope, null, 2)}`;
|
|
|
23594
23594
|
spiderMassDecrementPerTurn: 0.5,
|
|
23595
23595
|
flyInitialMass: 10,
|
|
23596
23596
|
flyMassDecrementPerTurn: 1,
|
|
23597
|
-
|
|
23597
|
+
spiderVisionRadius: 4,
|
|
23598
|
+
flyVisionRadius: 4,
|
|
23598
23599
|
eggHatchDelayTurns: 3,
|
|
23599
23600
|
flySpawnIntervalTurns: 3,
|
|
23600
|
-
|
|
23601
|
+
eggLayMassThreshold: 25,
|
|
23602
|
+
eggHatchCount: 2,
|
|
23603
|
+
minHatchlingMass: 3,
|
|
23601
23604
|
webDurationTurns: 10
|
|
23602
23605
|
}),
|
|
23603
23606
|
guessNumber: Object.freeze({
|
|
@@ -23614,10 +23617,13 @@ ${JSON.stringify(envelope, null, 2)}`;
|
|
|
23614
23617
|
spider_mass_decrement_per_turn: "spiderMassDecrementPerTurn",
|
|
23615
23618
|
fly_initial_mass: "flyInitialMass",
|
|
23616
23619
|
fly_mass_decrement_per_turn: "flyMassDecrementPerTurn",
|
|
23617
|
-
|
|
23620
|
+
spider_vision_radius: "spiderVisionRadius",
|
|
23621
|
+
fly_vision_radius: "flyVisionRadius",
|
|
23618
23622
|
egg_hatch_delay_turns: "eggHatchDelayTurns",
|
|
23619
23623
|
fly_spawn_interval_turns: "flySpawnIntervalTurns",
|
|
23620
|
-
|
|
23624
|
+
egg_lay_mass_threshold: "eggLayMassThreshold",
|
|
23625
|
+
egg_hatch_count: "eggHatchCount",
|
|
23626
|
+
min_hatchling_mass: "minHatchlingMass",
|
|
23621
23627
|
web_duration_turns: "webDurationTurns"
|
|
23622
23628
|
});
|
|
23623
23629
|
var GUESS_NUMBER_KEY_MAP = Object.freeze({
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// plan-browser-entry.mjs — the esbuild entry for the "it plans, and shows
|
|
2
|
+
// the work" page's live session (public/plan-browser.bundle.js, built by
|
|
3
|
+
// scripts/build-plan-bundle.mjs), mirroring spider-fly-browser-entry.mjs's
|
|
4
|
+
// and adventure-browser-entry.mjs's own session-factory shape.
|
|
5
|
+
//
|
|
6
|
+
// Unlike spider-fly's board or Ashcombe Hall's world, hanoi has no
|
|
7
|
+
// structured fact/rule corpus to bootstrap from — its canonical definition
|
|
8
|
+
// (data/games/hanoi-3.txt) IS taught English, one sentence per teach frame.
|
|
9
|
+
// So this session seeds itself the same way `tmct import --file` teaches
|
|
10
|
+
// that file (src/services/import-file.mjs): every sentence
|
|
11
|
+
// hanoi-lesson.mjs's hanoiLessonSentences() generates runs as its own
|
|
12
|
+
// `turn()`, over the exact same runTurn the CLI and every other viz page's
|
|
13
|
+
// chat dock run — not raw appendFacts/appendRule, since there is no
|
|
14
|
+
// structured fact list to append here, only taught English.
|
|
15
|
+
//
|
|
16
|
+
// createPlanSession({ diskCount, maxDepth }) teaches a fresh N-disk puzzle
|
|
17
|
+
// and solves it once (mirroring the "disk-1 rests on disk-2. … the goal is
|
|
18
|
+
// that every disk rests on peg-c. solve it." prompt scripts/build-demo-
|
|
19
|
+
// site.mjs used to shell out to the CLI for), returning `{ plan, turn, ... }`
|
|
20
|
+
// — `plan` is the freshly solved plan (or null on an honest miss, e.g. a
|
|
21
|
+
// max-depth too low to find one), `turn(line, { maxDepth })` is the SAME
|
|
22
|
+
// chat-dock entry point adventure/spider-fly expose, so a visitor's typed
|
|
23
|
+
// fact and a visitor's typed "solve it" both dispatch through the real
|
|
24
|
+
// engine. `maxDepth` is overridable PER CALL (not just at session creation)
|
|
25
|
+
// so the page's own max-search-depth control can re-run "solve it" on the
|
|
26
|
+
// CURRENT board without tearing down and re-teaching the whole puzzle.
|
|
27
|
+
import { runTurn } from "../../services/chat.mjs";
|
|
28
|
+
import { createInMemoryStore } from "../../adapters/memory/core.mjs";
|
|
29
|
+
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
30
|
+
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
31
|
+
import { DEFAULT_GAME_CONFIG } from "../../domain/game-config.mjs";
|
|
32
|
+
import { hanoiLessonSentences } from "../../domain/hanoi-lesson.mjs";
|
|
33
|
+
import { computeBlocksLayout, planToPageData, renderInputsFromPlan } from "../../services/plan-viz.mjs";
|
|
34
|
+
import { planToPddl } from "../../services/plan-pddl.mjs";
|
|
35
|
+
// Re-exported so the page can register a CDN-loaded wink-nlp pair before the
|
|
36
|
+
// first teach, the same seam chat-browser-entry.mjs exposes as
|
|
37
|
+
// tmctChat.registerWinkModel — see wink-model.mjs's own header. The hanoi
|
|
38
|
+
// lesson's own "moving a disk onto a target makes the disk rest on the
|
|
39
|
+
// target" sentence needs a REAL lemmatiser (verbLemma reduces "moving" to
|
|
40
|
+
// "move" to match the taught "move onto" action family) — without it, that
|
|
41
|
+
// one sentence honestly declines ("the lemmatizer isn't available"), the
|
|
42
|
+
// action rule never gets its effect, and every later locative fact for the
|
|
43
|
+
// puzzle fails to teach in turn. spider-fly/adventure never register a wink
|
|
44
|
+
// model because their own gameplay never asks a taught rule to reduce a
|
|
45
|
+
// verb; the hanoi lesson is the first live session here that does.
|
|
46
|
+
import { registerWinkModel } from "../../adapters/wink-model.mjs";
|
|
47
|
+
|
|
48
|
+
/** A live in-memory towers-of-hanoi session this page's live controls AND
|
|
49
|
+
* chat dock can both drive. Returns `{ memoryDir, sessionId, diskCount,
|
|
50
|
+
* maxDepth, plan, turn }`. `plan` is the puzzle's freshly solved plan (the
|
|
51
|
+
* same shape chat.mjs's planLaneAnswer returns, enriched with
|
|
52
|
+
* `becauseText` — see `turn()` below), or null when `maxDepth` was too low
|
|
53
|
+
* to find one (an honest miss, not an error: `turn()`'s own answer text
|
|
54
|
+
* names it). */
|
|
55
|
+
export async function createPlanSession({ diskCount = 3, maxDepth = DEFAULT_GAME_CONFIG.planning.maxDepth } = {}) {
|
|
56
|
+
const memoryDir = createInMemoryStore();
|
|
57
|
+
const graph = parseEntities({ individuals: [], objectProperties: [] });
|
|
58
|
+
const lexicon = loadLexicon();
|
|
59
|
+
const sessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
60
|
+
const planHolder = { state: null };
|
|
61
|
+
let focus = null;
|
|
62
|
+
let last = null;
|
|
63
|
+
|
|
64
|
+
/** One dispatched chat turn — the SAME runTurn the CLI and every other
|
|
65
|
+
* viz page's own chat dock run, over this session's own memoryDir. A
|
|
66
|
+
* throwing runTurn must never kill the session — the page has no other
|
|
67
|
+
* chance to show this turn's answer. `maxDepth` overrides the session's
|
|
68
|
+
* own default for just this one call (the page's own max-search-depth
|
|
69
|
+
* control threads it on every call, including a plain typed "solve
|
|
70
|
+
* it"), so raising or lowering it never requires re-teaching the board.
|
|
71
|
+
* A returned `plan` carries `becauseText` folded in from the session's
|
|
72
|
+
* own plan slot — the plan-lane contract's returned object never carries
|
|
73
|
+
* it itself (only planHolder.state does), and the PDDL panel's own
|
|
74
|
+
* "because —" line needs it. */
|
|
75
|
+
async function turn(line, { maxDepth: maxDepthOverride } = {}) {
|
|
76
|
+
const gameConfig = {
|
|
77
|
+
...DEFAULT_GAME_CONFIG,
|
|
78
|
+
planning: { ...DEFAULT_GAME_CONFIG.planning, maxDepth: maxDepthOverride ?? maxDepth },
|
|
79
|
+
};
|
|
80
|
+
let result;
|
|
81
|
+
try {
|
|
82
|
+
result = await runTurn(line, {
|
|
83
|
+
config: null, source: null, graph, focus, last, memoryDir, sessionId,
|
|
84
|
+
env: {}, lexicon, vocabHint: "", planState: planHolder.state, gameConfig,
|
|
85
|
+
});
|
|
86
|
+
} catch (e) {
|
|
87
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
88
|
+
return { answer: `Something went wrong answering that (${message}). Try rephrasing, or /help.`, end: false, record: null, plan: null };
|
|
89
|
+
}
|
|
90
|
+
focus = result.focus;
|
|
91
|
+
last = result.last;
|
|
92
|
+
if ("planState" in result) planHolder.state = result.planState;
|
|
93
|
+
const plan = result.plan
|
|
94
|
+
? { ...result.plan, becauseText: planHolder.state?.becauseText ?? null }
|
|
95
|
+
: null;
|
|
96
|
+
return { answer: result.answer, end: Boolean(result.end), record: result.record ?? null, plan };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let plan = null;
|
|
100
|
+
for (const sentence of hanoiLessonSentences(diskCount)) {
|
|
101
|
+
const r = await turn(sentence);
|
|
102
|
+
if (r.plan) plan = r.plan;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { memoryDir, sessionId, diskCount, maxDepth, plan, turn };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Re-exported so the page's own rendering script (plan-viz.mjs's inlined
|
|
109
|
+
// script) never has to duplicate board layout or PDDL/OWL-RDF formatting —
|
|
110
|
+
// the same posture adventure-browser-entry.mjs/spider-fly-browser-entry.mjs
|
|
111
|
+
// take re-exporting their own engines' pure helpers.
|
|
112
|
+
globalThis.tmctPlan = {
|
|
113
|
+
createPlanSession, computeBlocksLayout, planToPageData, renderInputsFromPlan, planToPddl, registerWinkModel,
|
|
114
|
+
};
|
|
@@ -53,8 +53,10 @@ import {
|
|
|
53
53
|
worldFactRows, WORLD_NAME, WORLD_OPENING, cellId, parseCellId, DIRECTION_DELTA, visibleCells,
|
|
54
54
|
} from "../../domain/spider-fly-world.mjs";
|
|
55
55
|
import { foldSpiderFlyState, runSpiderFlyTick, startSpiderFlyGame, liveWebs, DEFAULT_VISION_RADIUS } from "../../services/spider-fly.mjs";
|
|
56
|
+
import { pillsForSpiderFly, oneStepDirectionBetween } from "../../services/spider-fly-turn.mjs";
|
|
56
57
|
import { resolveSpriteForClass, SPRITE_REGISTRY } from "../../domain/sprite-map.mjs";
|
|
57
58
|
import { resolveSpriteAsset } from "../../domain/sprite-templates.mjs";
|
|
59
|
+
import { DEFAULT_GAME_CONFIG } from "../../domain/game-config.mjs";
|
|
58
60
|
|
|
59
61
|
/** A live in-memory game the page's ticker and chat dock can both drive.
|
|
60
62
|
* Returns { memoryDir, sessionId, opening, initial, taxonomyRows, tick,
|
|
@@ -89,6 +91,14 @@ export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
|
89
91
|
let focus = null;
|
|
90
92
|
let last = null;
|
|
91
93
|
let planState = { spiderFly: { turn: 0 } };
|
|
94
|
+
// The live, in-page-slider-adjustable knobs (mass-loss-rate/spawn-rate/
|
|
95
|
+
// vision-radius per class, and every other spiderFly tunable) — starts at
|
|
96
|
+
// the shipped defaults, mutated only through setConfig() below, and
|
|
97
|
+
// forwarded into every future tick()/turn() call. Never rewinds a value
|
|
98
|
+
// that was already written to a past turn's facts (e.g. a spider already
|
|
99
|
+
// above the OLD lay threshold isn't retroactively un-laid) — a config
|
|
100
|
+
// change only changes what happens FROM HERE ON, same as tmct.toml would.
|
|
101
|
+
let config = { ...DEFAULT_GAME_CONFIG.spiderFly };
|
|
92
102
|
|
|
93
103
|
return {
|
|
94
104
|
memoryDir,
|
|
@@ -100,7 +110,7 @@ export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
|
100
110
|
/** Run one real engine turn directly. Returns spider-fly.mjs's own
|
|
101
111
|
* { turn, agents, ecology } shape unmodified. */
|
|
102
112
|
async tick() {
|
|
103
|
-
const result = await runSpiderFlyTick(memoryDir);
|
|
113
|
+
const result = await runSpiderFlyTick(memoryDir, { config });
|
|
104
114
|
planState = { spiderFly: { turn: result.turn } };
|
|
105
115
|
return result;
|
|
106
116
|
},
|
|
@@ -115,6 +125,7 @@ export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
|
115
125
|
result = await runTurn(line, {
|
|
116
126
|
config: null, source: null, graph, focus, last, memoryDir, sessionId,
|
|
117
127
|
env: {}, lexicon, vocabHint: "", planState,
|
|
128
|
+
gameConfig: { ...DEFAULT_GAME_CONFIG, spiderFly: config },
|
|
118
129
|
});
|
|
119
130
|
} catch (e) {
|
|
120
131
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -141,6 +152,23 @@ export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
|
141
152
|
}
|
|
142
153
|
return { turn: state.turnCount, agents, activeWebs: liveWebs(state.webs, state.turnCount) };
|
|
143
154
|
},
|
|
155
|
+
|
|
156
|
+
/** The live spiderFly config this session's future tick()/turn() calls
|
|
157
|
+
* will read — a plain copy, safe for a caller to inspect without
|
|
158
|
+
* risking a shared-reference mutation. */
|
|
159
|
+
getConfig() {
|
|
160
|
+
return { ...config };
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
/** Merge `partial` (any subset of DEFAULT_GAME_CONFIG.spiderFly's own
|
|
164
|
+
* keys, e.g. `{ spiderMassDecrementPerTurn: 0.2 }`) over the live
|
|
165
|
+
* config — the in-page sliders' own write path. Every unset sibling
|
|
166
|
+
* keeps its current value, mirroring resolveGameConfig's own
|
|
167
|
+
* toml-merge shape so a slider and tmct.toml can never disagree about
|
|
168
|
+
* what "partial" means. */
|
|
169
|
+
setConfig(partial) {
|
|
170
|
+
config = { ...config, ...partial };
|
|
171
|
+
},
|
|
144
172
|
};
|
|
145
173
|
}
|
|
146
174
|
|
|
@@ -149,7 +177,11 @@ export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
|
149
177
|
// has to duplicate grid geometry or the vision-radius default: reconstructing
|
|
150
178
|
// a spider's remaining silk-thread path from its returned direction list, and
|
|
151
179
|
// computing the POV overlay's visible-cell mask, both need them.
|
|
180
|
+
// pillsForSpiderFly/oneStepDirectionBetween are re-exported so the same page
|
|
181
|
+
// can build its own dynamic deception-pill container without duplicating
|
|
182
|
+
// spider-fly-turn.mjs's own pill logic.
|
|
152
183
|
globalThis.tmctSpiderFly = {
|
|
153
184
|
createSpiderFlySession, normFactTerm, resolveSpriteForClass, SPRITE_REGISTRY, resolveSpriteAsset,
|
|
154
185
|
cellId, parseCellId, DIRECTION_DELTA, visibleCells, DEFAULT_VISION_RADIUS,
|
|
186
|
+
pillsForSpiderFly, oneStepDirectionBetween, DEFAULT_GAME_CONFIG,
|
|
155
187
|
};
|