@polycode-projects/the-mechanical-code-talker 2.8.1 → 2.8.3
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/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
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// chat text.
|
|
17
17
|
|
|
18
18
|
import {
|
|
19
|
-
DIRECTION_DELTA, WORLD_NAME, cellId, parseCellId, inBounds, chebyshevDistance,
|
|
19
|
+
DIRECTION_DELTA, WORLD_NAME, cellId, parseCellId, inBounds, chebyshevDistance, oneStepDirectionBetween,
|
|
20
20
|
} from "../domain/spider-fly-world.mjs";
|
|
21
21
|
import { foldSpiderFlyState, runSpiderFlyTick, startSpiderFlyGame } from "./spider-fly.mjs";
|
|
22
22
|
import { worldProvenanceTag } from "../domain/worlds-pack.mjs";
|
|
@@ -170,6 +170,121 @@ function resolveTargetCell({ direction, cellLiteral, fromCell }) {
|
|
|
170
170
|
return inBounds(nx, ny) ? { x: nx, y: ny } : null;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
// oneStepDirectionBetween lives in spider-fly-world.mjs (the shared grid
|
|
174
|
+
// geometry both this chat-turn layer and the engine need — see that
|
|
175
|
+
// module's own header comment); re-exported here so a caller of this file
|
|
176
|
+
// never has to reach into the domain layer just to build a deception pill's
|
|
177
|
+
// direction wording by hand.
|
|
178
|
+
export { oneStepDirectionBetween };
|
|
179
|
+
|
|
180
|
+
// ---- deception pills, built on the addressed teach-frame above: dynamic,
|
|
181
|
+
// per-tick chat-dock suggestions alongside (never replacing) the existing
|
|
182
|
+
// static 6-button address/direction rail. No new grammar at all — every
|
|
183
|
+
// pill's sentence is exactly the SAME SPIDER_FLY_TOLD_RE line above already
|
|
184
|
+
// accepts, filled in with either the subject's real position or a
|
|
185
|
+
// deliberately false one, so a human clicking one submits a plain
|
|
186
|
+
// "@spider the fly is east"-shaped line indistinguishable from a hand-typed
|
|
187
|
+
// claim, true or false alike. A pill's `truth` tag is for the human eye
|
|
188
|
+
// only — it never rides along in the submitted text itself.
|
|
189
|
+
|
|
190
|
+
const liveIdsOfKindFromAgents = (kind, agents) => {
|
|
191
|
+
const re = new RegExp(`^${kind}-\\d+$`);
|
|
192
|
+
return Object.keys(agents || {}).filter((id) => re.test(id)).sort();
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/** "spider"/"fly" bare when exactly one individual of that kind is live
|
|
196
|
+
* (nothing to disambiguate), else the individual's own numbered id — a
|
|
197
|
+
* pill-set legibility choice, not a grammar restriction (the addressed
|
|
198
|
+
* teach-frame's own bare form always resolves to "the first live
|
|
199
|
+
* individual" regardless of count; this just stops offering a bare pill
|
|
200
|
+
* once it would read as ambiguous to a human watching two-plus). */
|
|
201
|
+
function agentPillLabel(kind, id, liveIdsOfKind) {
|
|
202
|
+
return liveIdsOfKind.length > 1 ? id : kind;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** The point reflection of `cell` through the 10x10 board's center —
|
|
206
|
+
* cell-<11-x>-<11-y> — the canonical false-claim cell: deterministic (no
|
|
207
|
+
* seeded RNG needed, since pills are never persisted state), always
|
|
208
|
+
* in-bounds (1..10 reflects onto 1..10), and never accidentally true (x =
|
|
209
|
+
* 11-x has no integer solution for an integer x in 1..10, so the reflected
|
|
210
|
+
* cell can never coincide with the real one). */
|
|
211
|
+
function reflectedCell(cell) {
|
|
212
|
+
return { x: 11 - cell.x, y: 11 - cell.y };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The spider-fly chat dock's dynamic pill set for one tick's live `agents`
|
|
217
|
+
* (runSpiderFlyTick's/foldSpiderFlyState's own `{ id: { cell } }` shape —
|
|
218
|
+
* only `.cell` is read): one address pill per live spider/fly (bare
|
|
219
|
+
* "@spider" while exactly one of that kind is alive, numbered "@spider-2"
|
|
220
|
+
* once more than one is), plus — for whichever individual is currently
|
|
221
|
+
* addressed (`explicitAddresseeId`, falling back to `opts.defaultKind`'s
|
|
222
|
+
* first live individual, "spider" by default) — one true-claim and one
|
|
223
|
+
* canonical false-claim pill per live individual of the OPPOSITE kind (you
|
|
224
|
+
* address a spider about a fly's position, or a fly about a spider's — the
|
|
225
|
+
* predator/prey belief channel the addressed teach-frame already carries).
|
|
226
|
+
* A true-claim pill reads the nearest compass direction when the
|
|
227
|
+
* candidate's real cell sits exactly one cardinal step from the addressee's
|
|
228
|
+
* own cell (oneStepDirectionBetween), else the exact cell (always
|
|
229
|
+
* expressible). A false-claim pill always reads the exact cell form,
|
|
230
|
+
* holding the candidate's point-reflected cell (reflectedCell) — never a
|
|
231
|
+
* direction, since a fabricated direction has no single canonical,
|
|
232
|
+
* deterministic form the way a fabricated cell does.
|
|
233
|
+
*
|
|
234
|
+
* Returns `{ addressPills, claimPills, addresseeId }` — `addressPills` is
|
|
235
|
+
* `[{ id, kind, label }]`; `claimPills` is `[{ subjectId, truth, text,
|
|
236
|
+
* sentence }]` (`sentence` is the complete, ready-to-submit chat line);
|
|
237
|
+
* both empty when nothing is live. `addresseeId` is whichever individual
|
|
238
|
+
* the claim pills were actually built for (or null), so a caller can track
|
|
239
|
+
* "currently addressing" across ticks without re-deriving it. Pure.
|
|
240
|
+
*/
|
|
241
|
+
export function pillsForSpiderFly(agents, explicitAddresseeId, opts = {}) {
|
|
242
|
+
const { defaultKind = "spider" } = opts;
|
|
243
|
+
const liveSpiders = liveIdsOfKindFromAgents("spider", agents);
|
|
244
|
+
const liveFlies = liveIdsOfKindFromAgents("fly", agents);
|
|
245
|
+
|
|
246
|
+
const addressPills = [
|
|
247
|
+
...liveSpiders.map((id) => ({ id, kind: "spider", label: `@${agentPillLabel("spider", id, liveSpiders)}` })),
|
|
248
|
+
...liveFlies.map((id) => ({ id, kind: "fly", label: `@${agentPillLabel("fly", id, liveFlies)}` })),
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
const fallbackAddresseeId = (defaultKind === "fly" ? liveFlies[0] : liveSpiders[0]) ?? liveFlies[0] ?? liveSpiders[0] ?? null;
|
|
252
|
+
const addresseeId = (explicitAddresseeId && agents[explicitAddresseeId]) ? explicitAddresseeId : fallbackAddresseeId;
|
|
253
|
+
if (!addresseeId) return { addressPills, claimPills: [], addresseeId: null };
|
|
254
|
+
|
|
255
|
+
const addresseeKind = /^spider-\d+$/.test(addresseeId) ? "spider" : "fly";
|
|
256
|
+
const addresseeLabel = agentPillLabel(addresseeKind, addresseeId, addresseeKind === "spider" ? liveSpiders : liveFlies);
|
|
257
|
+
const addresseeCell = parseCellId(agents[addresseeId].cell);
|
|
258
|
+
|
|
259
|
+
const candidateKind = addresseeKind === "spider" ? "fly" : "spider";
|
|
260
|
+
const candidateIds = candidateKind === "spider" ? liveSpiders : liveFlies;
|
|
261
|
+
|
|
262
|
+
const claimPills = [];
|
|
263
|
+
for (const subjectId of candidateIds) {
|
|
264
|
+
const subjectLabel = agentPillLabel(candidateKind, subjectId, candidateIds);
|
|
265
|
+
const trueCell = parseCellId(agents[subjectId].cell);
|
|
266
|
+
const direction = oneStepDirectionBetween(addresseeCell, trueCell);
|
|
267
|
+
// A direction reads "is east"; the exact-cell fallback (used whenever
|
|
268
|
+
// there's no genuine one-step adjacency, and ALWAYS for the false claim
|
|
269
|
+
// below) reads "is at cell-x-y" — the two forms SPIDER_FLY_TOLD_RE
|
|
270
|
+
// itself accepts.
|
|
271
|
+
const trueValue = direction ? direction : `at ${cellId(trueCell.x, trueCell.y)}`;
|
|
272
|
+
const falseCell = reflectedCell(trueCell);
|
|
273
|
+
const falseValue = `at ${cellId(falseCell.x, falseCell.y)}`;
|
|
274
|
+
claimPills.push({
|
|
275
|
+
subjectId, truth: true,
|
|
276
|
+
text: `the ${subjectLabel} is ${trueValue}`,
|
|
277
|
+
sentence: `@${addresseeLabel} the ${subjectLabel} is ${trueValue}`,
|
|
278
|
+
});
|
|
279
|
+
claimPills.push({
|
|
280
|
+
subjectId, truth: false,
|
|
281
|
+
text: `the ${subjectLabel} is ${falseValue}`,
|
|
282
|
+
sentence: `@${addresseeLabel} the ${subjectLabel} is ${falseValue}`,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
return { addressPills, claimPills, addresseeId };
|
|
286
|
+
}
|
|
287
|
+
|
|
173
288
|
// ---- rendering one tick's return value as plain chat text --------------------
|
|
174
289
|
|
|
175
290
|
function renderTickText(tick, addressedNote) {
|
|
@@ -181,10 +296,11 @@ function renderTickText(tick, addressedNote) {
|
|
|
181
296
|
: `Turn ${tick.turn} — no agents remain on the board.`);
|
|
182
297
|
const eco = tick.ecology;
|
|
183
298
|
const events = [];
|
|
299
|
+
for (const c of eco.caught) events.push(`${c.spider} caught ${c.fly} at ${c.cell}`);
|
|
184
300
|
for (const e of eco.eaten) events.push(`${e.fly} was eaten by ${e.spider} at ${e.cell}`);
|
|
185
301
|
for (const f of eco.starved) events.push(`${f} starved`);
|
|
186
302
|
if (eco.laid) events.push(`${eco.laid} was laid`);
|
|
187
|
-
for (const h of eco.hatched) events.push(`${h.egg} hatched into ${h.spider} at ${h.cell}`);
|
|
303
|
+
for (const h of eco.hatched) events.push(`${h.egg} hatched into ${h.spiders.map((s) => s.spider).join(" and ")} at ${h.cell}`);
|
|
188
304
|
if (eco.spawned) events.push(`${eco.spawned} arrived at the board edge`);
|
|
189
305
|
if (events.length) parts.push(`${events.join("; ")}.`);
|
|
190
306
|
return parts.join(" ");
|
|
@@ -205,10 +321,11 @@ function combinedGoalLine(agents) {
|
|
|
205
321
|
|
|
206
322
|
function describeEcologyNote(eco) {
|
|
207
323
|
const bits = [];
|
|
324
|
+
if (eco.caught.length) bits.push(`${eco.caught.length} caught`);
|
|
208
325
|
if (eco.eaten.length) bits.push(`${eco.eaten.length} eaten`);
|
|
209
326
|
if (eco.starved.length) bits.push(`${eco.starved.length} starved`);
|
|
210
327
|
if (eco.laid) bits.push("1 laid");
|
|
211
|
-
if (eco.hatched.length) bits.push(`${eco.hatched.length} hatched`);
|
|
328
|
+
if (eco.hatched.length) bits.push(`${eco.hatched.reduce((n, h) => n + h.spiders.length, 0)} hatched`);
|
|
212
329
|
if (eco.spawned) bits.push("1 spawned");
|
|
213
330
|
return bits.length ? `; ${bits.join(", ")}` : "";
|
|
214
331
|
}
|