@polycode-projects/the-mechanical-code-talker 4.0.1 → 4.1.0
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 -1
- package/corpus/sprites/src/sprite-facts.jsonl +375 -8
- package/package.json +1 -1
- package/src/adapters/memory/core.mjs +20 -0
- package/src/domain/ask-vocab.mjs +71 -0
- package/src/domain/ask.mjs +168 -0
- package/src/domain/game-config.mjs +11 -0
- package/src/domain/mud-facts.mjs +15 -0
- package/src/domain/router/drive.mjs +35 -9
- package/src/domain/router/registry.mjs +24 -4
- package/src/domain/router/resolver.mjs +102 -40
- package/src/domain/scene-compose.mjs +117 -0
- package/src/domain/spider-fly-world.mjs +36 -0
- package/src/domain/sprite-facts.mjs +0 -0
- package/src/domain/sprite-request.mjs +156 -0
- package/src/domain/sprite-templates.mjs +161 -14
- package/src/services/adventure-editor.mjs +8 -14
- package/src/services/adventure-viz.mjs +90 -121
- package/src/services/adventure.mjs +97 -35
- package/src/services/chat-page-viz.mjs +32 -16
- package/src/services/chat.mjs +101 -33
- package/src/services/code-explorer-viz.mjs +51 -49
- package/src/services/ingest-viz.mjs +15 -57
- package/src/services/ledger-viz.mjs +47 -27
- package/src/services/memory-panel-viz.mjs +38 -0
- package/src/services/mud-editor.mjs +10 -15
- package/src/services/mud-turn.mjs +6 -6
- package/src/services/mud-viz.mjs +87 -198
- package/src/services/p2p-room.mjs +90 -23
- package/src/services/plan-pddl.mjs +3 -1
- package/src/services/plan-viz.mjs +4 -3
- package/src/services/research-viz.mjs +10 -52
- package/src/services/spider-fly-turn.mjs +14 -22
- package/src/services/spider-fly-viz.mjs +79 -118
- package/src/services/spider-fly.mjs +69 -11
- package/src/services/sprite-catalog-viz.mjs +271 -221
- package/src/services/viz-boot.mjs +71 -0
- package/src/services/viz-room-graph.mjs +203 -0
- package/src/services/viz-theme.mjs +75 -1
- package/src/services/viz-ticker.mjs +22 -0
- package/src/surfaces/web/adventure-browser-entry.mjs +49 -33
- package/src/surfaces/web/chat-browser-entry.mjs +30 -105
- package/src/surfaces/web/code-explorer-browser-entry.mjs +168 -24
- package/src/surfaces/web/ingest-browser-entry.mjs +3 -13
- package/src/surfaces/web/ledger-browser-entry.mjs +7 -47
- package/src/surfaces/web/memory-ask-browser.bundle.js +128 -125
- package/src/surfaces/web/memory-stats.mjs +11 -0
- package/src/surfaces/web/mud-browser-entry.mjs +28 -28
- package/src/surfaces/web/plan-browser-entry.mjs +22 -40
- package/src/surfaces/web/research-browser-entry.mjs +26 -41
- package/src/surfaces/web/spider-fly-browser-entry.mjs +45 -28
- package/src/surfaces/web/sprites-browser-entry.mjs +14 -27
- package/src/surfaces/web/turn-session.mjs +120 -0
- package/src/tools/definitions.mjs +30 -0
- package/src/tools/handlers/index.mjs +6 -3
- package/src/tools/handlers/kit.mjs +19 -2
- package/src/tools/handlers/tmct-ask.mjs +11 -6
- package/src/tools/handlers/tmct-ingest.mjs +5 -1
- package/src/tools/handlers/tmct-related.mjs +4 -4
- package/src/tools/handlers/tmct-sprite.mjs +147 -0
- package/src/tools/memory-fallthrough.mjs +9 -2
- package/src/tools/server.mjs +25 -1
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// inline script degrades honestly when that sibling script is absent or
|
|
19
19
|
// fails to load — the live controls disable themselves rather than pretend
|
|
20
20
|
// to work.
|
|
21
|
-
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml, embedJson } from "./viz-theme.mjs";
|
|
21
|
+
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml, embedJson, countLabel } from "./viz-theme.mjs";
|
|
22
22
|
import { planToPddl } from "./plan-pddl.mjs";
|
|
23
23
|
|
|
24
24
|
const BOARD_W = 640;
|
|
@@ -285,7 +285,7 @@ export function renderPlanHtml({ plan, rendersAs = {}, sizeOrder = [], title } =
|
|
|
285
285
|
const pageData = planToPageData({ plan, rendersAs, sizeOrder });
|
|
286
286
|
const embedded = embedJson(pageData);
|
|
287
287
|
const pddlText = planToPddl(plan);
|
|
288
|
-
const pageTitle = title || `tmct plan — ${actions.length
|
|
288
|
+
const pageTitle = title || `tmct plan — ${countLabel(actions.length, "move")}`;
|
|
289
289
|
|
|
290
290
|
return `<!doctype html>
|
|
291
291
|
<!-- data-theme is pinned to dark on purpose: this page reads as a track/
|
|
@@ -483,6 +483,7 @@ const PLAN = ${embedded};
|
|
|
483
483
|
(function () {
|
|
484
484
|
"use strict";
|
|
485
485
|
const escapeHtml = ${escapeHtml.toString()};
|
|
486
|
+
const countLabel = ${countLabel.toString()};
|
|
486
487
|
// Best-effort: a copy of this page opened without the sibling worker file
|
|
487
488
|
// (a tmct --render plan --output file, a file:// open) just swallows the
|
|
488
489
|
// registration failure and works exactly as before.
|
|
@@ -759,7 +760,7 @@ const PLAN = ${embedded};
|
|
|
759
760
|
session = await tmctPlan.createPlanSession({ diskCount: n, maxDepth: d });
|
|
760
761
|
if (session.plan) {
|
|
761
762
|
applyPlan(session.plan);
|
|
762
|
-
liveStatusEl.textContent = "live — " + n
|
|
763
|
+
liveStatusEl.textContent = "live — " + countLabel(n, "disk", "disks") + ", max depth " + d + ".";
|
|
763
764
|
} else {
|
|
764
765
|
liveStatusEl.textContent = "no plan found within " + d + " moves — raise max search depth and try again.";
|
|
765
766
|
liveStatusEl.classList.add("isError");
|
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
// input. scripts/build-demo-site.mjs calls it directly and writes the result to
|
|
21
21
|
// public/research.html, after research-browser.bundle.js already exists.
|
|
22
22
|
import { THEME_TOKENS_CSS, MONO_STACK, escapeHtml } from "./viz-theme.mjs";
|
|
23
|
-
import { fetchWithProgress } from "./memory-panel-viz.mjs";
|
|
23
|
+
import { fetchWithProgress, loadProgressLine, factTripleParts } from "./memory-panel-viz.mjs";
|
|
24
24
|
import { createTicker, prefersReducedMotion } from "./viz-ticker.mjs";
|
|
25
|
+
import { loadWinkVendor } from "./viz-boot.mjs";
|
|
26
|
+
import { cloneMemoryPayload } from "../adapters/memory/core.mjs";
|
|
25
27
|
|
|
26
28
|
const DEFAULT_TITLE = "the-mechanical-code-talker — research";
|
|
27
29
|
|
|
@@ -63,35 +65,6 @@ export function sourceLabelFor(source) {
|
|
|
63
65
|
return { label: (BANDS[band] || band || "seed") + " (seed corpus)", tone: "seed" };
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
/** One learned fact as its three canonical cells plus a source tone, for the
|
|
67
|
-
* highlights and history lists. Pure, `.toString()`-splice safe. */
|
|
68
|
-
export function factTripleParts(fact) {
|
|
69
|
-
return {
|
|
70
|
-
subject: String((fact && fact.subject) || ""),
|
|
71
|
-
predicate: String((fact && fact.predicate) || ""),
|
|
72
|
-
object: String((fact && fact.object) || ""),
|
|
73
|
-
source: String((fact && fact.source) || ""),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** The boot statusline while the big assets stream in — the same aggregator
|
|
78
|
-
* ingest-viz.mjs's own loadProgressLine is. `parts` is an array of
|
|
79
|
-
* { loaded, total } byte counts. Self-contained, `.toString()`-splice safe. */
|
|
80
|
-
export function loadProgressLine(parts) {
|
|
81
|
-
const mb = (n) => (n / 1048576).toFixed(1);
|
|
82
|
-
let loaded = 0;
|
|
83
|
-
let total = 0;
|
|
84
|
-
let totalKnown = true;
|
|
85
|
-
for (const p of parts || []) {
|
|
86
|
-
loaded += (p && p.loaded) || 0;
|
|
87
|
-
if (p && p.total > 0) total += p.total;
|
|
88
|
-
else totalKnown = false;
|
|
89
|
-
}
|
|
90
|
-
return totalKnown && total > 0
|
|
91
|
-
? "loading the engine… " + mb(loaded) + " MB / " + mb(total) + " MB"
|
|
92
|
-
: "loading the engine… " + mb(loaded) + " MB";
|
|
93
|
-
}
|
|
94
|
-
|
|
95
68
|
/** The self-contained research page. Pure — the same output for the same
|
|
96
69
|
* input every time; every piece of state is computed live in the browser once
|
|
97
70
|
* the sibling research bundle loads. `digestStructures` are the pre-parsed
|
|
@@ -136,7 +109,7 @@ ${DASH_DARK_CHROME_CSS}
|
|
|
136
109
|
.brand { display: flex; flex-direction: column; gap: .3rem; max-width: 640px; }
|
|
137
110
|
.eyebrow { font-family: ${MONO_STACK}; font-size: .72rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
|
|
138
111
|
.subtitle { font-size: .92rem; color: var(--ink); opacity: .82; max-width: 58ch; }
|
|
139
|
-
.statuspanel { display: flex; gap: 1.1rem; background: var(--card); border: 1px solid var(--line); border-radius: 6px; padding: .5rem .9rem; }
|
|
112
|
+
.statuspanel { display: flex; flex-wrap: wrap; gap: .5rem 1.1rem; background: var(--card); border: 1px solid var(--line); border-radius: 6px; padding: .5rem .9rem; }
|
|
140
113
|
.statuspanel .stat { display: flex; flex-direction: column; gap: .14rem; min-width: 7rem; }
|
|
141
114
|
.statuspanel .stat-label { font-family: ${MONO_STACK}; font-size: .6rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
|
|
142
115
|
.statuspanel .stat-value { font-family: ${MONO_STACK}; font-size: .82rem; font-variant-numeric: tabular-nums; color: var(--ink); }
|
|
@@ -255,6 +228,7 @@ ${DASH_DARK_CHROME_CSS}
|
|
|
255
228
|
.cols { grid-template-columns: 1fr; }
|
|
256
229
|
.chip.tapchip { grid-template-columns: 1.5rem minmax(0,1fr) 3.2rem; }
|
|
257
230
|
.chip.tapchip .track { display: none; }
|
|
231
|
+
.statuspanel .stat { min-width: 0; }
|
|
258
232
|
}
|
|
259
233
|
@media (prefers-reduced-motion: reduce) { * { scroll-behavior: auto !important; } }
|
|
260
234
|
@media (prefers-reduced-motion: no-preference) {
|
|
@@ -381,6 +355,8 @@ ${DASH_DARK_CHROME_CSS}
|
|
|
381
355
|
const fetchWithProgress = ${fetchWithProgress.toString()};
|
|
382
356
|
const createTicker = ${createTicker.toString()};
|
|
383
357
|
const prefersReducedMotion = ${prefersReducedMotion.toString()};
|
|
358
|
+
const loadWinkVendor = ${loadWinkVendor.toString()};
|
|
359
|
+
const cloneMemoryPayload = ${cloneMemoryPayload.toString()};
|
|
384
360
|
const el = (id) => document.getElementById(id);
|
|
385
361
|
// The digest sentence-structure bank, pre-parsed at build time — the browser
|
|
386
362
|
// has no TOML parser, so the page carries the table the client-side digest
|
|
@@ -409,7 +385,6 @@ ${DASH_DARK_CHROME_CSS}
|
|
|
409
385
|
const checkedSources = new Set(); // source keys currently checked for the ask
|
|
410
386
|
|
|
411
387
|
// ---- boot --------------------------------------------------------------
|
|
412
|
-
const WINK_LOAD_TIMEOUT_MS = 8000;
|
|
413
388
|
const progressParts = {};
|
|
414
389
|
let progressActive = true;
|
|
415
390
|
function noteProgress(key, loaded, total) {
|
|
@@ -417,20 +392,7 @@ ${DASH_DARK_CHROME_CSS}
|
|
|
417
392
|
if (progressActive) statEngineEl.textContent = loadProgressLine(Object.values(progressParts));
|
|
418
393
|
}
|
|
419
394
|
|
|
420
|
-
|
|
421
|
-
let settled = false;
|
|
422
|
-
const timeout = new Promise((_, reject) => setTimeout(() => { if (!settled) reject(new Error("wink load stalled")); }, WINK_LOAD_TIMEOUT_MS));
|
|
423
|
-
try {
|
|
424
|
-
const mod = await Promise.race([import("./vendor/wink.js"), timeout]);
|
|
425
|
-
settled = true;
|
|
426
|
-
window.tmctResearch.registerWinkModel(() => ({ winkNLP: mod.winkNLP, model: mod.model }));
|
|
427
|
-
return "loaded";
|
|
428
|
-
} catch (err) {
|
|
429
|
-
settled = true;
|
|
430
|
-
console.warn("tmct research: the wink vendor asset failed to load", err);
|
|
431
|
-
return "unavailable";
|
|
432
|
-
}
|
|
433
|
-
}
|
|
395
|
+
const loadWink = loadWinkVendor({ register: (factory) => window.tmctResearch.registerWinkModel(factory) });
|
|
434
396
|
|
|
435
397
|
let seedPayload = null;
|
|
436
398
|
let seedFacts = 0;
|
|
@@ -444,12 +406,8 @@ ${DASH_DARK_CHROME_CSS}
|
|
|
444
406
|
console.warn("tmct research: chat-seed.json unavailable — starting unseeded", err);
|
|
445
407
|
}
|
|
446
408
|
}
|
|
447
|
-
const cloneSeed = () => {
|
|
448
|
-
if (!seedPayload) return null;
|
|
449
|
-
try { return structuredClone(seedPayload); } catch { return JSON.parse(JSON.stringify(seedPayload)); }
|
|
450
|
-
};
|
|
451
409
|
function newSession() {
|
|
452
|
-
return window.tmctResearch.createResearchSession({ seedPayload:
|
|
410
|
+
return window.tmctResearch.createResearchSession({ seedPayload: cloneMemoryPayload(seedPayload), vocabSeeded: Boolean(seedPayload), digestStructures: DIGEST_STRUCTURES });
|
|
453
411
|
}
|
|
454
412
|
|
|
455
413
|
// The curated reference pack provider, same fetch seam chat.html registers,
|
|
@@ -481,7 +439,7 @@ ${DASH_DARK_CHROME_CSS}
|
|
|
481
439
|
statEngineEl.textContent = "unavailable";
|
|
482
440
|
return;
|
|
483
441
|
}
|
|
484
|
-
const [winkStatus] = await Promise.all([
|
|
442
|
+
const [winkStatus] = await Promise.all([loadWink(), fetchSeed()]);
|
|
485
443
|
progressActive = false;
|
|
486
444
|
window.tmctResearch.registerReferencePackProvider(fetchPackProvider);
|
|
487
445
|
session = newSession();
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import {
|
|
19
19
|
DIRECTION_DELTA, WORLD_NAME, cellId, parseCellId, inBounds, chebyshevDistance, oneStepDirectionBetween,
|
|
20
|
+
agentKindOf, liveIdsOfKind,
|
|
20
21
|
} from "../domain/spider-fly-world.mjs";
|
|
21
22
|
import { foldSpiderFlyState, runSpiderFlyTick, startSpiderFlyGame, beliefSnapshotFor } from "./spider-fly.mjs";
|
|
22
23
|
import { worldProvenanceTag } from "../domain/worlds-pack.mjs";
|
|
@@ -121,11 +122,6 @@ async function openSpiderFlyGame({ planHolder, memoryDir, env, cache, gameConfig
|
|
|
121
122
|
|
|
122
123
|
// ---- resolving an addressed agent / belief target ----------------------------
|
|
123
124
|
|
|
124
|
-
const liveIdsOfKind = (kind, state) => {
|
|
125
|
-
const re = new RegExp(`^${kind}-\\d+$`);
|
|
126
|
-
return [...state.placements.keys()].filter((id) => re.test(id) && !state.removed.has(id)).sort();
|
|
127
|
-
};
|
|
128
|
-
|
|
129
125
|
/** An exact "kind-num" reference, or (no number given) the first live
|
|
130
126
|
* individual of that kind — null when nothing live matches. */
|
|
131
127
|
function resolveAgentId(kind, num, state) {
|
|
@@ -133,7 +129,7 @@ function resolveAgentId(kind, num, state) {
|
|
|
133
129
|
const id = `${kind}-${num}`;
|
|
134
130
|
return state.placements.has(id) && !state.removed.has(id) ? id : null;
|
|
135
131
|
}
|
|
136
|
-
const live = liveIdsOfKind(kind, state);
|
|
132
|
+
const live = liveIdsOfKind(state.placements, kind, state.removed);
|
|
137
133
|
return live[0] ?? null;
|
|
138
134
|
}
|
|
139
135
|
|
|
@@ -143,7 +139,7 @@ function resolveAgentId(kind, num, state) {
|
|
|
143
139
|
* than one spider or fly. */
|
|
144
140
|
function resolveNearestAgentId(kind, num, state, nearCell) {
|
|
145
141
|
if (num) return resolveAgentId(kind, num, state);
|
|
146
|
-
const live = liveIdsOfKind(kind, state);
|
|
142
|
+
const live = liveIdsOfKind(state.placements, kind, state.removed);
|
|
147
143
|
if (!live.length || !nearCell) return live[0] ?? null;
|
|
148
144
|
let best = live[0];
|
|
149
145
|
let bestDist = Infinity;
|
|
@@ -193,11 +189,6 @@ export { oneStepDirectionBetween };
|
|
|
193
189
|
// claim, true or false alike. A pill's `truth` tag is for the human eye
|
|
194
190
|
// only — it never rides along in the submitted text itself.
|
|
195
191
|
|
|
196
|
-
const liveIdsOfKindFromAgents = (kind, agents) => {
|
|
197
|
-
const re = new RegExp(`^${kind}-\\d+$`);
|
|
198
|
-
return Object.keys(agents || {}).filter((id) => re.test(id)).sort();
|
|
199
|
-
};
|
|
200
|
-
|
|
201
192
|
/** "spider"/"fly" bare when exactly one individual of that kind is live
|
|
202
193
|
* (nothing to disambiguate), else the individual's own numbered id — a
|
|
203
194
|
* pill-set legibility choice, not a grammar restriction (the addressed
|
|
@@ -246,8 +237,8 @@ function reflectedCell(cell) {
|
|
|
246
237
|
*/
|
|
247
238
|
export function pillsForSpiderFly(agents, explicitAddresseeId, opts = {}) {
|
|
248
239
|
const { defaultKind = "spider" } = opts;
|
|
249
|
-
const liveSpiders =
|
|
250
|
-
const liveFlies =
|
|
240
|
+
const liveSpiders = liveIdsOfKind(agents, "spider");
|
|
241
|
+
const liveFlies = liveIdsOfKind(agents, "fly");
|
|
251
242
|
|
|
252
243
|
const addressPills = [
|
|
253
244
|
...liveSpiders.map((id) => ({ id, kind: "spider", label: `@${agentPillLabel("spider", id, liveSpiders)}` })),
|
|
@@ -258,7 +249,7 @@ export function pillsForSpiderFly(agents, explicitAddresseeId, opts = {}) {
|
|
|
258
249
|
const addresseeId = (explicitAddresseeId && agents[explicitAddresseeId]) ? explicitAddresseeId : fallbackAddresseeId;
|
|
259
250
|
if (!addresseeId) return { addressPills, claimPills: [], addresseeId: null };
|
|
260
251
|
|
|
261
|
-
const addresseeKind =
|
|
252
|
+
const addresseeKind = agentKindOf(addresseeId);
|
|
262
253
|
const addresseeLabel = agentPillLabel(addresseeKind, addresseeId, addresseeKind === "spider" ? liveSpiders : liveFlies);
|
|
263
254
|
const addresseeCell = parseCellId(agents[addresseeId].cell);
|
|
264
255
|
|
|
@@ -352,10 +343,11 @@ async function runTickAndRender({ planHolder, memoryDir, cache, toldFacts = [],
|
|
|
352
343
|
|
|
353
344
|
/** One `[id, cellId | null]` belief entry as a sentence: `"spider-1 is at
|
|
354
345
|
* cell-3-4."` when observed/told, `"fly-2 has not been observed."`
|
|
355
|
-
* otherwise —
|
|
356
|
-
* (observedFactsHtml) renders
|
|
357
|
-
*
|
|
358
|
-
|
|
346
|
+
* otherwise — exported so spider-fly-viz.mjs's own click-expand panel
|
|
347
|
+
* (observedFactsHtml) renders from the SAME function instead of a
|
|
348
|
+
* hand-typed copy, so the chat phrasing and the browser panel can never
|
|
349
|
+
* drift apart. Pure, self-contained, `.toString()`-splice safe. */
|
|
350
|
+
export function believedFactSentence(id, believedCell) {
|
|
359
351
|
return believedCell ? `${id} is at ${believedCell}.` : `${id} has not been observed.`;
|
|
360
352
|
}
|
|
361
353
|
|
|
@@ -374,14 +366,14 @@ async function spiderFlyBeliefAnswer(match, { memoryDir, gameConfig = DEFAULT_GA
|
|
|
374
366
|
const observerId = resolveAgentId(kind, num, state);
|
|
375
367
|
if (!observerId) return noSuchAgentAnswer(kind, "addressee");
|
|
376
368
|
const observerCell = parseCellId(state.placements.get(observerId).cell);
|
|
377
|
-
const candidateIds = [...liveIdsOfKind("spider", state), ...liveIdsOfKind("fly", state)];
|
|
369
|
+
const candidateIds = [...liveIdsOfKind(state.placements, "spider", state.removed), ...liveIdsOfKind(state.placements, "fly", state.removed)];
|
|
378
370
|
const visionRadius = kind === "spider"
|
|
379
371
|
? gameConfig?.spiderFly?.spiderVisionRadius
|
|
380
372
|
: gameConfig?.spiderFly?.flyVisionRadius;
|
|
381
373
|
const belief = beliefSnapshotFor(observerId, observerCell, candidateIds, state, { visionRadius });
|
|
382
374
|
const entries = Object.entries(belief);
|
|
383
375
|
const text = entries.length
|
|
384
|
-
? `${observerId} sees: ${entries.map(([id, cell]) =>
|
|
376
|
+
? `${observerId} sees: ${entries.map(([id, cell]) => believedFactSentence(id, cell)).join(" ")}`
|
|
385
377
|
: `${observerId} is alone on the board — nothing else to see.`;
|
|
386
378
|
return {
|
|
387
379
|
text,
|
|
@@ -446,7 +438,7 @@ const SF_GOAL_RE = /^(?:what(?:'s|\s+is)\s+(?:the\s+|my\s+)?(?:goal|objective|po
|
|
|
446
438
|
const WATCHER_STANCE = 'you have no piece here — both agents move on their own. Watch, say "tick" to advance, or address one, e.g. "@spider the fly is east".';
|
|
447
439
|
|
|
448
440
|
const positionsOfKind = (kind, state) =>
|
|
449
|
-
liveIdsOfKind(kind, state).map((id) => `${id} at ${state.placements.get(id).cell}`);
|
|
441
|
+
liveIdsOfKind(state.placements, kind, state.removed).map((id) => `${id} at ${state.placements.get(id).cell}`);
|
|
450
442
|
|
|
451
443
|
async function spiderFlyContextAnswer(line, { memoryDir }) {
|
|
452
444
|
const l = String(line).trim();
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
// `.toString()` — exactly ledger-viz.mjs's own `facetCounts`/
|
|
23
23
|
// `resolveAnsweredTerm` pattern — because they are genuinely UI-only, with no
|
|
24
24
|
// reason to live inside the engine bundle: `createTicker` (viz-ticker.mjs,
|
|
25
|
-
// the shared play/pause/step/reset primitive), `
|
|
25
|
+
// the shared play/pause/step/reset primitive), `agentKindOf` (the shared
|
|
26
|
+
// kind-from-id helper, defined once in spider-fly-world.mjs),
|
|
26
27
|
// `threadCellsForSpiderPlan` (the silk-thread reconstruction), `nextCorpses`
|
|
27
28
|
// (the dying-agent bookkeeping behind the dusty-corner corpse pile) and
|
|
28
29
|
// `facingDegreesFor` (plan-driven sprite orientation) — all kept as real,
|
|
@@ -32,11 +33,14 @@
|
|
|
32
33
|
// bundle's own real ES exports instead, since (unlike ledger-viz, which
|
|
33
34
|
// reuses a FIXED shared bundle it can't extend for one page's own needs) this
|
|
34
35
|
// page ships its own dedicated bundle and can just export what it needs.
|
|
35
|
-
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml, embedJson, embedScriptText } from "./viz-theme.mjs";
|
|
36
|
+
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml, embedJson, embedScriptText, meterBarHtml } from "./viz-theme.mjs";
|
|
36
37
|
import { createTicker } from "./viz-ticker.mjs";
|
|
37
|
-
import {
|
|
38
|
+
import { loadWinkVendor } from "./viz-boot.mjs";
|
|
39
|
+
import { GRID_SIZE, WEB_HOME, WEB_RADIUS, isInWebBlock, cellId, agentKindOf } from "../domain/spider-fly-world.mjs";
|
|
38
40
|
import { FLY_INITIAL_MASS, EGG_LAY_MASS_THRESHOLD } from "./spider-fly.mjs";
|
|
39
|
-
import {
|
|
41
|
+
import { believedFactSentence } from "./spider-fly-turn.mjs";
|
|
42
|
+
import { DEFAULT_GAME_CONFIG, massScaleFor } from "../domain/game-config.mjs";
|
|
43
|
+
import { resolveSpriteRequest } from "../domain/sprite-request.mjs";
|
|
40
44
|
|
|
41
45
|
// A larger cell than this page's first cut (44px) — the board sits in its
|
|
42
46
|
// own full-width block, so a bigger cell keeps it reading as the page's
|
|
@@ -62,13 +66,6 @@ function webCellIds() {
|
|
|
62
66
|
return out;
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
/** An agent id's class for sprite/HUD purposes — "spider-2" -> "spider",
|
|
66
|
-
* "fly-10" -> "fly", "egg-1" -> "egg". Self-contained (no outer refs),
|
|
67
|
-
* `.toString()`-splice safe. */
|
|
68
|
-
export function classOfAgentId(id) {
|
|
69
|
-
return String(id).replace(/-\d+$/, "");
|
|
70
|
-
}
|
|
71
|
-
|
|
72
69
|
/**
|
|
73
70
|
* Reconstruct the spider's remaining silk-thread path — the sequence of
|
|
74
71
|
* cell ids from its CURRENT cell (after this tick's one executed step) to
|
|
@@ -128,54 +125,6 @@ export function facingDegreesFor(plan, previousDegrees) {
|
|
|
128
125
|
return previousDegrees ?? 0;
|
|
129
126
|
}
|
|
130
127
|
|
|
131
|
-
/** The instance-level `mgx:feels` emotion for one spider-fly agent this
|
|
132
|
-
* tick — a PURE derivation from state spider-fly.mjs's own `runSpiderFlyTick`
|
|
133
|
-
* already returns on the agent (`goal`/`mass`), never a new persisted fact,
|
|
134
|
-
* the same derive-don't-duplicate posture `hasActiveWebAt` already holds for
|
|
135
|
-
* web state (PLAN_GAMES_UPLIFT_V3.md §B.2.4). Operator-confirmed mapping: a
|
|
136
|
-
* spider that just ate is happy; a spider carrying an uncaught fly or
|
|
137
|
-
* mid-chase (chasing, or co-located and about to catch) is angry (predatory
|
|
138
|
-
* focus); a spider avoiding another spider is scared; a spider holding
|
|
139
|
-
* position or building a web is calm. A fly evading a believed-visible
|
|
140
|
-
* spider is scared, and so is the sharper form of the same fear — just
|
|
141
|
-
* caught, or already being carried; a fly with no threat visible (wandering,
|
|
142
|
-
* or trapped with none in sight) is calm.
|
|
143
|
-
*
|
|
144
|
-
* `agent.goal` is spider-fly.mjs's own fixed-template text (`goalLineFor`'s
|
|
145
|
-
* literal phrasing) — the only reliable per-tick discriminator this
|
|
146
|
-
* function's caller has, and every real goal string that engine can ever
|
|
147
|
-
* produce is matched below by its own distinguishing prefix. A goal this
|
|
148
|
-
* function doesn't recognize (a freshly hatched/spawned agent's "no goal
|
|
149
|
-
* yet", or the transient "X is gone — re-evaluating" scrub once a named
|
|
150
|
-
* agent dies) falls through to "calm" — the 6-word vocabulary's own
|
|
151
|
-
* deliberate no-strong-emotion baseline (sprite-expressions.mjs's B.2.1
|
|
152
|
-
* design), not a guess.
|
|
153
|
-
*
|
|
154
|
-
* `maxMass` is accepted for parity with this page's own per-class mass-bar
|
|
155
|
-
* denominators (`SPIDERFLY.maxSpiderMass`/`maxFlyMass`) but unused today —
|
|
156
|
-
* no state in the operator's own confirmed table keys off a mass ratio,
|
|
157
|
-
* only off the goal text above. Self-contained (no outer refs),
|
|
158
|
-
* `.toString()`-splice safe. */
|
|
159
|
-
export function emotionFor(agent, kind, maxMass) {
|
|
160
|
-
const goal = agent?.goal || "";
|
|
161
|
-
if (kind === "spider") {
|
|
162
|
-
if (goal.startsWith("just ate")) return "happy";
|
|
163
|
-
if (goal.startsWith("carrying") || goal.startsWith("chasing") || goal.startsWith("co-located with")) return "angry";
|
|
164
|
-
if (goal.startsWith("avoiding")) return "scared";
|
|
165
|
-
return "calm";
|
|
166
|
-
}
|
|
167
|
-
if (kind === "fly") {
|
|
168
|
-
if (
|
|
169
|
-
goal.startsWith("evading")
|
|
170
|
-
|| goal.startsWith("being carried by")
|
|
171
|
-
|| goal.startsWith("just caught by")
|
|
172
|
-
|| goal.startsWith("trapped in an active web")
|
|
173
|
-
) return "scared";
|
|
174
|
-
return "calm";
|
|
175
|
-
}
|
|
176
|
-
return "calm";
|
|
177
|
-
}
|
|
178
|
-
|
|
179
128
|
/**
|
|
180
129
|
* The updated corpse set for one redraw (§A.2.5 — visual-only, entirely
|
|
181
130
|
* client-side; the actual starve/eat removal already happened in the
|
|
@@ -200,7 +149,7 @@ export function nextCorpses(prevCorpses, prevAgents, agents, turn, lingerTurns =
|
|
|
200
149
|
if (agents[id] || out[id]) continue;
|
|
201
150
|
const cell = prevAgents[id]?.cell;
|
|
202
151
|
if (!cell) continue;
|
|
203
|
-
out[id] = { cls:
|
|
152
|
+
out[id] = { cls: agentKindOf(id), cell, diedAtTurn: turn };
|
|
204
153
|
}
|
|
205
154
|
return out;
|
|
206
155
|
}
|
|
@@ -409,9 +358,9 @@ ${THEME_TOKENS_CSS}
|
|
|
409
358
|
texture (repeating-linear-gradient) instead of a smooth gradient bar —
|
|
410
359
|
discrete resource units, the same reading-at-a-glance language a 90s
|
|
411
360
|
strategy game's own gold/mana meters use. */
|
|
412
|
-
.
|
|
413
|
-
.
|
|
414
|
-
.
|
|
361
|
+
.meter-track { height: 7px; margin-top: .35rem; background: var(--chrome-well); border: 1px solid var(--chrome-edge-lo); box-shadow: var(--chrome-shadow-inset); border-radius: 1px; overflow: hidden; }
|
|
362
|
+
.meter-fill { height: 100%; background: repeating-linear-gradient(90deg, var(--taught) 0 6px, rgba(0, 0, 0, .25) 6px 7px); }
|
|
363
|
+
.meter-fill.fly { background: repeating-linear-gradient(90deg, var(--fly) 0 6px, rgba(0, 0, 0, .25) 6px 7px); }
|
|
415
364
|
.hud-empty { color: var(--muted); font-size: .85rem; }
|
|
416
365
|
.chatlog { display: flex; flex-direction: column; gap: .4rem; max-height: 220px; overflow-y: auto; margin-bottom: .5rem; }
|
|
417
366
|
.chatlog:empty { display: none; margin-bottom: 0; }
|
|
@@ -591,12 +540,25 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
591
540
|
(function () {
|
|
592
541
|
"use strict";
|
|
593
542
|
const createTicker = ${createTicker.toString()};
|
|
594
|
-
const
|
|
543
|
+
const loadWinkVendor = ${loadWinkVendor.toString()};
|
|
544
|
+
const agentKindOf = ${agentKindOf.toString()};
|
|
595
545
|
const threadCellsForSpiderPlan = ${threadCellsForSpiderPlan.toString()};
|
|
596
546
|
const facingDegreesFor = ${facingDegreesFor.toString()};
|
|
597
|
-
const
|
|
547
|
+
const resolveSpriteRequest = ${resolveSpriteRequest.toString()};
|
|
598
548
|
const nextCorpses = ${nextCorpses.toString()};
|
|
599
|
-
const
|
|
549
|
+
const massScaleFor = ${massScaleFor.toString()};
|
|
550
|
+
const believedFactSentence = ${believedFactSentence.toString()};
|
|
551
|
+
const escapeHtml = ${escapeHtml.toString()};
|
|
552
|
+
const meterBarHtml = ${meterBarHtml.toString()};
|
|
553
|
+
const esc = escapeHtml;
|
|
554
|
+
// The lemma/POS tier every sibling dock loads: this game's OWN commands
|
|
555
|
+
// (tick, address, stop, see) are closed regexes that never need it, but a
|
|
556
|
+
// line that doesn't match one of those still falls through to the
|
|
557
|
+
// ordinary chat/ask/teach lanes (spiderFlyTurn returns null), and those
|
|
558
|
+
// lanes read exactly as much better with wink loaded as they do on every
|
|
559
|
+
// other page. Fired eagerly here so it is likely settled well before a
|
|
560
|
+
// visitor's first ordinary (non-game) question.
|
|
561
|
+
loadWinkVendor({ register: (factory) => tmctSpiderFly.registerWinkModel(factory) })();
|
|
600
562
|
const el = (id) => document.getElementById(id);
|
|
601
563
|
const boardFrame = el("boardFrame");
|
|
602
564
|
const boardCanvas = el("board");
|
|
@@ -660,7 +622,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
660
622
|
const goalById = {};
|
|
661
623
|
const spriteEls = {};
|
|
662
624
|
const facingByAgent = {};
|
|
663
|
-
const
|
|
625
|
+
const moodByAgent = {};
|
|
664
626
|
let corpses = {};
|
|
665
627
|
const corpseEls = {};
|
|
666
628
|
let selectedAddresseeId = null;
|
|
@@ -675,30 +637,37 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
675
637
|
function removeStaleSprites(agents) {
|
|
676
638
|
for (const id of Object.keys(spriteEls)) {
|
|
677
639
|
if (!agents[id]) {
|
|
678
|
-
spriteEls[id].remove(); delete spriteEls[id]; delete goalById[id]; delete facingByAgent[id]; delete
|
|
640
|
+
spriteEls[id].remove(); delete spriteEls[id]; delete goalById[id]; delete facingByAgent[id]; delete moodByAgent[id];
|
|
679
641
|
}
|
|
680
642
|
}
|
|
681
643
|
}
|
|
682
644
|
|
|
683
|
-
//
|
|
684
|
-
//
|
|
685
|
-
//
|
|
686
|
-
//
|
|
687
|
-
//
|
|
688
|
-
//
|
|
689
|
-
//
|
|
690
|
-
//
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
function
|
|
695
|
-
if (cls !== "spider" && cls !== "fly") return
|
|
696
|
-
return
|
|
645
|
+
// The engine writes each agent's mood as a real mgx:feels fact every turn
|
|
646
|
+
// and hands the same word back on the agent, so this page reads it rather
|
|
647
|
+
// than deriving a mood of its own. Only spider/fly carry the mgx:feels
|
|
648
|
+
// parameter (data/sprites-large/*-with-emotion.toml exists for those two
|
|
649
|
+
// classes only) — every other class (egg, and any class this page has no
|
|
650
|
+
// template for at all) asks for no expression at all, resolving through its
|
|
651
|
+
// plain template. A snapshot-sourced agent carries no mood (a chat-driven
|
|
652
|
+
// tick returns text, not the structured tick shape), so the last mood the
|
|
653
|
+
// engine reported stands, exactly as goalById already holds the last goal.
|
|
654
|
+
// The starting board has no reported mood at all, and calm is the mood the
|
|
655
|
+
// engine itself writes for a just-minted agent, so that is the baseline.
|
|
656
|
+
function expressionForAgent(id, agent, cls) {
|
|
657
|
+
if (cls !== "spider" && cls !== "fly") return null;
|
|
658
|
+
return (agent && agent.mood) || moodByAgent[id] || "calm";
|
|
697
659
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
660
|
+
// One sprite request, in the same { class, expression } shape tmct_sprite
|
|
661
|
+
// takes, answered by the same pure resolver the tool wraps (sprite-request.mjs,
|
|
662
|
+
// spliced in above) — the page holds the state and nothing else.
|
|
663
|
+
function resolveSpriteFace(spriteRequest) {
|
|
664
|
+
if (!window.tmctSpiderFly) return "";
|
|
665
|
+
return resolveSpriteRequest(spriteRequest, {
|
|
666
|
+
factRows: (session && session.taxonomyRows) || [],
|
|
667
|
+
templates: SPIDERFLY.spriteTemplates,
|
|
668
|
+
spriteRegistry: tmctSpiderFly.SPRITE_REGISTRY,
|
|
669
|
+
resolveSpriteAsset: tmctSpiderFly.resolveSpriteAsset,
|
|
670
|
+
}).svg || "";
|
|
702
671
|
}
|
|
703
672
|
|
|
704
673
|
function togglePov(id) {
|
|
@@ -717,18 +686,18 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
717
686
|
node.className = "sprite";
|
|
718
687
|
node.dataset.cls = cls;
|
|
719
688
|
// Property-aware resolution (sprite-templates.mjs's resolveSpriteAsset):
|
|
720
|
-
// a spider/fly's live mgx:feels
|
|
721
|
-
//
|
|
722
|
-
//
|
|
723
|
-
//
|
|
724
|
-
const
|
|
725
|
-
|
|
689
|
+
// a spider/fly's live mgx:feels mood resolves it through the matching
|
|
690
|
+
// data/sprites-large/*-with-emotion.toml template; every other class
|
|
691
|
+
// (egg) asks for no expression and resolves through its plain class
|
|
692
|
+
// template (or the flat SPRITE_REGISTRY), unchanged.
|
|
693
|
+
const mood = expressionForAgent(id, agent, cls);
|
|
694
|
+
moodByAgent[id] = mood;
|
|
726
695
|
// The sprite SVG lives in its own inner wrapper so plan-driven facing
|
|
727
696
|
// (a CSS rotate on THIS wrapper) never fights the outer .sprite node's
|
|
728
697
|
// own translate(-50%,-50%) positioning transform.
|
|
729
698
|
const face = document.createElement("div");
|
|
730
699
|
face.className = "sprite-face";
|
|
731
|
-
face.innerHTML = resolveSpriteFace(cls,
|
|
700
|
+
face.innerHTML = resolveSpriteFace({ class: cls, expression: mood });
|
|
732
701
|
node.appendChild(face);
|
|
733
702
|
if (!preview) {
|
|
734
703
|
node.tabIndex = 0;
|
|
@@ -747,7 +716,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
747
716
|
function applyAgents(agents) {
|
|
748
717
|
removeStaleSprites(agents);
|
|
749
718
|
for (const [id, a] of Object.entries(agents)) {
|
|
750
|
-
const cls =
|
|
719
|
+
const cls = agentKindOf(id);
|
|
751
720
|
const node = ensureSpriteEl(id, cls, a);
|
|
752
721
|
const parsed = tmctSpiderFly.parseCellId(a.cell);
|
|
753
722
|
if (!parsed) continue;
|
|
@@ -763,16 +732,15 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
763
732
|
const face = node.querySelector(".sprite-face");
|
|
764
733
|
if (face) {
|
|
765
734
|
face.style.transform = "rotate(" + facingByAgent[id] + "deg)";
|
|
766
|
-
//
|
|
735
|
+
// Mood changes tick-to-tick (a fly's fear spikes the instant a
|
|
767
736
|
// spider comes into view, a spider's joy fires the instant it eats),
|
|
768
737
|
// so this must re-resolve every redraw, not just at sprite creation —
|
|
769
|
-
// but only actually replace the DOM when the
|
|
738
|
+
// but only actually replace the DOM when the mood word itself
|
|
770
739
|
// changed since last render, never on every tick regardless.
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
face.innerHTML = resolveSpriteFace(cls, propertyFacts);
|
|
740
|
+
const mood = expressionForAgent(id, a, cls);
|
|
741
|
+
if (mood !== moodByAgent[id]) {
|
|
742
|
+
moodByAgent[id] = mood;
|
|
743
|
+
face.innerHTML = resolveSpriteFace({ class: cls, expression: mood });
|
|
776
744
|
}
|
|
777
745
|
}
|
|
778
746
|
if (a.goal) goalById[id] = a.goal;
|
|
@@ -805,7 +773,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
805
773
|
// never emotion-parameterized at all (a rotting carcass has none),
|
|
806
774
|
// just its class's own plain template under the grayscale/fade the
|
|
807
775
|
// .sprite.corpse CSS rule already applies.
|
|
808
|
-
face.innerHTML = resolveSpriteFace(corpse.cls
|
|
776
|
+
face.innerHTML = resolveSpriteFace({ class: corpse.cls });
|
|
809
777
|
node.appendChild(face);
|
|
810
778
|
spriteLayer.appendChild(node);
|
|
811
779
|
corpseEls[id] = node;
|
|
@@ -818,13 +786,6 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
818
786
|
}
|
|
819
787
|
}
|
|
820
788
|
|
|
821
|
-
function massBarHtml(cls, mass) {
|
|
822
|
-
const maxMass = cls === "spider" ? SPIDERFLY.maxSpiderMass : cls === "fly" ? SPIDERFLY.maxFlyMass : null;
|
|
823
|
-
if (typeof mass !== "number" || !maxMass) return "";
|
|
824
|
-
const pct = Math.max(0, Math.min(100, (mass / maxMass) * 100));
|
|
825
|
-
return '<div class="mass-track"><div class="mass-fill ' + esc(cls) + '" style="width:' + pct + '%"></div></div>';
|
|
826
|
-
}
|
|
827
|
-
|
|
828
789
|
// The last-created plan as a plain arrow chain ("west → west"), or
|
|
829
790
|
// "holding." when the agent's plan this tick is empty — mirrors exactly
|
|
830
791
|
// what drove this tick's sprite facing (facingDegreesFor reads the same
|
|
@@ -850,15 +811,15 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
850
811
|
|
|
851
812
|
// The full text rendering behind the click-expand panel: one sentence per
|
|
852
813
|
// other candidate the observer currently has a belief about, or "has not
|
|
853
|
-
// been observed" for a candidate it doesn't —
|
|
854
|
-
//
|
|
855
|
-
//
|
|
814
|
+
// been observed" for a candidate it doesn't — believedFactSentence is the
|
|
815
|
+
// SAME function spider-fly-turn.mjs's own chat lane renders "X sees: ..."
|
|
816
|
+
// from, so the chat phrasing and this panel can never drift apart.
|
|
817
|
+
// beliefLineHtml already compresses the same belief map into a single line
|
|
818
|
+
// above; this spells it out in full beside the clicked agent.
|
|
856
819
|
function observedFactsHtml(observerId, belief) {
|
|
857
820
|
const entries = Object.entries(belief || {});
|
|
858
821
|
const lines = entries.length
|
|
859
|
-
? entries.map(([id, cell]) => '<div class="hud-detail-line">'
|
|
860
|
-
+ (cell ? esc(id) + " is at " + esc(cell) + "." : esc(id) + " has not been observed.")
|
|
861
|
-
+ "</div>").join("")
|
|
822
|
+
? entries.map(([id, cell]) => '<div class="hud-detail-line">' + esc(believedFactSentence(id, cell)) + "</div>").join("")
|
|
862
823
|
: '<div class="hud-detail-line">nothing else is on the board yet.</div>';
|
|
863
824
|
return '<div class="hud-detail"><div class="hud-detail-title">' + esc(observerId) + " observes</div>" + lines + "</div>";
|
|
864
825
|
}
|
|
@@ -867,13 +828,13 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
867
828
|
const ids = Object.keys(lastAgents).sort();
|
|
868
829
|
if (!ids.length) { hudEl.innerHTML = '<div class="hud-empty">no agents on the board.</div>'; return; }
|
|
869
830
|
hudEl.innerHTML = ids.map((id) => {
|
|
870
|
-
const cls =
|
|
831
|
+
const cls = agentKindOf(id);
|
|
871
832
|
const a = lastAgents[id];
|
|
872
833
|
const clickable = cls === "spider" || cls === "fly";
|
|
873
834
|
const expanded = clickable && expandedAgents.has(id);
|
|
874
835
|
const main = '<div class="hud-main"><span class="hud-id ' + esc(cls) + '">' + esc(id) + '</span>'
|
|
875
836
|
+ '<span class="hud-goal">' + esc(goalById[id] || "watching\\u2026") + "</span>"
|
|
876
|
-
+
|
|
837
|
+
+ meterBarHtml(cls, a.mass, massScaleFor(cls, { maxSpiderMass: SPIDERFLY.maxSpiderMass, maxFlyMass: SPIDERFLY.maxFlyMass }))
|
|
877
838
|
+ planLineHtml(a.plan)
|
|
878
839
|
+ beliefLineHtml(a.belief)
|
|
879
840
|
+ "</div>";
|
|
@@ -963,7 +924,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
963
924
|
// engine's own shipped default before the session has finished
|
|
964
925
|
// booting) — a POV toggle always reflects whatever vision range this
|
|
965
926
|
// class currently actually has, not a fixed constant.
|
|
966
|
-
const radius =
|
|
927
|
+
const radius = agentKindOf(povAgentId) === "spider"
|
|
967
928
|
? (liveConfig.spiderVisionRadius ?? tmctSpiderFly.DEFAULT_VISION_RADIUS)
|
|
968
929
|
: (liveConfig.flyVisionRadius ?? tmctSpiderFly.DEFAULT_VISION_RADIUS);
|
|
969
930
|
const visible = new Set(tmctSpiderFly.visibleCells(p.x, p.y, radius));
|