@polycode-projects/the-mechanical-code-talker 2.7.21 → 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.21",
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