@polycode-projects/the-mechanical-code-talker 2.10.5 → 2.11.1
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 +2 -2
- package/corpus/sprites/src/sprite-facts.jsonl +18 -0
- package/corpus/worlds/manifest.json +5 -5
- package/corpus/worlds/shards/ashcombe-hall.jsonl.gz +0 -0
- package/corpus/worlds/src/ashcombe-hall.jsonl +27 -0
- package/data/sprites/book-icon.toml +12 -0
- package/data/sprites/cellar-icon.toml +12 -0
- package/data/sprites/drawing-room-icon.toml +13 -0
- package/data/sprites/garden-icon.toml +12 -0
- package/data/sprites/kitchen-icon.toml +13 -0
- package/data/sprites/library-icon.toml +12 -0
- package/data/sprites/pan-icon.toml +11 -0
- package/data/sprites/study-icon.toml +12 -0
- package/data/templates/responses.jsonl +1 -0
- package/package.json +5 -2
- package/src/adapters/corpus/wikipedia-live.mjs +182 -26
- package/src/adapters/corpus/worlds-pack.mjs +8 -2
- package/src/adapters/toml-config.mjs +6 -0
- package/src/domain/ask-vocab.mjs +17 -0
- package/src/domain/ask.mjs +51 -1
- package/src/domain/grammar/ace.mjs +43 -3
- package/src/domain/interpret/normalize.mjs +6 -2
- package/src/domain/interpret/strategies/grammar.mjs +47 -17
- package/src/domain/interpret/strategies/keywords.mjs +53 -4
- package/src/domain/memory/trust.mjs +11 -0
- package/src/domain/router/registry.mjs +8 -1
- package/src/domain/worlds-pack.mjs +50 -0
- package/src/services/adventure-autoplay.mjs +5 -2
- package/src/services/adventure-viz.mjs +301 -33
- package/src/services/adventure.mjs +162 -14
- package/src/services/chat-page-viz.mjs +265 -189
- package/src/services/chat-session.mjs +15 -5
- package/src/services/chat.mjs +471 -79
- package/src/services/code-explorer-viz.mjs +183 -75
- package/src/services/extract-facts.mjs +118 -28
- package/src/services/ingest-viz.mjs +328 -79
- package/src/services/ledger-viz.mjs +99 -0
- package/src/services/memory-panel-viz.mjs +159 -0
- package/src/services/research.mjs +266 -0
- package/src/services/sentences.mjs +19 -0
- package/src/services/spider-fly-viz.mjs +21 -5
- package/src/surfaces/web/adventure-browser-entry.mjs +9 -5
- package/src/surfaces/web/chat-browser-entry.mjs +28 -11
- package/src/surfaces/web/code-explorer-browser-entry.mjs +27 -11
- package/src/surfaces/web/ingest-browser-entry.mjs +123 -41
- package/src/surfaces/web/ledger-browser-entry.mjs +10 -4
- package/src/surfaces/web/memory-ask-browser.bundle.js +116 -116
- package/src/surfaces/web/memory-stats.mjs +53 -0
|
@@ -162,7 +162,7 @@ export async function unclaimedAdventureOpening(line, { env }) {
|
|
|
162
162
|
* the meta opening speaks. */
|
|
163
163
|
async function resumedPosition(memoryDir) {
|
|
164
164
|
try {
|
|
165
|
-
const state = foldWorldState(readFactRows(await loadMemory(memoryDir)));
|
|
165
|
+
const state = foldWorldState(worldActionRows(readFactRows(await loadMemory(memoryDir))));
|
|
166
166
|
if (!state.turnCount) return null;
|
|
167
167
|
return state.placements.get("player")?.object ?? null;
|
|
168
168
|
} catch {
|
|
@@ -176,15 +176,35 @@ const SNAPSHOT_RE = /^(.+)@turn(\d+)$/;
|
|
|
176
176
|
const PLACEMENT_PREDICATES = new Set([
|
|
177
177
|
"mgx:currently-in", "mgx:located-in", "mgx:fixed-in", "mgx:stands-locked-in", "mgx:hidden-in",
|
|
178
178
|
]);
|
|
179
|
+
// Supplemental positional relations: where a thing sits WITHIN its room,
|
|
180
|
+
// never where its room is. Folded like placements (newest per subject) but
|
|
181
|
+
// kept apart, since visibility and movement key on the placement, not on
|
|
182
|
+
// which surface a thing rests against.
|
|
183
|
+
const POSITION_PREDICATES = new Set(["mgx:on-top-of", "mgx:on-plane", "mgx:under"]);
|
|
179
184
|
const OPEN_PREDICATE = "mgx:is-open";
|
|
180
185
|
const EXIT_PREDICATE_RE = /^mgx:has-exit-([a-z]+)$/;
|
|
181
186
|
|
|
187
|
+
/** The rows a live world's STATE fold may see: those the world itself wrote
|
|
188
|
+
* (provenance empty, or `world:*` — the loaded shard and its @turn
|
|
189
|
+
* snapshots), never a taught assert (`teach:chat:*`) or a merged corpus. The
|
|
190
|
+
* game world changes only through actions; a locative fact the player TAUGHT
|
|
191
|
+
* mid-game is their own note, and must not silently move a prop. Digest and
|
|
192
|
+
* background-colour paths keep the unfiltered rows — a taught fact still
|
|
193
|
+
* reads back as prose, it just never folds into the playable state. */
|
|
194
|
+
export function worldActionRows(rows) {
|
|
195
|
+
return (rows || []).filter((r) => {
|
|
196
|
+
const prov = String(r.provenance || "").trim();
|
|
197
|
+
return prov === "" || prov.startsWith("world:");
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
182
201
|
/** Fold fact rows into the CURRENT world state: per subject, the newest
|
|
183
202
|
* placement (base row = turn 0, @turnN snapshots override), the newest
|
|
184
203
|
* open/closed state, the exit map, and the turn counter (the largest @turnN
|
|
185
204
|
* suffix written so far — derived, never stored). Pure. */
|
|
186
205
|
export function foldWorldState(factRows) {
|
|
187
206
|
const placements = new Map(); // subject -> { predicate, object, turn }
|
|
207
|
+
const positions = new Map(); // subject -> { predicate, object, turn }
|
|
188
208
|
const openness = new Map(); // subject -> { open, turn }
|
|
189
209
|
const exits = new Map(); // room -> Map(direction -> room)
|
|
190
210
|
let turnCount = 0;
|
|
@@ -198,6 +218,11 @@ export function foldWorldState(factRows) {
|
|
|
198
218
|
if (!prior || turn >= prior.turn) placements.set(base, { predicate: row.predicate, object: row.object, turn });
|
|
199
219
|
continue;
|
|
200
220
|
}
|
|
221
|
+
if (POSITION_PREDICATES.has(row.predicate)) {
|
|
222
|
+
const prior = positions.get(base);
|
|
223
|
+
if (!prior || turn >= prior.turn) positions.set(base, { predicate: row.predicate, object: row.object, turn });
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
201
226
|
if (row.predicate === OPEN_PREDICATE) {
|
|
202
227
|
const prior = openness.get(base);
|
|
203
228
|
if (!prior || turn >= prior.turn) openness.set(base, { open: row.object === "true", turn });
|
|
@@ -209,7 +234,46 @@ export function foldWorldState(factRows) {
|
|
|
209
234
|
exits.get(row.subject).set(exit[1], row.object);
|
|
210
235
|
}
|
|
211
236
|
}
|
|
212
|
-
return { placements, openness, exits, turnCount };
|
|
237
|
+
return { placements, positions, openness, exits, turnCount };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** A subject's CURRENT within-room position, or null. A position goes stale
|
|
241
|
+
* the moment the subject is placed somewhere new: taking the lamp writes a
|
|
242
|
+
* later-turn placement, so its turn-0 `on-top-of desk` no longer holds and
|
|
243
|
+
* no extra write is needed to retract it. */
|
|
244
|
+
export function currentPosition(state, subject) {
|
|
245
|
+
const pos = state.positions.get(subject);
|
|
246
|
+
if (!pos) return null;
|
|
247
|
+
const place = state.placements.get(subject);
|
|
248
|
+
if (place && pos.turn < place.turn) return null;
|
|
249
|
+
return pos;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** The default surface a subject's class implies, walking its rdf:type and
|
|
253
|
+
* rdfs:subClassOf edges for an `mgx:default-plane` fact. A non-floor plane
|
|
254
|
+
* (wall, ceiling) wins over floor wherever both are reachable — a portrait
|
|
255
|
+
* typed both `furniture` (floor) and, via `painting`, wall reads as hanging
|
|
256
|
+
* on the wall. Returns the plane, or null when no class default applies.
|
|
257
|
+
* Pure. */
|
|
258
|
+
function classDefaultPlane(rows, subject) {
|
|
259
|
+
const edgesFrom = (node) => (rows || [])
|
|
260
|
+
.filter((r) => r.subject === node && (r.predicate === "rdf:type" || r.predicate === "rdfs:subClassOf"))
|
|
261
|
+
.map((r) => r.object);
|
|
262
|
+
const planeOf = (node) => (rows || [])
|
|
263
|
+
.find((r) => r.subject === node && r.predicate === "mgx:default-plane")?.object ?? null;
|
|
264
|
+
const seen = new Set([subject]);
|
|
265
|
+
const queue = edgesFrom(subject);
|
|
266
|
+
let fallback = null;
|
|
267
|
+
while (queue.length) {
|
|
268
|
+
const node = queue.shift();
|
|
269
|
+
if (seen.has(node)) continue;
|
|
270
|
+
seen.add(node);
|
|
271
|
+
const plane = planeOf(node);
|
|
272
|
+
if (plane && plane !== "floor") return plane;
|
|
273
|
+
if (plane && !fallback) fallback = plane;
|
|
274
|
+
queue.push(...edgesFrom(node));
|
|
275
|
+
}
|
|
276
|
+
return fallback;
|
|
213
277
|
}
|
|
214
278
|
|
|
215
279
|
// ---- the world interpreter ---------------------------------------------------
|
|
@@ -384,11 +448,20 @@ async function writeWorldTurn(memoryDir, world, k, facts, cache) {
|
|
|
384
448
|
const VIEW_EXCLUDED_PREDICATES = new Set([
|
|
385
449
|
"mgx:hidden-in", "mgx:is-open", "mgx:is-npc", "mgx:is-container",
|
|
386
450
|
"mgx:unlocks-with", "mgx:acts-on-turn", "mgx:acts-toward",
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
451
|
+
// is-objective is an internal marker for auto-play's goal inference — the
|
|
452
|
+
// same information the opening narration already tells a human player in
|
|
453
|
+
// prose, never meant to surface as a raw, unphrased triple ("Letter
|
|
454
|
+
// mgx:is-objective true.") itself.
|
|
391
455
|
"mgx:is-objective",
|
|
456
|
+
// Staff knowledge is the whole puzzle: a room look must never leak
|
|
457
|
+
// "Gardener knows-where letter" or the game is spoiled. It reaches the
|
|
458
|
+
// player only through the talk lane, which resolves each pointer live.
|
|
459
|
+
"mgx:knows-where", "mgx:knows-objective", "mgx:knows-about",
|
|
460
|
+
// Class-schema facts describe the ontology, not the scene. default-contains
|
|
461
|
+
// is already materialized into real instances at load; default-plane and
|
|
462
|
+
// subClassOf drive positional rendering by their own readers, and read as
|
|
463
|
+
// raw triples if they land in room prose.
|
|
464
|
+
"mgx:default-contains", "mgx:default-plane", "rdfs:subClassOf",
|
|
392
465
|
]);
|
|
393
466
|
|
|
394
467
|
const sentenceCase = (term) => String(term).charAt(0).toUpperCase() + String(term).slice(1);
|
|
@@ -450,6 +523,18 @@ export function worldDigestRows(rows, state) {
|
|
|
450
523
|
}[place.predicate];
|
|
451
524
|
if (phrase) push(subject, phrase, place.object);
|
|
452
525
|
}
|
|
526
|
+
// Where a placed thing sits within its room — an instance position (the
|
|
527
|
+
// lamp on the desk) if one is current, else a notable class default (a
|
|
528
|
+
// portrait on the wall). Floor is the unremarkable default the room view
|
|
529
|
+
// already assumes, so it is left unsaid.
|
|
530
|
+
const POSITION_PHRASE = { "mgx:on-top-of": "is on the", "mgx:on-plane": "is on the", "mgx:under": "is under the" };
|
|
531
|
+
for (const [subject, place] of state.placements) {
|
|
532
|
+
if (place.predicate === "mgx:hidden-in" || place.object === "player") continue;
|
|
533
|
+
const pos = currentPosition(state, subject);
|
|
534
|
+
if (pos && POSITION_PHRASE[pos.predicate]) { push(subject, POSITION_PHRASE[pos.predicate], pos.object); continue; }
|
|
535
|
+
const plane = classDefaultPlane(rows, subject);
|
|
536
|
+
if (plane && plane !== "floor") push(subject, "is usually on the", plane);
|
|
537
|
+
}
|
|
453
538
|
for (const row of rows || []) {
|
|
454
539
|
if (SNAPSHOT_RE.test(row.subject)) continue; // folded above
|
|
455
540
|
// Room text comes from the world source only. A merged corpus overlaps a
|
|
@@ -459,6 +544,7 @@ export function worldDigestRows(rows, state) {
|
|
|
459
544
|
// only drops rows a non-world source provably owns.
|
|
460
545
|
if (isNonWorldSourced(row)) continue;
|
|
461
546
|
if (PLACEMENT_PREDICATES.has(row.predicate)) continue; // folded above
|
|
547
|
+
if (POSITION_PREDICATES.has(row.predicate)) continue; // folded above
|
|
462
548
|
if (VIEW_EXCLUDED_PREDICATES.has(row.predicate)) continue;
|
|
463
549
|
const exit = EXIT_PREDICATE_RE.exec(row.predicate);
|
|
464
550
|
if (exit) { push(row.subject, `has an exit ${exit[1]} to the`, row.object); continue; }
|
|
@@ -537,10 +623,54 @@ function containerDatatypeState(state, object) {
|
|
|
537
623
|
return rows;
|
|
538
624
|
}
|
|
539
625
|
|
|
626
|
+
/** The knowledge a person shares when talked to, resolved against the LIVE
|
|
627
|
+
* world fold this turn — never a frozen string, so it stays true as the
|
|
628
|
+
* world changes and honest when it doesn't know. `knows-where` reveals a
|
|
629
|
+
* thing's current location, a hidden one included: talking to the staff is
|
|
630
|
+
* the sanctioned way to learn a hiding place, while the where-is aside keeps
|
|
631
|
+
* declining. `knows-objective` states the quest. `knows-about` topics come
|
|
632
|
+
* back for the caller to digest. Pure. */
|
|
633
|
+
export function personKnowledgeLines(rows, state, person) {
|
|
634
|
+
const lines = [];
|
|
635
|
+
for (const objective of factObjects(rows, person, "mgx:knows-objective")) {
|
|
636
|
+
lines.push(`the ${objective} is what you're after — find it and carry it out of the house.`);
|
|
637
|
+
}
|
|
638
|
+
for (const thing of factObjects(rows, person, "mgx:knows-where")) {
|
|
639
|
+
const place = state.placements.get(thing);
|
|
640
|
+
if (!place) continue;
|
|
641
|
+
lines.push(isTyped(rows, thing, "person")
|
|
642
|
+
? `you'll find the ${thing} in the ${place.object}.`
|
|
643
|
+
: `the ${thing} is in the ${place.object}.`);
|
|
644
|
+
}
|
|
645
|
+
return { lines, aboutTopics: factObjects(rows, person, "mgx:knows-about") };
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/** What a person can report from where they stand this turn — derived each
|
|
649
|
+
* turn, never stored: who and what shares their room, each container's
|
|
650
|
+
* open/locked status, and what unlocks a locked one there. Pure. */
|
|
651
|
+
export function personRoomReport(rows, state, person) {
|
|
652
|
+
const room = state.placements.get(person)?.object ?? null;
|
|
653
|
+
if (!room) return "";
|
|
654
|
+
const here = [...state.placements.keys()]
|
|
655
|
+
.filter((s) => s !== person && s !== "player" && visibleRoomOf(s, { rows, state }) === room)
|
|
656
|
+
.sort();
|
|
657
|
+
const parts = [];
|
|
658
|
+
if (here.length) parts.push(`here in the ${room}: the ${here.join(", the ")}.`);
|
|
659
|
+
for (const thing of here) {
|
|
660
|
+
if (!isContainer(rows, thing)) continue;
|
|
661
|
+
parts.push(containerStatusPhrase(thing, { state }));
|
|
662
|
+
const key = factObjects(rows, thing, "mgx:unlocks-with")[0];
|
|
663
|
+
if (key && state.placements.get(thing)?.predicate === "mgx:stands-locked-in") {
|
|
664
|
+
parts.push(`the ${thing} needs the ${key} to open.`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return parts.join(" ");
|
|
668
|
+
}
|
|
669
|
+
|
|
540
670
|
async function runWorldCommand(cmd, { world, memoryDir, env, graph, cache }) {
|
|
541
671
|
const memory = await loadMemory(memoryDir);
|
|
542
672
|
const rows = readFactRows(memory);
|
|
543
|
-
const state = foldWorldState(rows);
|
|
673
|
+
const state = foldWorldState(worldActionRows(rows));
|
|
544
674
|
const here = state.placements.get("player")?.object ?? null;
|
|
545
675
|
const noteFor = (detail) => `ADVENTURE — ${detail}`;
|
|
546
676
|
|
|
@@ -608,6 +738,24 @@ async function runWorldCommand(cmd, { world, memoryDir, env, graph, cache }) {
|
|
|
608
738
|
);
|
|
609
739
|
}
|
|
610
740
|
const person = isTyped(rows, object, "person");
|
|
741
|
+
// Talking to a person is the game's reveal channel: the staff share what
|
|
742
|
+
// they know (a hiding place, the quest, a topic) and report their own
|
|
743
|
+
// room, all resolved from the live fold this turn.
|
|
744
|
+
if (cmd.verb === "talk" && person) {
|
|
745
|
+
const { lines, aboutTopics } = personKnowledgeLines(rows, state, object);
|
|
746
|
+
const aboutLines = [];
|
|
747
|
+
for (const topic of aboutTopics) {
|
|
748
|
+
const digested = await worldDigest(topic, { memoryDir, memory, rows, state, graph });
|
|
749
|
+
if (digested) aboutLines.push(digested);
|
|
750
|
+
}
|
|
751
|
+
const report = personRoomReport(rows, state, object);
|
|
752
|
+
const said = [...lines, ...aboutLines, report].filter(Boolean).join(" ");
|
|
753
|
+
return answer(
|
|
754
|
+
said ? `the ${object} says: ${said}` : `the ${object} has nothing to tell you right now.`,
|
|
755
|
+
noteFor(`talk — the ${object}'s live knowledge (knows-where/objective/about from the current fold) and a derived room report`),
|
|
756
|
+
{ goal: `talk to the ${object}` },
|
|
757
|
+
);
|
|
758
|
+
}
|
|
611
759
|
const digest = await worldDigest(object, { memoryDir, memory, rows, state, graph });
|
|
612
760
|
const body = digest ?? `nothing more about the ${object} is written down yet.`;
|
|
613
761
|
const containerNote = !person && isContainer(rows, object) ? ` ${containerStatusPhrase(object, { state })}` : "";
|
|
@@ -649,7 +797,7 @@ async function runWorldCommand(cmd, { world, memoryDir, env, graph, cache }) {
|
|
|
649
797
|
// player is never left to retype "look" to see what just changed.
|
|
650
798
|
const freshMemory = await loadMemory(memoryDir);
|
|
651
799
|
const freshRows = readFactRows(freshMemory);
|
|
652
|
-
const freshState = foldWorldState(freshRows);
|
|
800
|
+
const freshState = foldWorldState(worldActionRows(freshRows));
|
|
653
801
|
const relookDigest = await worldDigest(playerRoomAfter, { memoryDir, memory: freshMemory, rows: freshRows, state: freshState, graph });
|
|
654
802
|
const actions = roomAffordances(freshRows, freshState, playerRoomAfter);
|
|
655
803
|
const relook = `you are in the ${playerRoomAfter}. ${relookDigest ?? "Nothing more about it is written down yet."}${affordanceSuffix(actions)}`;
|
|
@@ -874,13 +1022,13 @@ async function worldWhereAnswer(line, { memoryDir }) {
|
|
|
874
1022
|
const thing = normFactTerm(m[1]);
|
|
875
1023
|
let rows;
|
|
876
1024
|
try { rows = readFactRows(await loadMemory(memoryDir)); } catch { return null; }
|
|
877
|
-
const state = foldWorldState(rows);
|
|
1025
|
+
const state = foldWorldState(worldActionRows(rows));
|
|
878
1026
|
const place = state.placements.get(thing);
|
|
879
1027
|
if (!place) return null;
|
|
880
1028
|
if (place.predicate === "mgx:hidden-in") {
|
|
881
1029
|
return answer(
|
|
882
|
-
`nothing you've seen says where the ${thing} is.`,
|
|
883
|
-
`ADVENTURE — where-aside: ${thing} is hidden; declined without naming the hiding place`,
|
|
1030
|
+
`nothing you've seen says where the ${thing} is. Someone in the house may know — try talking to the staff.`,
|
|
1031
|
+
`ADVENTURE — where-aside: ${thing} is hidden; declined without naming the hiding place, pointed at the talk lane`,
|
|
884
1032
|
{ miss: true, goal: `locate the ${thing}` },
|
|
885
1033
|
);
|
|
886
1034
|
}
|
|
@@ -916,7 +1064,7 @@ async function worldOpennessAnswer(line, { memoryDir }) {
|
|
|
916
1064
|
const askedOpen = /^open$/i.test(m[2]);
|
|
917
1065
|
let rows;
|
|
918
1066
|
try { rows = readFactRows(await loadMemory(memoryDir)); } catch { return null; }
|
|
919
|
-
const state = foldWorldState(rows);
|
|
1067
|
+
const state = foldWorldState(worldActionRows(rows));
|
|
920
1068
|
const openness = state.openness.get(thing);
|
|
921
1069
|
if (!openness) return null;
|
|
922
1070
|
const matches = askedOpen ? openness.open : !openness.open;
|
|
@@ -947,7 +1095,7 @@ async function worldContextAnswer(line, { memoryDir }) {
|
|
|
947
1095
|
if (!asksWhere && !asksOptions && !asksQuest) return null;
|
|
948
1096
|
let rows;
|
|
949
1097
|
try { rows = readFactRows(await loadMemory(memoryDir)); } catch { return null; }
|
|
950
|
-
const state = foldWorldState(rows);
|
|
1098
|
+
const state = foldWorldState(worldActionRows(rows));
|
|
951
1099
|
const here = state.placements.get("player")?.object ?? null;
|
|
952
1100
|
|
|
953
1101
|
if (asksWhere) {
|
|
@@ -982,7 +1130,7 @@ async function worldContextAnswer(line, { memoryDir }) {
|
|
|
982
1130
|
async function inventoryAnswer({ memoryDir, graph }) {
|
|
983
1131
|
const memory = await loadMemory(memoryDir);
|
|
984
1132
|
const rows = readFactRows(memory);
|
|
985
|
-
const state = foldWorldState(rows);
|
|
1133
|
+
const state = foldWorldState(worldActionRows(rows));
|
|
986
1134
|
const carried = [...state.placements]
|
|
987
1135
|
.filter(([, p]) => p.predicate === "mgx:located-in" && p.object === "player")
|
|
988
1136
|
.map(([thing]) => thing)
|