@polycode-projects/the-mechanical-code-talker 5.0.2 → 5.0.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.
- package/corpus/worlds/manifest.json +9 -9
- package/corpus/worlds/shards/town-square-chapel.jsonl.gz +0 -0
- package/corpus/worlds/shards/town-square-market.jsonl.gz +0 -0
- package/corpus/worlds/shards/town-square.jsonl.gz +0 -0
- package/corpus/worlds/src/town-square-chapel.jsonl +1 -1
- package/corpus/worlds/src/town-square-market.jsonl +1 -1
- package/corpus/worlds/src/town-square.jsonl +1 -1
- package/data/mudiii-assets.json +35 -5
- package/package.json +1 -1
- package/src/adapters/memory/core.mjs +236 -18
- package/src/domain/ask-vocab.mjs +1 -1
- package/src/domain/ask.mjs +20 -10
- package/src/domain/game-config.mjs +10 -1
- package/src/domain/interpret/normalize.mjs +4 -0
- package/src/domain/memory/retraction.mjs +232 -0
- package/src/domain/p2p/sync-filter.mjs +9 -1
- package/src/domain/spider-fly-world.mjs +80 -35
- package/src/domain/syllogise.mjs +128 -75
- package/src/services/adventure-autoplay.mjs +2 -2
- package/src/services/adventure-viz.mjs +12 -7
- package/src/services/chat.mjs +42 -14
- package/src/services/mud-turn.mjs +1 -1
- package/src/services/mud-viz.mjs +11 -2
- package/src/services/mudiii-scene.mjs +199 -55
- package/src/services/mudiii-turn.mjs +82 -1
- package/src/services/mudiii-viz.mjs +17 -7
- package/src/services/p2p-room.mjs +130 -9
- package/src/services/predator-prey.mjs +524 -69
- package/src/services/spider-fly-turn.mjs +128 -56
- package/src/services/spider-fly-viz.mjs +65 -71
- package/src/services/world-teach.mjs +3 -3
- package/src/surfaces/web/adventure-browser-entry.mjs +12 -3
- package/src/surfaces/web/memory-ask-browser.bundle.js +118 -118
- package/src/surfaces/web/mud-browser-entry.mjs +10 -3
- package/src/surfaces/web/mudiii-browser-entry.mjs +3 -3
- package/src/surfaces/web/spider-fly-browser-entry.mjs +26 -22
- package/src/services/spider-fly.mjs +0 -943
|
@@ -1,30 +1,85 @@
|
|
|
1
|
-
// spider-fly-turn.mjs — the
|
|
2
|
-
//
|
|
3
|
-
// memory store, the stop command, the addressed spatial teach-frame that
|
|
4
|
-
// feeds a told-fact into the next tick, and the bare "tick" command this
|
|
5
|
-
// game's no-player-controlled-entity posture (§1 — both agents move on
|
|
6
|
-
// their own, every turn) needs for CLI use. Mirrors adventure.mjs's own
|
|
7
|
-
// shape exactly: closed-regex openers/stop, a slot-tagged one-at-a-time
|
|
8
|
-
// coexistence check against the other two lanes, a lane function returning
|
|
9
|
-
// { text, goal?, lane, note, miss? } or null when the turn is not this
|
|
10
|
-
// lane's to answer. The fourth of the four lanes sharing planState's slot.
|
|
1
|
+
// spider-fly-turn.mjs — the spider-and-fly cast: its bindings onto the shared
|
|
2
|
+
// predator/prey engine, and its chat lane.
|
|
11
3
|
//
|
|
12
|
-
//
|
|
13
|
-
// game logic
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
4
|
+
// The bindings are the first section below, and they are the whole of what this
|
|
5
|
+
// game adds to predator-prey.mjs. Every piece of game logic — the (epoch, turn)
|
|
6
|
+
// fold, the pathfinding, belief, the decision chains, the ecology pass — is the
|
|
7
|
+
// shared engine's, run with this board's layout, this board's roles and this
|
|
8
|
+
// board's knobs. Carrying a catch to a web, spinning webs and laying eggs are
|
|
9
|
+
// three opt-in engine features spiderFlyEngineConfig switches on; nothing about
|
|
10
|
+
// them is written twice.
|
|
11
|
+
//
|
|
12
|
+
// The lane is everything after that: loading the shipped board into the
|
|
13
|
+
// session's memory store, the stop command, the addressed spatial teach-frame
|
|
14
|
+
// that feeds a told-fact into the next tick, and the bare "tick" command this
|
|
15
|
+
// game's no-player-controlled-entity posture (both agents move on their own,
|
|
16
|
+
// every turn) needs for CLI use. It mirrors adventure.mjs's own shape exactly:
|
|
17
|
+
// closed-regex openers/stop, a slot-tagged one-at-a-time coexistence check
|
|
18
|
+
// against the other lanes, a lane function returning { text, goal?, lane, note,
|
|
19
|
+
// miss? } or null when the turn is not this lane's to answer.
|
|
17
20
|
|
|
18
21
|
import {
|
|
19
|
-
DIRECTION_DELTA, WORLD_NAME,
|
|
22
|
+
DIRECTION_DELTA, WORLD_NAME, SPIDER_FLY_LAYOUT, SPIDER_FLY_ROLES, spiderFlyEngineConfig,
|
|
23
|
+
cellId, parseCellId, inBounds, chebyshevDistance, oneStepDirectionBetween,
|
|
20
24
|
agentKindOf, liveIdsOfKind,
|
|
21
25
|
} from "../domain/spider-fly-world.mjs";
|
|
22
|
-
import {
|
|
26
|
+
import { perimeterCells } from "../domain/town-square-world.mjs";
|
|
27
|
+
import {
|
|
28
|
+
beliefSnapshotFor, foldTownSquareState, liveWebs, runTownSquareTick, seededSpawnCell,
|
|
29
|
+
startTownSquareGame, townSquareBoard,
|
|
30
|
+
} from "./predator-prey.mjs";
|
|
23
31
|
import { worldProvenanceTag } from "../domain/worlds-pack.mjs";
|
|
24
32
|
import { getWorldsPackProvider } from "../adapters/corpus/worlds-pack.mjs";
|
|
25
33
|
import { appendFacts, appendRule, loadMemory, readFactRows } from "../adapters/memory/core.mjs";
|
|
26
34
|
import { DEFAULT_GAME_CONFIG } from "../domain/game-config.mjs";
|
|
27
35
|
|
|
36
|
+
// ---- the cast's bindings onto the shared engine ------------------------------
|
|
37
|
+
|
|
38
|
+
/** Fold fact rows into the current board state — the engine's own fold, named
|
|
39
|
+
* for the game whose readers ask for it. */
|
|
40
|
+
export const foldSpiderFlyState = foldTownSquareState;
|
|
41
|
+
|
|
42
|
+
export { beliefSnapshotFor, liveWebs };
|
|
43
|
+
|
|
44
|
+
const engineOpts = (config) => ({
|
|
45
|
+
layout: SPIDER_FLY_LAYOUT,
|
|
46
|
+
roles: SPIDER_FLY_ROLES,
|
|
47
|
+
config: spiderFlyEngineConfig(config),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/** Mint spider-1 in its web and a spread of flies around the board edge — a
|
|
51
|
+
* fresh session's own starting state, never part of the shipped (reusable,
|
|
52
|
+
* static) world pack. A no-op once this epoch already holds a roster, so it is
|
|
53
|
+
* safe to call from a caller unsure whether the game has started. The flies'
|
|
54
|
+
* cells are seeded picks over the same perimeter list, and off the same
|
|
55
|
+
* keyspace, a mid-game arrival draws from. */
|
|
56
|
+
export async function startSpiderFlyGame(memoryDir, { flyCount = 1, config = DEFAULT_GAME_CONFIG.spiderFly } = {}) {
|
|
57
|
+
const homeCell = SPIDER_FLY_LAYOUT.webHomeCell;
|
|
58
|
+
const agents = { "spider-1": { role: "predator", cell: homeCell } };
|
|
59
|
+
const edge = perimeterCells(SPIDER_FLY_LAYOUT);
|
|
60
|
+
const taken = new Set([homeCell]);
|
|
61
|
+
for (let i = 1; i <= flyCount; i += 1) {
|
|
62
|
+
const flyId = `fly-${i}`;
|
|
63
|
+
const free = edge.filter((c) => !taken.has(c));
|
|
64
|
+
const cell = seededSpawnCell(free.length ? free : edge, { layoutName: SPIDER_FLY_LAYOUT.name, id: flyId });
|
|
65
|
+
taken.add(cell);
|
|
66
|
+
agents[flyId] = { role: "prey", cell };
|
|
67
|
+
}
|
|
68
|
+
return startTownSquareGame(memoryDir, { ...engineOpts(config), agents });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** One full turn on this board. `toldFacts` is the belief layer's chat channel:
|
|
72
|
+
* `{ subject, toAgent, cell, turn }` rows, empty for an unaddressed tick. */
|
|
73
|
+
export function runSpiderFlyTick(memoryDir, { toldFacts = [], config = DEFAULT_GAME_CONFIG.spiderFly } = {}) {
|
|
74
|
+
return runTownSquareTick(memoryDir, { ...engineOpts(config), toldFacts });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The board as it stands, in a tick's own payload shape, without running one —
|
|
78
|
+
* what a renderer draws between opening a session and the first tick. */
|
|
79
|
+
export function spiderFlyBoard(memoryDir, { toldFacts = [], config = DEFAULT_GAME_CONFIG.spiderFly } = {}) {
|
|
80
|
+
return townSquareBoard(memoryDir, { ...engineOpts(config), toldFacts });
|
|
81
|
+
}
|
|
82
|
+
|
|
28
83
|
// ---- recognizers: the closed opening/stop/tick/address set -------------------
|
|
29
84
|
|
|
30
85
|
// The opener names the game without requiring a specific phrasing order —
|
|
@@ -42,15 +97,14 @@ const SPIDER_FLY_OPEN_RE =
|
|
|
42
97
|
// player thinks of the session.
|
|
43
98
|
const SPIDER_FLY_STOP_RE =
|
|
44
99
|
/^(?:stop\s+(?:watching|playing)|quit\s+(?:the\s+)?(?:spider\s+and\s+fly\s+)?game|end\s+the\s+spider(?:\s+and\s+fly)?\s+game|leave\s+the\s+game)[.!?\s]*$/i;
|
|
45
|
-
// The
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
// plan lane's own PLAN_NEXT_RE ("next"/"next move"/"go on"/"continue").
|
|
100
|
+
// The chat equivalent of the page's own Play/step button: nothing here
|
|
101
|
+
// requires the human to act, so a watcher needs a word that advances a turn.
|
|
102
|
+
// Styled after the plan lane's own PLAN_NEXT_RE ("next"/"next move"/"go on").
|
|
49
103
|
const SPIDER_FLY_TICK_RE = /^(?:tick|next\s+turn|advance(?:\s+the\s+turn)?)[.!?\s]*$/i;
|
|
50
104
|
|
|
51
|
-
// The spatial teach-frame
|
|
52
|
-
//
|
|
53
|
-
// parseRelation/parseCopula/parseOfForm —
|
|
105
|
+
// The spatial teach-frame: "@spider the fly is east" / "@spider the fly is at
|
|
106
|
+
// cell-7-3". Its own closed regex, not a route through
|
|
107
|
+
// parseRelation/parseCopula/parseOfForm — both hit real grammar
|
|
54
108
|
// gaps for this exact shape (the bare copula reading mints a nonsense
|
|
55
109
|
// subclass axiom; the "of" form hits parseAce's own hard-null guard before
|
|
56
110
|
// ever reaching parseRelation/parseCopula). A fixed compass set — north,
|
|
@@ -284,6 +338,21 @@ export function pillsForSpiderFly(agents, explicitAddresseeId, opts = {}) {
|
|
|
284
338
|
|
|
285
339
|
// ---- rendering one tick's return value as plain chat text --------------------
|
|
286
340
|
|
|
341
|
+
/** One ecology event as the clause a player reads. Null for an event type this
|
|
342
|
+
* board never produces, so a cast that grows one later reads as silence rather
|
|
343
|
+
* than as a broken sentence. */
|
|
344
|
+
function ecologyClause(event) {
|
|
345
|
+
switch (event.type) {
|
|
346
|
+
case "catch-prey": return `${event.predator} caught ${event.prey} at ${event.cell}`;
|
|
347
|
+
case "eat-agent": return `${event.prey} was eaten by ${event.predator} at ${event.cell}`;
|
|
348
|
+
case "starve": return `${event.agent} starved`;
|
|
349
|
+
case "lay-egg": return `${event.egg} was laid`;
|
|
350
|
+
case "hatch-egg": return `${event.egg} hatched into ${event.hatchlings.map((h) => h.id).join(" and ")} at ${event.cell}`;
|
|
351
|
+
case "spawn-prey": return `${event.agent} arrived at the board edge`;
|
|
352
|
+
default: return null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
287
356
|
function renderTickText(tick, addressedNote) {
|
|
288
357
|
const parts = [];
|
|
289
358
|
if (addressedNote) parts.push(`${addressedNote}.`);
|
|
@@ -291,15 +360,8 @@ function renderTickText(tick, addressedNote) {
|
|
|
291
360
|
parts.push(ids.length
|
|
292
361
|
? `Turn ${tick.turn} — ${ids.map((id) => `${id} is now at ${tick.agents[id].cell}`).join("; ")}.`
|
|
293
362
|
: `Turn ${tick.turn} — no agents remain on the board.`);
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
for (const c of eco.caught) events.push(`${c.spider} caught ${c.fly} at ${c.cell}`);
|
|
297
|
-
for (const e of eco.eaten) events.push(`${e.fly} was eaten by ${e.spider} at ${e.cell}`);
|
|
298
|
-
for (const f of eco.starved) events.push(`${f} starved`);
|
|
299
|
-
if (eco.laid) events.push(`${eco.laid} was laid`);
|
|
300
|
-
for (const h of eco.hatched) events.push(`${h.egg} hatched into ${h.spiders.map((s) => s.spider).join(" and ")} at ${h.cell}`);
|
|
301
|
-
if (eco.spawned) events.push(`${eco.spawned} arrived at the board edge`);
|
|
302
|
-
if (events.length) parts.push(`${events.join("; ")}.`);
|
|
363
|
+
const clauses = tick.ecology.map(ecologyClause).filter(Boolean);
|
|
364
|
+
if (clauses.length) parts.push(`${clauses.join("; ")}.`);
|
|
303
365
|
return parts.join(" ");
|
|
304
366
|
}
|
|
305
367
|
|
|
@@ -316,14 +378,26 @@ function combinedGoalLine(agents) {
|
|
|
316
378
|
return ids.map((id) => `${id} — ${agents[id].goal.replace(/\.\s*$/, "")}`).join("; ");
|
|
317
379
|
}
|
|
318
380
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
381
|
+
const ECOLOGY_NOTE_WORDS = Object.freeze({
|
|
382
|
+
"catch-prey": "caught",
|
|
383
|
+
"eat-agent": "eaten",
|
|
384
|
+
starve: "starved",
|
|
385
|
+
"lay-egg": "laid",
|
|
386
|
+
"hatch-egg": "hatched",
|
|
387
|
+
"spawn-prey": "spawned",
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
function describeEcologyNote(events) {
|
|
391
|
+
const tally = new Map();
|
|
392
|
+
for (const event of events) {
|
|
393
|
+
const word = ECOLOGY_NOTE_WORDS[event.type];
|
|
394
|
+
if (!word) continue;
|
|
395
|
+
const n = event.type === "hatch-egg" ? event.hatchlings.length : 1;
|
|
396
|
+
tally.set(word, (tally.get(word) ?? 0) + n);
|
|
397
|
+
}
|
|
398
|
+
const bits = Object.values(ECOLOGY_NOTE_WORDS)
|
|
399
|
+
.filter((word) => tally.has(word))
|
|
400
|
+
.map((word) => `${tally.get(word)} ${word}`);
|
|
327
401
|
return bits.length ? `; ${bits.join(", ")}` : "";
|
|
328
402
|
}
|
|
329
403
|
|
|
@@ -352,8 +426,8 @@ export function believedFactSentence(id, believedCell) {
|
|
|
352
426
|
}
|
|
353
427
|
|
|
354
428
|
/** "what does the fly see?" / "what does the spider see?" rendered as plain
|
|
355
|
-
* text: the same beliefSnapshotFor read
|
|
356
|
-
*
|
|
429
|
+
* text: the same beliefSnapshotFor read the engine's own tick loop and the
|
|
430
|
+
* browser panel already use, over the CURRENT board state — read-only,
|
|
357
431
|
* no tick runs, nothing is written. Candidates are every OTHER live agent
|
|
358
432
|
* of either kind; toldFacts is empty (a told position only ever arrives
|
|
359
433
|
* fresh alongside a tick — see runToldFactTurn — so there is none standing
|
|
@@ -385,11 +459,10 @@ async function spiderFlyBeliefAnswer(match, { memoryDir, gameConfig = DEFAULT_GA
|
|
|
385
459
|
/** The addressed teach-frame turn: resolve the addressee and the belief
|
|
386
460
|
* subject, resolve the told cell, and run ONE tick with that told-fact fed
|
|
387
461
|
* in. Told-facts are NOT persisted on the session slot across turns — each
|
|
388
|
-
* addressed line supplies belief for the NEXT tick only, then is gone
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
* recomputing everything fresh from the folded fact rows every tick. */
|
|
462
|
+
* addressed line supplies belief for the NEXT tick only, then is gone. That
|
|
463
|
+
* matches how the engine itself already works: it holds no standing plan or
|
|
464
|
+
* belief between calls, recomputing everything fresh from the folded fact
|
|
465
|
+
* rows every tick. */
|
|
393
466
|
async function runToldFactTurn(match, { planHolder, memoryDir, cache, gameConfig = DEFAULT_GAME_CONFIG }) {
|
|
394
467
|
const [, addrKindRaw, addrNum, subjKindRaw, subjNum, direction, cellLiteral] = match;
|
|
395
468
|
const addrKind = addrKindRaw.toLowerCase();
|
|
@@ -414,7 +487,7 @@ async function runToldFactTurn(match, { planHolder, memoryDir, cache, gameConfig
|
|
|
414
487
|
}
|
|
415
488
|
|
|
416
489
|
const targetCellId = cellId(targetCell.x, targetCell.y);
|
|
417
|
-
const toldFacts = [{ subject: subjectId, toAgent: addresseeId, cell: targetCellId, turn: state.
|
|
490
|
+
const toldFacts = [{ subject: subjectId, toAgent: addresseeId, cell: targetCellId, turn: state.tickCount + 1 }];
|
|
418
491
|
return runTickAndRender({
|
|
419
492
|
planHolder, memoryDir, cache, toldFacts, gameConfig,
|
|
420
493
|
addressedNote: `told the ${addresseeId} the ${subjectId} is at ${targetCellId}`,
|
|
@@ -487,13 +560,12 @@ async function spiderFlyContextAnswer(line, { memoryDir }) {
|
|
|
487
560
|
|
|
488
561
|
/**
|
|
489
562
|
* The whole spider-and-fly lane for one turn: the opening moves, the stop
|
|
490
|
-
* command, the addressed spatial teach-frame
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
*
|
|
496
|
-
* spider-fly code path for plain questions).
|
|
563
|
+
* command, the addressed spatial teach-frame, the bare tick command, and the
|
|
564
|
+
* one-at-a-time declines across the shared plan slot the other lanes already
|
|
565
|
+
* implement pairwise. Returns { text, lane, note, goal?, miss? } or null when
|
|
566
|
+
* the turn is not this lane's to answer — an unaddressed aside (e.g. "where is
|
|
567
|
+
* the spider") falls through to the ordinary lanes unchanged, board untouched,
|
|
568
|
+
* with no special-cased code path for plain questions.
|
|
497
569
|
*/
|
|
498
570
|
export async function spiderFlyTurn(line, { planHolder, memoryDir, env, cache = null, isPlanFrameLine = () => false, gameConfig = DEFAULT_GAME_CONFIG }) {
|
|
499
571
|
const slot = planHolder?.state ?? null;
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
// spider-fly-viz.mjs — the spider-and-fly full-screen page
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// portable on its own — that reason doesn't apply here).
|
|
1
|
+
// spider-fly-viz.mjs — the spider-and-fly full-screen page: a self-contained
|
|
2
|
+
// document shaped exactly like ledger-viz.mjs/plan-viz.mjs — one inlined <style>
|
|
3
|
+
// (importing viz-theme.mjs's shared tokens), behaviour as an inlined IIFE — but
|
|
4
|
+
// unlike those two, almost nothing is embedded as build-time data: the whole
|
|
5
|
+
// game is LIVE client-side state (spider-fly-browser-entry.mjs's
|
|
6
|
+
// createSpiderFlySession), so renderSpiderFlyHtml only needs the page's own
|
|
7
|
+
// static grid geometry (which never changes) plus a title. The engine, sprite
|
|
8
|
+
// resolver and chat turn engine all arrive via ./spider-fly-browser.bundle.js,
|
|
9
|
+
// referenced with a plain same-origin <script src> — the same sibling-file
|
|
10
|
+
// arrangement index.html already uses for chat-browser.bundle.js (both are the
|
|
11
|
+
// FULL turn engine, both generated fresh per build, neither meant to be
|
|
12
|
+
// committed), not memory-ask-browser.bundle.js's inlined-text arrangement (that
|
|
13
|
+
// bundle is small and inlined specifically so ledger.html stays portable on its
|
|
14
|
+
// own — that reason doesn't apply here).
|
|
16
15
|
//
|
|
17
16
|
// renderSpiderFlyHtml() is pure: no I/O, deterministic output for identical
|
|
18
17
|
// input. scripts/build-demo-site.mjs calls it directly and writes the result
|
|
@@ -37,7 +36,6 @@ import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml, embedJson, embed
|
|
|
37
36
|
import { createTicker } from "./viz-ticker.mjs";
|
|
38
37
|
import { loadWinkVendor } from "./viz-boot.mjs";
|
|
39
38
|
import { GRID_SIZE, WEB_HOME, WEB_RADIUS, isInWebBlock, cellId, agentKindOf } from "../domain/spider-fly-world.mjs";
|
|
40
|
-
import { FLY_INITIAL_MASS, EGG_LAY_MASS_THRESHOLD } from "./spider-fly.mjs";
|
|
41
39
|
import { believedFactSentence } from "./spider-fly-turn.mjs";
|
|
42
40
|
import { DEFAULT_GAME_CONFIG, massScaleFor } from "../domain/game-config.mjs";
|
|
43
41
|
import { resolveSpriteRequest } from "../domain/sprite-request.mjs";
|
|
@@ -69,11 +67,11 @@ function webCellIds() {
|
|
|
69
67
|
/**
|
|
70
68
|
* Reconstruct the spider's remaining silk-thread path — the sequence of
|
|
71
69
|
* cell ids from its CURRENT cell (after this tick's one executed step) to
|
|
72
|
-
* wherever `findActionPath` was aiming — from spider-fly.mjs's own
|
|
70
|
+
* wherever `findActionPath` was aiming — from spider-fly-turn.mjs's own
|
|
73
71
|
* `agents[spiderId]` shape ({ cell, plan }), where `plan` is the FULL
|
|
74
72
|
* direction list `findActionPath` returned (the step already taken this
|
|
75
73
|
* tick, `plan[0]`, plus every step still to come). Only a spider with a
|
|
76
|
-
* REAL multi-step plan draws a thread at all: spider-fly.mjs's own
|
|
74
|
+
* REAL multi-step plan draws a thread at all: spider-fly-turn.mjs's own
|
|
77
75
|
* `planSpiderPath` only ever returns one when the believed fly cell sits
|
|
78
76
|
* inside the web block (its `isGoal`'s own requirement) — most ticks the
|
|
79
77
|
* spider is greedily closing distance with no such plan, and this
|
|
@@ -104,7 +102,7 @@ export function threadCellsForSpiderPlan(agents, geometry) {
|
|
|
104
102
|
}
|
|
105
103
|
|
|
106
104
|
/** The sprite-facing rotation (degrees) for one agent this tick, driven by
|
|
107
|
-
* its CURRENT plan's first step (spider-fly.mjs's own `agents[id].plan`),
|
|
105
|
+
* its CURRENT plan's first step (spider-fly-turn.mjs's own `agents[id].plan`),
|
|
108
106
|
* never its actual next move — the two usually coincide, but re-planning
|
|
109
107
|
* fresh every tick means they can visibly diverge as a plan gets clobbered
|
|
110
108
|
* and replaced, which is the intended, honest demonstration of "plans get
|
|
@@ -126,19 +124,17 @@ export function facingDegreesFor(plan, previousDegrees) {
|
|
|
126
124
|
}
|
|
127
125
|
|
|
128
126
|
/**
|
|
129
|
-
* The updated corpse set for one redraw
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* CORPSE_LINGER_TURNS's own callers elsewhere in this module) passes it
|
|
141
|
-
* explicitly anyway.
|
|
127
|
+
* The updated corpse set for one redraw — visual-only, entirely client-side;
|
|
128
|
+
* the actual starve/eat removal already happened in the engine. Every id
|
|
129
|
+
* present in `prevAgents` but absent from `agents` died THIS tick — eaten or
|
|
130
|
+
* starved are the only two ways an agent ever leaves the engine's own returned
|
|
131
|
+
* roster — and is added at its last-known cell and class; every corpse already
|
|
132
|
+
* older than `lingerTurns` past its own death turn is dropped first, so the set
|
|
133
|
+
* never grows without bound. Returns a plain `{ [id]: { cls, cell, diedAtTurn }
|
|
134
|
+
* }` map. Pure. `lingerTurns` defaults to a literal 4 (not the module-level
|
|
135
|
+
* CORPSE_LINGER_TURNS constant) so this function stays fully `.toString()`-splice
|
|
136
|
+
* safe — every real caller (both the inlined page and CORPSE_LINGER_TURNS's own
|
|
137
|
+
* callers elsewhere in this module) passes it explicitly anyway.
|
|
142
138
|
*/
|
|
143
139
|
export function nextCorpses(prevCorpses, prevAgents, agents, turn, lingerTurns = 4) {
|
|
144
140
|
const out = {};
|
|
@@ -167,9 +163,9 @@ export function nextCorpses(prevCorpses, prevAgents, agents, turn, lingerTurns =
|
|
|
167
163
|
* back to the flat SPRITE_REGISTRY, unchanged from before this module
|
|
168
164
|
* existed).
|
|
169
165
|
* `?preview=1` on the page's own URL switches it into the small, auto-
|
|
170
|
-
* playing, non-interactive mode the home page's hero iframe embeds
|
|
171
|
-
*
|
|
172
|
-
*
|
|
166
|
+
* playing, non-interactive mode the home page's hero iframe embeds — one file
|
|
167
|
+
* serves both the hero and the "open full-screen" link, matching how
|
|
168
|
+
* ledger.html/plan.html are each one file embedded two ways.
|
|
173
169
|
* `engineBundleJs` (the built spider-fly-browser bundle's own text) inlines
|
|
174
170
|
* the engine into the page instead of the sibling `<script src>`, for the
|
|
175
171
|
* CLI's standalone export — one downloadable file that runs from file://
|
|
@@ -188,13 +184,11 @@ export function renderSpiderFlyHtml({ title = DEFAULT_TITLE, spriteTemplates = [
|
|
|
188
184
|
previewMaxTurns: PREVIEW_MAX_TURNS,
|
|
189
185
|
tickWaitMs: TICK_WAIT_MS,
|
|
190
186
|
corpseLingerTurns: CORPSE_LINGER_TURNS,
|
|
191
|
-
maxFlyMass:
|
|
192
|
-
// The spider's mass bar
|
|
193
|
-
// its own starting mass
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
// the spider's actual goal.
|
|
197
|
-
maxSpiderMass: EGG_LAY_MASS_THRESHOLD,
|
|
187
|
+
maxFlyMass: DEFAULT_GAME_CONFIG.spiderFly.flyInitialMass,
|
|
188
|
+
// The spider's mass bar scales against the egg-lay threshold rather than
|
|
189
|
+
// its own starting mass: "how close to laying" is what a viewer wants to
|
|
190
|
+
// read, and a flat starting mass says nothing about progress toward it.
|
|
191
|
+
maxSpiderMass: DEFAULT_GAME_CONFIG.spiderFly.eggLayMassThreshold,
|
|
198
192
|
defaultConfig: DEFAULT_GAME_CONFIG.spiderFly,
|
|
199
193
|
spriteTemplates,
|
|
200
194
|
});
|
|
@@ -347,11 +341,11 @@ ${THEME_TOKENS_CSS}
|
|
|
347
341
|
.hud-id.spider { color: var(--taught); } .hud-id.fly { color: var(--fly); } .hud-id.egg { color: var(--muted); }
|
|
348
342
|
.hud-goal { font-size: .85rem; }
|
|
349
343
|
.hud-plan, .hud-belief { font-family: ${MONO_STACK}; font-size: .66rem; color: var(--muted); margin-top: .25rem; line-height: 1.4; padding-left: .5rem; border-left: 2px solid var(--chrome-accent); }
|
|
350
|
-
/* the click-expand facts panel
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
344
|
+
/* the click-expand facts panel: beside the clicked spider/fly's own row,
|
|
345
|
+
never a separate popover or a second panel elsewhere on the page — the same
|
|
346
|
+
believedCellOf/beliefSnapshotFor read path spider-fly-turn.mjs already
|
|
347
|
+
computes every tick for planning, rendered here as full sentences instead of
|
|
348
|
+
the compact believes:-line above. */
|
|
355
349
|
.hud-detail { flex: 1 1 auto; min-width: 0; font-family: ${MONO_STACK}; font-size: .64rem; line-height: 1.5; color: var(--chrome-well-ink); background: var(--chrome-well); border: 1px solid var(--chrome-edge-lo); box-shadow: var(--chrome-shadow-inset); border-radius: 2px; padding: .35rem .5rem; }
|
|
356
350
|
.hud-detail-title { text-transform: uppercase; letter-spacing: .06em; opacity: .75; margin-bottom: .2rem; }
|
|
357
351
|
/* A stat-readout track: an inset "LCD" well, filled with a segmented pip
|
|
@@ -381,11 +375,11 @@ ${THEME_TOKENS_CSS}
|
|
|
381
375
|
.pill:hover:not(:disabled) { border-color: var(--chrome-accent); }
|
|
382
376
|
.pill:disabled { opacity: .45; cursor: default; }
|
|
383
377
|
.pill[data-role="addr"].active { border-color: var(--taught); color: var(--taught); }
|
|
384
|
-
/* The dynamic deception-pill rail
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
378
|
+
/* The dynamic deception-pill rail: a true/false tag shown ONLY here, via
|
|
379
|
+
border style/color and a small human-facing glyph — the submitted sentence
|
|
380
|
+
itself (data-sentence, filled into #chatq on click) never carries the tag,
|
|
381
|
+
so a clicked pill is indistinguishable from a hand-typed claim once it's in
|
|
382
|
+
the input. */
|
|
389
383
|
.dynpills { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .5rem; padding-top: .5rem; border-top: 1px solid var(--chrome-edge-lo); }
|
|
390
384
|
.dynpills:empty { display: none; padding-top: 0; border-top: none; }
|
|
391
385
|
.pill[data-role="dyn-addr"][data-active="1"] { border-color: var(--taught); color: var(--taught); }
|
|
@@ -748,13 +742,13 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
748
742
|
lastAgents = agents;
|
|
749
743
|
}
|
|
750
744
|
|
|
751
|
-
// ---- corpses
|
|
752
|
-
//
|
|
753
|
-
//
|
|
754
|
-
//
|
|
755
|
-
//
|
|
756
|
-
//
|
|
757
|
-
//
|
|
745
|
+
// ---- corpses: visual-only — the actual starve/eat removal already happened
|
|
746
|
+
// in the engine before this redraw ever sees "agents". A corpse sinks to the
|
|
747
|
+
// bottom row of the SAME board column it died in ("drops to the bottom" —
|
|
748
|
+
// spider-fly-world.mjs's own x/y convention, y = GRID_SIZE is the bottom row)
|
|
749
|
+
// and fades out once CORPSE_LINGER_TURNS passes, drawn in the same sprite
|
|
750
|
+
// layer as every live sprite, just grayscaled and non-interactive (see the
|
|
751
|
+
// .sprite.corpse CSS rule).
|
|
758
752
|
function renderCorpses() {
|
|
759
753
|
for (const id of Object.keys(corpseEls)) {
|
|
760
754
|
if (!corpses[id]) { corpseEls[id].remove(); delete corpseEls[id]; }
|
|
@@ -796,12 +790,12 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
796
790
|
return '<div class="hud-plan">plan: ' + text + ".</div>";
|
|
797
791
|
}
|
|
798
792
|
|
|
799
|
-
// The agent's own current world-knowledge-graph snapshot
|
|
800
|
-
//
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
//
|
|
804
|
-
//
|
|
793
|
+
// The agent's own current world-knowledge-graph snapshot — every OTHER live
|
|
794
|
+
// individual it believes it knows the position of, or "unseen" when it has no
|
|
795
|
+
// belief at all. Deliberately never ground truth: this is what the agent
|
|
796
|
+
// would actually ACT on, which a false pill or a told fact can make visibly
|
|
797
|
+
// wrong compared to where that individual really is — the gap IS the
|
|
798
|
+
// demonstration.
|
|
805
799
|
function beliefLineHtml(belief) {
|
|
806
800
|
const entries = Object.entries(belief || {});
|
|
807
801
|
if (!entries.length) return "";
|
|
@@ -1026,12 +1020,12 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
1026
1020
|
chatqEl.addEventListener("input", refreshPills);
|
|
1027
1021
|
refreshPills();
|
|
1028
1022
|
|
|
1029
|
-
// ---- deception pills
|
|
1030
|
-
//
|
|
1031
|
-
//
|
|
1032
|
-
//
|
|
1033
|
-
//
|
|
1034
|
-
//
|
|
1023
|
+
// ---- deception pills: a SEPARATE dynamic rail, alongside (never replacing)
|
|
1024
|
+
// the static one above. tmct.page.pillsForSpiderFly is the exact same pure
|
|
1025
|
+
// function spider-fly-turn.mjs exports — this page never reimplements the
|
|
1026
|
+
// true/false claim logic, only renders its output and fills #chatq on click,
|
|
1027
|
+
// same click-to-fill discipline as every other pill on this page (never
|
|
1028
|
+
// auto-submits).
|
|
1035
1029
|
function renderDynamicPills() {
|
|
1036
1030
|
if (!session || !Object.keys(lastAgents).length) { dynamicPillsEl.innerHTML = ""; return; }
|
|
1037
1031
|
const result = tmct.page.pillsForSpiderFly(lastAgents, selectedAddresseeId, {});
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
// page's own edit mode already makes.
|
|
35
35
|
import { appendFacts, loadMemory, readFactRows } from "../adapters/memory/core.mjs";
|
|
36
36
|
import { worldProvenanceTag } from "../domain/worlds-pack.mjs";
|
|
37
|
-
import { correctMisspellings } from "../domain/interpret/normalize.mjs";
|
|
37
|
+
import { correctMisspellings, QUESTION_LEAD_RE } from "../domain/interpret/normalize.mjs";
|
|
38
38
|
import {
|
|
39
39
|
classMassFacts, foldWorldState, freshObjectId, snapshotSubject, worldActionRows, worldRelook,
|
|
40
40
|
} from "./adventure.mjs";
|
|
@@ -44,8 +44,8 @@ import {
|
|
|
44
44
|
// word earlier, run over the closed misspelling repair so a typo'd "wat is the
|
|
45
45
|
// lamp" goes back to the question side rather than reading as a declarative.
|
|
46
46
|
// Both mirror chat.mjs's own teach lane, which stands the whole lane down on
|
|
47
|
-
// either — a world teach has exactly the same reason to
|
|
48
|
-
|
|
47
|
+
// either — a world teach has exactly the same reason to (see
|
|
48
|
+
// normalize.mjs's QUESTION_LEAD_RE).
|
|
49
49
|
|
|
50
50
|
// A subject with no referent of its own. The generic "X is a Y." fallback in
|
|
51
51
|
// both sentence tables would otherwise read "There is a book in the study" as
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
44
44
|
import { memoryFactGraphPayload } from "../../domain/memory-facts.mjs";
|
|
45
45
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
46
|
+
import { DEFAULT_GAME_CONFIG } from "../../domain/game-config.mjs";
|
|
46
47
|
import { foldWorldState, worldDigestRows, roomAffordances, worldActionRows, snapshotSubject } from "../../services/adventure.mjs";
|
|
47
48
|
import { runAdventureAutoplayTick, exposedFacts } from "../../services/adventure-autoplay.mjs";
|
|
48
49
|
import { parseWorldEditorText, planWorldEditorSync } from "../../services/adventure-editor.mjs";
|
|
@@ -66,8 +67,13 @@ import { openPersistedStore } from "./idb-persist.mjs";
|
|
|
66
67
|
* already carries them, plus every @turnN state row played since, so the
|
|
67
68
|
* world resumes exactly where the fold left it. `restoredVisitedRoomIds`
|
|
68
69
|
* carries the matching exposure set forward; without it, only the player's
|
|
69
|
-
* current room counts as visited.
|
|
70
|
-
|
|
70
|
+
* current room counts as visited.
|
|
71
|
+
*
|
|
72
|
+
* `getTeachEnabled` (optional) is read fresh on every turn, never once at
|
|
73
|
+
* boot — the page's own teach checkbox, so flipping it mid-game changes the
|
|
74
|
+
* very next line's reading without a reset. Defaults to always-off, which is
|
|
75
|
+
* DEFAULT_GAME_CONFIG.adventure.teach's own default. */
|
|
76
|
+
export async function createAdventureSession(worldPayload, { restoredPayload = null, restoredVisitedRoomIds = null, getTeachEnabled = () => false } = {}) {
|
|
71
77
|
const memoryDir = createInMemoryStore();
|
|
72
78
|
const tag = `world:${worldPayload.name}`;
|
|
73
79
|
if (restoredPayload) {
|
|
@@ -134,7 +140,10 @@ export async function createAdventureSession(worldPayload, { restoredPayload = n
|
|
|
134
140
|
const turnSession = createTurnSession({
|
|
135
141
|
memoryDir, graph: codeGraph, lexicon, sessionId,
|
|
136
142
|
vocabHint: 'Try a world question ("where is the key"), or teach me: "remember: the moat is a ditch".',
|
|
137
|
-
buildExtraOptions: () => ({
|
|
143
|
+
buildExtraOptions: () => ({
|
|
144
|
+
planState: planHolder.state,
|
|
145
|
+
gameConfig: { ...DEFAULT_GAME_CONFIG, adventure: { ...DEFAULT_GAME_CONFIG.adventure, teach: getTeachEnabled() } },
|
|
146
|
+
}),
|
|
138
147
|
captureExtraState: async (result) => {
|
|
139
148
|
if ("planState" in result) planHolder.state = result.planState;
|
|
140
149
|
const here = foldWorldState(worldActionRows(readFactRows(await loadMemory(memoryDir)))).placements.get("player")?.object ?? null;
|