@polycode-projects/the-mechanical-code-talker 2.7.20 → 2.7.22
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polycode-projects/the-mechanical-code-talker",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.22",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The Mechanical Code Talker (tmct) — a tolerant, offline, $0 chat surface that guides you toward precision queries about a software repository. ELIZA/PARRY-style but domain-obsessed with code. No model calls; no codebase index of its own.",
|
|
@@ -87,6 +87,43 @@ function exposedExitApplyActions(exposedState) {
|
|
|
87
87
|
const unexposedExitsOf = (room, exposedState, exposed) =>
|
|
88
88
|
[...(exposedState.exits.get(room)?.entries() ?? [])].filter(([, target]) => !exposed.has(target));
|
|
89
89
|
|
|
90
|
+
const carriedByPlayer = (state, thing) => {
|
|
91
|
+
const place = state.placements.get(thing);
|
|
92
|
+
return !!place && place.predicate === "mgx:located-in" && place.object === "player";
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/** Every mgx:is-container subject whose OWN placement is exposed (the room
|
|
96
|
+
* holding it has been visited) — a container's presence and lock state are
|
|
97
|
+
* visible on sight, even though its CONTENTS stay hidden until it's actually
|
|
98
|
+
* opened. Sorted for deterministic tie-breaking across ticks. */
|
|
99
|
+
function exposedContainers(exposedRows, exposedState) {
|
|
100
|
+
const ids = new Set(
|
|
101
|
+
exposedRows.filter((r) => r.predicate === "mgx:is-container" && r.object === "true").map((r) => r.subject),
|
|
102
|
+
);
|
|
103
|
+
return [...ids].filter((id) => roomOfSubject(id, exposedRows, exposedState) != null).sort();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** One tick's worth of progress toward standing in `targetRoom` and then
|
|
107
|
+
* issuing `finalCommand` once there — the same path-then-act shape the
|
|
108
|
+
* objective fetch above already uses, generalized so opening/unlocking a
|
|
109
|
+
* container and fetching the instrument that unlocks it can all reuse it
|
|
110
|
+
* rather than re-deriving the same findActionPath call three times. Returns
|
|
111
|
+
* null (never a stall) when no seen path exists yet, so the caller can fall
|
|
112
|
+
* through to try the next candidate instead of reporting a false stall. */
|
|
113
|
+
async function stepTowardThenAct({
|
|
114
|
+
here, targetRoom, finalCommand, goalWhenArrived, goalWhenEnRoute, runCommand, exposedState, exposed, turnCount,
|
|
115
|
+
}) {
|
|
116
|
+
if (targetRoom === here) {
|
|
117
|
+
await runCommand(finalCommand);
|
|
118
|
+
return { turn: turnCount + 1, goal: goalWhenArrived, plan: null, done: false, stalled: false, exposedRoomIds: exposed };
|
|
119
|
+
}
|
|
120
|
+
const path = findActionPath(here, (room) => room === targetRoom, exposedExitApplyActions(exposedState));
|
|
121
|
+
if (!path || !path.actions.length) return null;
|
|
122
|
+
await runCommand(`go ${path.actions[0]}`);
|
|
123
|
+
exposed.add(path.states[1]);
|
|
124
|
+
return { turn: turnCount + 1, goal: goalWhenEnRoute, plan: path.actions, done: false, stalled: false, exposedRoomIds: exposed };
|
|
125
|
+
}
|
|
126
|
+
|
|
90
127
|
/**
|
|
91
128
|
* One auto-play tick over a live, loaded adventure: fold the world, infer a
|
|
92
129
|
* goal from the one generic objective marker under the exposure constraint,
|
|
@@ -125,9 +162,7 @@ export async function runAdventureAutoplayTick(memoryDir, opts = {}) {
|
|
|
125
162
|
// the same unconditional "OR the subject is player" exposure the marker
|
|
126
163
|
// fact itself gets (worldDigestRows shows carried items regardless of
|
|
127
164
|
// room visibility too — carrying was never gated on being seen).
|
|
128
|
-
const carried = objectiveId
|
|
129
|
-
&& state.placements.get(objectiveId)?.predicate === "mgx:located-in"
|
|
130
|
-
&& state.placements.get(objectiveId)?.object === "player";
|
|
165
|
+
const carried = objectiveId && carriedByPlayer(state, objectiveId);
|
|
131
166
|
if (objectiveId && carried) {
|
|
132
167
|
return {
|
|
133
168
|
turn: state.turnCount, goal: `carrying the ${objectiveId} — the adventure is won.`,
|
|
@@ -160,6 +195,65 @@ export async function runAdventureAutoplayTick(memoryDir, opts = {}) {
|
|
|
160
195
|
};
|
|
161
196
|
}
|
|
162
197
|
|
|
198
|
+
// Progress a known container: the objective's own room is still unknown,
|
|
199
|
+
// which — since a hidden object's placement fact never resolves to a room
|
|
200
|
+
// while it stays hidden (roomOfSubject) — is exactly the state every world
|
|
201
|
+
// with ANY hidden contents starts in, even once its container has been
|
|
202
|
+
// walked right up to. A container's presence and lock state ARE exposed on
|
|
203
|
+
// sight, though (they're facts about the container itself, not about what's
|
|
204
|
+
// inside it), so this is the one place auto-play can act on something it
|
|
205
|
+
// has genuinely seen rather than just wander further:
|
|
206
|
+
// - a container standing open already has nothing left to reveal — skip;
|
|
207
|
+
// - a LOCKED container whose instrument is both named (mgx:unlocks-with)
|
|
208
|
+
// and already carried: go unlock it;
|
|
209
|
+
// - a LOCKED container whose instrument's own room is known (exposed) but
|
|
210
|
+
// not yet carried: go fetch the instrument first;
|
|
211
|
+
// - an UNLOCKED, still-closed container: opening it can only ever reveal
|
|
212
|
+
// more (never a wrong guess), so go open it.
|
|
213
|
+
// Every branch reuses the exact path-then-act shape the objective fetch
|
|
214
|
+
// above already uses; falling through (no seen path, or nothing sound for
|
|
215
|
+
// any exposed container) drops to the plain room-exploration below, exactly
|
|
216
|
+
// as before this container-awareness existed.
|
|
217
|
+
if (objectiveId && !objectiveRoom) {
|
|
218
|
+
for (const containerId of exposedContainers(exposedRows, exposedState)) {
|
|
219
|
+
if (exposedState.openness.get(containerId)?.open) continue;
|
|
220
|
+
const containerRoom = roomOfSubject(containerId, exposedRows, exposedState);
|
|
221
|
+
if (!containerRoom) continue;
|
|
222
|
+
const locked = exposedState.placements.get(containerId)?.predicate === "mgx:stands-locked-in";
|
|
223
|
+
if (locked) {
|
|
224
|
+
const instrumentId = exposedRows.find((r) => r.subject === containerId && r.predicate === "mgx:unlocks-with")?.object ?? null;
|
|
225
|
+
if (!instrumentId) continue; // this world names no instrument for it — nothing sound to try
|
|
226
|
+
if (carriedByPlayer(state, instrumentId)) {
|
|
227
|
+
const step = await stepTowardThenAct({
|
|
228
|
+
here, targetRoom: containerRoom, finalCommand: `unlock ${containerId} with ${instrumentId}`,
|
|
229
|
+
goalWhenArrived: `in the ${here} — unlocking the ${containerId} with the ${instrumentId}.`,
|
|
230
|
+
goalWhenEnRoute: `heading toward the ${containerRoom} to unlock the ${containerId}.`,
|
|
231
|
+
runCommand, exposedState, exposed, turnCount: state.turnCount,
|
|
232
|
+
});
|
|
233
|
+
if (step) return step;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const instrumentRoom = roomOfSubject(instrumentId, exposedRows, exposedState);
|
|
237
|
+
if (!instrumentRoom) continue; // the instrument's own location isn't known yet — nothing sound here
|
|
238
|
+
const step = await stepTowardThenAct({
|
|
239
|
+
here, targetRoom: instrumentRoom, finalCommand: `take ${instrumentId}`,
|
|
240
|
+
goalWhenArrived: `in the ${instrumentRoom} — taking the ${instrumentId} to unlock the ${containerId} later.`,
|
|
241
|
+
goalWhenEnRoute: `heading toward the ${instrumentRoom} for the ${instrumentId}.`,
|
|
242
|
+
runCommand, exposedState, exposed, turnCount: state.turnCount,
|
|
243
|
+
});
|
|
244
|
+
if (step) return step;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const step = await stepTowardThenAct({
|
|
248
|
+
here, targetRoom: containerRoom, finalCommand: `open ${containerId}`,
|
|
249
|
+
goalWhenArrived: `in the ${here} — opening the ${containerId} to see what's inside.`,
|
|
250
|
+
goalWhenEnRoute: `heading toward the ${containerRoom} to open the ${containerId}.`,
|
|
251
|
+
runCommand, exposedState, exposed, turnCount: state.turnCount,
|
|
252
|
+
});
|
|
253
|
+
if (step) return step;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
163
257
|
// Explore: the objective either doesn't exist in this world, or its room
|
|
164
258
|
// isn't known yet. Prefer an immediate unexposed exit from here (the
|
|
165
259
|
// lowest-sorted direction); otherwise path toward the nearest exposed room
|
|
@@ -368,7 +368,7 @@ function mostRecentEaterSpider(state, eatenDeltaBySpider) {
|
|
|
368
368
|
export function runEcologyPass({ state, postMovePlacements, postMoveMassByFly, postMoveMassBySpider = new Map(), turn }) {
|
|
369
369
|
const k = turn;
|
|
370
370
|
const writes = [];
|
|
371
|
-
const events = { eaten: [], starved: [], laid: null, hatched: [], spawned: null };
|
|
371
|
+
const events = { eaten: [], starved: [], laid: null, hatched: [], spawned: null, massAfterEating: new Map() };
|
|
372
372
|
|
|
373
373
|
const spiders = [...postMovePlacements.keys()].filter((id) => /^spider-\d+$/.test(id)).sort();
|
|
374
374
|
const flies = [...postMovePlacements.keys()].filter((id) => /^fly-\d+$/.test(id)).sort();
|
|
@@ -399,6 +399,7 @@ export function runEcologyPass({ state, postMovePlacements, postMoveMassByFly, p
|
|
|
399
399
|
const priorSpiderMass = postMoveMassBySpider.get(spiderId) ?? (state.mass.get(spiderId)?.value ?? SPIDER_INITIAL_MASS);
|
|
400
400
|
const newSpiderMass = priorSpiderMass + (eatenMassBySpider.get(spiderId) ?? 0);
|
|
401
401
|
writes.push({ subject: `${spiderId}@turn${k}`, predicate: "mgx:mass", object: String(newSpiderMass) });
|
|
402
|
+
events.massAfterEating.set(spiderId, newSpiderMass);
|
|
402
403
|
}
|
|
403
404
|
|
|
404
405
|
// 2. Starve — mass reached zero, and not already claimed by this turn's
|
|
@@ -690,7 +691,10 @@ export async function runSpiderFlyTick(memoryDir, opts = {}) {
|
|
|
690
691
|
eatenBySpider.get(spider).push(fly);
|
|
691
692
|
}
|
|
692
693
|
for (const [spider, flyIds] of eatenBySpider) {
|
|
693
|
-
if (agents[spider])
|
|
694
|
+
if (agents[spider]) {
|
|
695
|
+
agents[spider].goal = `just ate ${flyIds.join(" and ")} in the web.`;
|
|
696
|
+
agents[spider].mass = ecology.events.massAfterEating.get(spider) ?? agents[spider].mass;
|
|
697
|
+
}
|
|
694
698
|
}
|
|
695
699
|
for (const flyId of ecology.events.starved) delete agents[flyId];
|
|
696
700
|
const writes = [...movementWrites, ...ecology.writes];
|