@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.
@@ -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 { resolveSpriteForClass, SPRITE_REGISTRY } from "../../domain/sprite-map.mjs";
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
- visionRadius: 4,
23597
+ spiderVisionRadius: 4,
23598
+ flyVisionRadius: 4,
23598
23599
  eggHatchDelayTurns: 3,
23599
23600
  flySpawnIntervalTurns: 3,
23600
- eggsEatenThreshold: 2,
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
- vision_radius: "visionRadius",
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
- eggs_eaten_threshold: "eggsEatenThreshold",
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
  };