@polycode-projects/the-mechanical-code-talker 4.0.0 → 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 +178 -9
- package/corpus/reference/index.json.gz +0 -0
- package/corpus/reference/manifest.json +8 -8
- package/corpus/reference/shards/ref-00.jsonl.gz +0 -0
- package/corpus/sprites/src/sprite-facts.jsonl +401 -0
- package/corpus/worlds/index.json.gz +0 -0
- package/corpus/worlds/manifest.json +49 -9
- package/corpus/worlds/shards/greyvale-museum.jsonl.gz +0 -0
- package/corpus/worlds/shards/lantern-cottage.jsonl.gz +0 -0
- package/corpus/worlds/shards/mud-hollow.jsonl.gz +0 -0
- package/corpus/worlds/shards/mud-warren.jsonl.gz +0 -0
- package/corpus/worlds/shards/spider-fly.jsonl.gz +0 -0
- package/corpus/worlds/src/greyvale-museum.jsonl +136 -0
- package/corpus/worlds/src/lantern-cottage.jsonl +60 -0
- package/corpus/worlds/src/mud-hollow.jsonl +82 -0
- package/corpus/worlds/src/mud-warren.jsonl +124 -0
- package/corpus/worlds/src/spider-fly.jsonl +1 -1
- package/data/sprites/animal-icon.toml +11 -5
- package/data/sprites/book-icon.toml +9 -5
- package/data/sprites/cabinet-icon.toml +10 -5
- package/data/sprites/cellar-icon.toml +16 -6
- package/data/sprites/container-icon.toml +7 -3
- package/data/sprites/desk-icon.toml +8 -5
- package/data/sprites/dog-icon.toml +8 -5
- package/data/sprites/dog-with-colour-icon.toml +13 -12
- package/data/sprites/drawing-room-icon.toml +14 -5
- package/data/sprites/egg-icon.toml +6 -3
- package/data/sprites/fly-icon.toml +10 -5
- package/data/sprites/furniture-icon.toml +5 -3
- package/data/sprites/garden-icon.toml +10 -3
- package/data/sprites/key-icon.toml +3 -1
- package/data/sprites/kitchen-icon.toml +15 -6
- package/data/sprites/lamp-icon.toml +9 -4
- package/data/sprites/letter-icon.toml +7 -4
- package/data/sprites/library-icon.toml +14 -3
- package/data/sprites/pan-icon.toml +7 -3
- package/data/sprites/person-icon.toml +6 -2
- package/data/sprites/poodle-icon.toml +1 -1
- package/data/sprites/portable-icon.toml +6 -4
- package/data/sprites/portrait-icon.toml +7 -4
- package/data/sprites/room-icon.toml +11 -2
- package/data/sprites/spider-icon.toml +9 -3
- package/data/sprites/study-icon.toml +10 -2
- package/package.json +5 -4
- 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/grammar/ace.mjs +40 -4
- package/src/domain/grammar/lexicon-core.json +2 -1
- package/src/domain/grammar/lexicon.mjs +18 -0
- package/src/domain/mud-facts.mjs +15 -0
- package/src/domain/reference-pack.mjs +31 -7
- 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 +37 -1
- package/src/domain/sprite-facts.mjs +0 -0
- package/src/domain/sprite-request.mjs +156 -0
- package/src/domain/sprite-templates.mjs +169 -20
- package/src/services/adventure-editor.mjs +8 -14
- package/src/services/adventure-viz.mjs +209 -157
- package/src/services/adventure.mjs +526 -391
- package/src/services/chat-page-viz.mjs +69 -25
- package/src/services/chat.mjs +200 -51
- package/src/services/code-explorer-viz.mjs +102 -62
- package/src/services/extract-facts.mjs +4 -7
- package/src/services/ingest-viz.mjs +68 -82
- package/src/services/ledger-viz.mjs +136 -67
- package/src/services/memory-panel-viz.mjs +62 -0
- package/src/services/mud-editor.mjs +10 -15
- package/src/services/mud-turn.mjs +6 -6
- package/src/services/mud-viz.mjs +1016 -208
- package/src/services/p2p-room.mjs +90 -23
- package/src/services/plan-pddl.mjs +3 -1
- package/src/services/plan-viz.mjs +123 -64
- package/src/services/research-viz.mjs +160 -108
- package/src/services/spider-fly-turn.mjs +15 -23
- package/src/services/spider-fly-viz.mjs +146 -161
- package/src/services/spider-fly.mjs +69 -11
- package/src/services/sprite-catalog-viz.mjs +414 -240
- package/src/services/viz-boot.mjs +71 -0
- package/src/services/viz-room-graph.mjs +203 -0
- package/src/services/viz-theme.mjs +90 -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 +127 -124
- package/src/surfaces/web/memory-stats.mjs +11 -0
- package/src/surfaces/web/mud-browser-entry.mjs +71 -29
- 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
|
@@ -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
|
}
|
|
@@ -265,42 +214,48 @@ ${THEME_TOKENS_CSS}
|
|
|
265
214
|
@media (prefers-color-scheme: dark) { :root { --fly: #D9A94B; } }
|
|
266
215
|
:root[data-theme="dark"] { --fly: #D9A94B; }
|
|
267
216
|
:root[data-theme="light"] { --fly: #A6791F; }
|
|
268
|
-
/* The
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
217
|
+
/* The console chrome, in this page's own gothic-whimsical register (a
|
|
218
|
+
Charles Addams cartoon: india-ink line work over aged paper, spooky-cute
|
|
219
|
+
rather than scary) WRAPPING the window-corner board scene above, never
|
|
220
|
+
touching it — every rule below this block styles
|
|
221
|
+
.side/.controls-row/.tuning/.status only, all of which are hidden in
|
|
222
|
+
?preview=1 mode alongside .side itself, so none of this bleeds into the
|
|
223
|
+
embedded home-page hero. --chrome-face is the panel's aged-ivory (night
|
|
224
|
+
slate in dark) face, textured by --chrome-hatch, a two-direction ink
|
|
225
|
+
cross-hatch; --chrome-edge-hi/-lo are its raised-bevel light/shadow
|
|
226
|
+
edges; --chrome-accent is the ink-plum accent (borders, active/hover
|
|
227
|
+
states); --chrome-well/-well-ink are the near-black readout well with
|
|
228
|
+
bone-pale numerals, deliberately dark in both themes (a lit readout
|
|
229
|
+
reads dark-with-a-glow whether the room around it is light or dark).
|
|
230
|
+
--chrome-shadow-raised/-inset compose the two bevel directions once so
|
|
231
|
+
every panel/button/well below just references one of them. */
|
|
281
232
|
:root {
|
|
282
|
-
--chrome-face: #
|
|
283
|
-
--chrome-
|
|
284
|
-
--chrome-well: #
|
|
233
|
+
--chrome-face: #E7E1D3; --chrome-face-hi: #F2EDE0; --chrome-edge-hi: #FAF6EA; --chrome-edge-lo: #6E6A5E;
|
|
234
|
+
--chrome-accent: #5C4B66; --chrome-accent-soft: rgba(92, 75, 102, .14);
|
|
235
|
+
--chrome-well: #1A171C; --chrome-well-ink: #E4DDCB;
|
|
236
|
+
--chrome-hatch: repeating-linear-gradient(55deg, rgba(28, 23, 32, .05) 0 1px, transparent 1px 6px), repeating-linear-gradient(-35deg, rgba(28, 23, 32, .04) 0 1px, transparent 1px 9px);
|
|
285
237
|
--chrome-shadow-raised: inset 0 1px 0 var(--chrome-edge-hi), inset 0 -1px 0 var(--chrome-edge-lo), 0 1px 2px rgba(0, 0, 0, .18);
|
|
286
238
|
--chrome-shadow-inset: inset 0 1px 3px var(--chrome-edge-lo), inset 0 -1px 0 var(--chrome-edge-hi);
|
|
287
239
|
}
|
|
288
240
|
@media (prefers-color-scheme: dark) {
|
|
289
|
-
:root { --chrome-face: #
|
|
290
|
-
--chrome-
|
|
291
|
-
--chrome-well: #
|
|
241
|
+
:root { --chrome-face: #262230; --chrome-face-hi: #302B3D; --chrome-edge-hi: #453E57; --chrome-edge-lo: #121016;
|
|
242
|
+
--chrome-accent: #A995BD; --chrome-accent-soft: rgba(169, 149, 189, .16);
|
|
243
|
+
--chrome-well: #0C0A10; --chrome-well-ink: #E0D8C6;
|
|
244
|
+
--chrome-hatch: repeating-linear-gradient(55deg, rgba(240, 235, 220, .04) 0 1px, transparent 1px 6px), repeating-linear-gradient(-35deg, rgba(240, 235, 220, .03) 0 1px, transparent 1px 9px); }
|
|
292
245
|
}
|
|
293
|
-
:root[data-theme="dark"] { --chrome-face: #
|
|
294
|
-
--chrome-
|
|
295
|
-
--chrome-well: #
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
--chrome-
|
|
246
|
+
:root[data-theme="dark"] { --chrome-face: #262230; --chrome-face-hi: #302B3D; --chrome-edge-hi: #453E57; --chrome-edge-lo: #121016;
|
|
247
|
+
--chrome-accent: #A995BD; --chrome-accent-soft: rgba(169, 149, 189, .16);
|
|
248
|
+
--chrome-well: #0C0A10; --chrome-well-ink: #E0D8C6;
|
|
249
|
+
--chrome-hatch: repeating-linear-gradient(55deg, rgba(240, 235, 220, .04) 0 1px, transparent 1px 6px), repeating-linear-gradient(-35deg, rgba(240, 235, 220, .03) 0 1px, transparent 1px 9px); }
|
|
250
|
+
:root[data-theme="light"] { --chrome-face: #E7E1D3; --chrome-face-hi: #F2EDE0; --chrome-edge-hi: #FAF6EA; --chrome-edge-lo: #6E6A5E;
|
|
251
|
+
--chrome-accent: #5C4B66; --chrome-accent-soft: rgba(92, 75, 102, .14);
|
|
252
|
+
--chrome-well: #1A171C; --chrome-well-ink: #E4DDCB;
|
|
253
|
+
--chrome-hatch: repeating-linear-gradient(55deg, rgba(28, 23, 32, .05) 0 1px, transparent 1px 6px), repeating-linear-gradient(-35deg, rgba(28, 23, 32, .04) 0 1px, transparent 1px 9px); }
|
|
299
254
|
html { background: var(--bg); }
|
|
300
255
|
body { margin: 0; background: var(--bg); color: var(--ink); font-family: ${SERIF_STACK}; font-size: 16px; line-height: 1.5; }
|
|
301
256
|
.mono { font-family: ${MONO_STACK}; }
|
|
302
257
|
main { max-width: 1120px; margin: 0 auto; padding: 1.4rem 1.2rem 2.2rem; }
|
|
303
|
-
.eyebrow { font-family: ${MONO_STACK}; font-size: .7rem; letter-spacing: .08em; text-transform: uppercase; color: var(--chrome-
|
|
258
|
+
.eyebrow { font-family: ${MONO_STACK}; font-size: .7rem; letter-spacing: .08em; text-transform: uppercase; color: var(--chrome-accent); }
|
|
304
259
|
h1 { font-size: 1.4rem; margin: .3rem 0 .9rem; text-wrap: balance; }
|
|
305
260
|
button { font: inherit; color: inherit; background: none; cursor: pointer; }
|
|
306
261
|
button:focus-visible, input:focus-visible, .sprite:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
|
|
@@ -310,7 +265,7 @@ ${THEME_TOKENS_CSS}
|
|
|
310
265
|
needing to sum to a full width. */
|
|
311
266
|
.stage { display: grid; grid-template-columns: minmax(0, 8fr) minmax(280px, 3fr); gap: 1.2rem; align-items: start; }
|
|
312
267
|
@media (max-width: 760px) { .stage { grid-template-columns: minmax(0, 1fr); } }
|
|
313
|
-
/* A
|
|
268
|
+
/* A moonlit window corner: a cool pale glow near the top-left (WEB_HOME
|
|
314
269
|
already sits near that corner — spider-fly-world.mjs's own header
|
|
315
270
|
comment), and a faint diagonal weave standing in for dust/silk caught
|
|
316
271
|
in the light. Decoration only — the 10x10 game grid itself is drawn by
|
|
@@ -319,20 +274,26 @@ ${THEME_TOKENS_CSS}
|
|
|
319
274
|
margin: 0 auto;
|
|
320
275
|
position: relative; width: ${BOARD_PX}px; max-width: 100%; aspect-ratio: 1 / 1;
|
|
321
276
|
background:
|
|
322
|
-
radial-gradient(140% 140% at 6% 6%, rgba(
|
|
323
|
-
repeating-linear-gradient(115deg, rgba(
|
|
277
|
+
radial-gradient(140% 140% at 6% 6%, rgba(224, 224, 240, .55), transparent 52%),
|
|
278
|
+
repeating-linear-gradient(115deg, rgba(100, 95, 110, .06) 0 1px, transparent 1px 30px),
|
|
324
279
|
var(--card);
|
|
325
280
|
border: 1px solid var(--line);
|
|
326
281
|
box-shadow: inset 0 0 0 6px var(--bg), inset 0 0 0 7px var(--line);
|
|
327
282
|
}
|
|
328
|
-
@media (prefers-color-scheme: dark) { .board-frame { background: radial-gradient(140% 140% at 6% 6%, rgba(
|
|
329
|
-
:root[data-theme="dark"] .board-frame { background: radial-gradient(140% 140% at 6% 6%, rgba(
|
|
283
|
+
@media (prefers-color-scheme: dark) { .board-frame { background: radial-gradient(140% 140% at 6% 6%, rgba(126, 120, 158, .30), transparent 52%), repeating-linear-gradient(115deg, rgba(255, 255, 255, .045) 0 1px, transparent 1px 30px), var(--card); } }
|
|
284
|
+
:root[data-theme="dark"] .board-frame { background: radial-gradient(140% 140% at 6% 6%, rgba(126, 120, 158, .30), transparent 52%), repeating-linear-gradient(115deg, rgba(255, 255, 255, .045) 0 1px, transparent 1px 30px), var(--card); }
|
|
330
285
|
.board-frame canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
|
|
286
|
+
/* The cobweb in the window corner — pure decoration over the moonlit
|
|
287
|
+
glow (never a game element; the ACTIVE web block is still drawn by
|
|
288
|
+
drawBoard() on the canvas). It sits under the sprite layer so agents
|
|
289
|
+
always pass in front of it. */
|
|
290
|
+
.cobweb { position: absolute; top: 7px; left: 7px; width: 36%; height: 36%; color: var(--ink); opacity: .26; pointer-events: none; }
|
|
291
|
+
.cobweb svg { width: 100%; height: 100%; display: block; }
|
|
331
292
|
.sprite-layer { position: absolute; inset: 0; }
|
|
332
293
|
.sprite { position: absolute; width: 7.6%; height: 7.6%; transform: translate(-50%, -50%); transition: left .25s ease, top .25s ease; }
|
|
333
294
|
@media (prefers-reduced-motion: reduce) { .sprite, .sprite-face { transition: none !important; } }
|
|
334
295
|
.sprite-face { width: 100%; height: 100%; transition: transform .25s ease; }
|
|
335
|
-
.sprite-face svg { width: 100%; height: 100%; display: block; }
|
|
296
|
+
.sprite-face svg { width: 100%; height: 100%; display: block; filter: var(--sprite-pop); }
|
|
336
297
|
.sprite[data-cls="spider"] { color: var(--taught); }
|
|
337
298
|
.sprite[data-cls="fly"] { color: var(--fly); }
|
|
338
299
|
.sprite[data-cls="egg"] { color: var(--muted); }
|
|
@@ -350,7 +311,7 @@ ${THEME_TOKENS_CSS}
|
|
|
350
311
|
nameplate header below bleeds to this same panel's own edges via a
|
|
351
312
|
negative margin equal to this padding, so it reads as one welded unit,
|
|
352
313
|
not a label floating inside a box. */
|
|
353
|
-
.hud, .chat, .tuning { background: var(--chrome-face); border: 1px solid var(--chrome-edge-lo); box-shadow: var(--chrome-shadow-raised); border-radius: 2px; padding: .6rem .75rem; }
|
|
314
|
+
.hud, .chat, .tuning { background: var(--chrome-hatch), var(--chrome-face); border: 1px solid var(--chrome-edge-lo); box-shadow: var(--chrome-shadow-raised); border-radius: 2px; padding: .6rem .75rem; }
|
|
354
315
|
/* Both the controls strip above the board and the tuning strip below it
|
|
355
316
|
match the board's own width (not the wider stage-left column they sit
|
|
356
317
|
in), so all three line up as one visual stack. */
|
|
@@ -380,12 +341,12 @@ ${THEME_TOKENS_CSS}
|
|
|
380
341
|
.hud-row { display: flex; align-items: flex-start; gap: .6rem; padding: .4rem 0; border-top: 1px solid var(--chrome-edge-lo); box-shadow: inset 0 1px 0 var(--chrome-edge-hi); }
|
|
381
342
|
.hud-row:first-of-type { border-top: none; box-shadow: none; }
|
|
382
343
|
.hud-row.clickable { cursor: pointer; }
|
|
383
|
-
.hud-row.clickable:hover, .hud-row.clickable:focus-visible { background: var(--chrome-
|
|
344
|
+
.hud-row.clickable:hover, .hud-row.clickable:focus-visible { background: var(--chrome-accent-soft); }
|
|
384
345
|
.hud-main { display: flex; flex-direction: column; gap: .1rem; flex: 1 1 auto; min-width: 0; }
|
|
385
346
|
.hud-id { font-family: ${MONO_STACK}; font-size: .74rem; font-weight: 600; }
|
|
386
347
|
.hud-id.spider { color: var(--taught); } .hud-id.fly { color: var(--fly); } .hud-id.egg { color: var(--muted); }
|
|
387
348
|
.hud-goal { font-size: .85rem; }
|
|
388
|
-
.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-
|
|
349
|
+
.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); }
|
|
389
350
|
/* the click-expand facts panel (§28): beside the clicked spider/fly's own
|
|
390
351
|
row, never a separate popover or a second panel elsewhere on the page —
|
|
391
352
|
the same believedCellOf/beliefSnapshotFor read path spider-fly.mjs
|
|
@@ -397,9 +358,9 @@ ${THEME_TOKENS_CSS}
|
|
|
397
358
|
texture (repeating-linear-gradient) instead of a smooth gradient bar —
|
|
398
359
|
discrete resource units, the same reading-at-a-glance language a 90s
|
|
399
360
|
strategy game's own gold/mana meters use. */
|
|
400
|
-
.
|
|
401
|
-
.
|
|
402
|
-
.
|
|
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); }
|
|
403
364
|
.hud-empty { color: var(--muted); font-size: .85rem; }
|
|
404
365
|
.chatlog { display: flex; flex-direction: column; gap: .4rem; max-height: 220px; overflow-y: auto; margin-bottom: .5rem; }
|
|
405
366
|
.chatlog:empty { display: none; margin-bottom: 0; }
|
|
@@ -417,7 +378,7 @@ ${THEME_TOKENS_CSS}
|
|
|
417
378
|
.chatask input:disabled { opacity: .5; }
|
|
418
379
|
.chatpills { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .5rem; }
|
|
419
380
|
.pill { font-family: ${MONO_STACK}; font-size: .68rem; padding: .2rem .6rem; border: 1px solid var(--chrome-edge-lo); border-radius: 99px; background: var(--chrome-face); color: var(--ink); box-shadow: inset 0 1px 0 var(--chrome-edge-hi); white-space: nowrap; }
|
|
420
|
-
.pill:hover:not(:disabled) { border-color: var(--chrome-
|
|
381
|
+
.pill:hover:not(:disabled) { border-color: var(--chrome-accent); }
|
|
421
382
|
.pill:disabled { opacity: .45; cursor: default; }
|
|
422
383
|
.pill[data-role="addr"].active { border-color: var(--taught); color: var(--taught); }
|
|
423
384
|
/* The dynamic deception-pill rail (§A.2.4): a true/false tag shown ONLY
|
|
@@ -437,7 +398,7 @@ ${THEME_TOKENS_CSS}
|
|
|
437
398
|
.tuning-col.spider h3 { color: var(--taught); } .tuning-col.fly h3 { color: var(--fly); }
|
|
438
399
|
.tuning-col label { display: block; font-size: .68rem; color: var(--muted); margin-bottom: .6rem; }
|
|
439
400
|
/* Every live number on this page (this readout, the turn plaque below)
|
|
440
|
-
shares the same
|
|
401
|
+
shares the same bone-on-ink readout treatment — one readout language,
|
|
441
402
|
not a one-off flourish. */
|
|
442
403
|
.tuning-col .tuning-val { font-family: ${MONO_STACK}; font-variant-numeric: tabular-nums; 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: 0 .35rem; float: right; }
|
|
443
404
|
.tuning-col input[type="range"] {
|
|
@@ -446,7 +407,7 @@ ${THEME_TOKENS_CSS}
|
|
|
446
407
|
-webkit-appearance: none; appearance: none;
|
|
447
408
|
}
|
|
448
409
|
.tuning-col.fly input[type="range"] { accent-color: var(--fly); }
|
|
449
|
-
/* A lever-look thumb (a molded
|
|
410
|
+
/* A lever-look thumb (a molded, ink-edged cap) — appearance: none above
|
|
450
411
|
drops the OS's own native slider look in browsers that honor it;
|
|
451
412
|
accent-color just above is the fallback for any that don't. */
|
|
452
413
|
.tuning-col input[type="range"]::-webkit-slider-thumb {
|
|
@@ -469,14 +430,14 @@ ${THEME_TOKENS_CSS}
|
|
|
469
430
|
control panel", and it costs nothing under reduced-motion since neither
|
|
470
431
|
state involves a transition, just an instant bevel swap. */
|
|
471
432
|
.controls-row button { font-family: ${MONO_STACK}; font-size: .78rem; letter-spacing: .03em; text-transform: uppercase; padding: .4rem .85rem; border-radius: 2px; border: 1px solid var(--chrome-edge-lo); background: var(--chrome-face); box-shadow: var(--chrome-shadow-raised); color: var(--ink); }
|
|
472
|
-
.controls-row button:hover:not(:disabled) { border-color: var(--chrome-
|
|
433
|
+
.controls-row button:hover:not(:disabled) { border-color: var(--chrome-accent); }
|
|
473
434
|
.controls-row button:active:not(:disabled) { box-shadow: var(--chrome-shadow-inset); }
|
|
474
435
|
.controls-row button:disabled { opacity: .4; cursor: default; box-shadow: none; }
|
|
475
|
-
/* The
|
|
436
|
+
/* The turn counter plaque: an accent-framed readout, the same
|
|
476
437
|
inset-well readout language as every stat above it — the one thing this
|
|
477
438
|
page is remembered by. */
|
|
478
|
-
.controls-row .turn { margin-left: auto; font-family: ${MONO_STACK}; font-size: .82rem; letter-spacing: .05em; text-transform: uppercase; font-variant-numeric: tabular-nums; color: var(--chrome-well-ink); background: var(--chrome-well); border: 1px solid var(--chrome-
|
|
479
|
-
.status { font-family: ${MONO_STACK}; font-size: .74rem; color: var(--muted); margin-top: .5rem; padding-left: .5rem; border-left: 2px solid var(--chrome-
|
|
439
|
+
.controls-row .turn { margin-left: auto; font-family: ${MONO_STACK}; font-size: .82rem; letter-spacing: .05em; text-transform: uppercase; font-variant-numeric: tabular-nums; color: var(--chrome-well-ink); background: var(--chrome-well); border: 1px solid var(--chrome-accent); box-shadow: var(--chrome-shadow-inset); border-radius: 2px; padding: .3rem .7rem; }
|
|
440
|
+
.status { font-family: ${MONO_STACK}; font-size: .74rem; color: var(--muted); margin-top: .5rem; padding-left: .5rem; border-left: 2px solid var(--chrome-accent); }
|
|
480
441
|
body.preview .side, body.preview .controls-panel, body.preview .tuning { display: none; }
|
|
481
442
|
body.preview main { padding: 0; max-width: none; }
|
|
482
443
|
body.preview .stage, body.preview .stage-left { display: block; }
|
|
@@ -507,11 +468,23 @@ ${THEME_TOKENS_CSS}
|
|
|
507
468
|
<div class="board-frame" id="boardFrame">
|
|
508
469
|
<canvas id="board" width="${BOARD_PX}" height="${BOARD_PX}" aria-label="the 10x10 board"></canvas>
|
|
509
470
|
<canvas id="pov" width="${BOARD_PX}" height="${BOARD_PX}" aria-hidden="true"></canvas>
|
|
471
|
+
<div class="cobweb" aria-hidden="true"><svg viewBox="0 0 100 100" fill="none" stroke="currentColor" stroke-linecap="round">
|
|
472
|
+
<g stroke-width="0.7">
|
|
473
|
+
<path d="M0 0 L73.8 5.2"/><path d="M0 0 L68.6 27.7"/><path d="M0 0 L52.3 52.3"/><path d="M0 0 L27.7 68.6"/><path d="M0 0 L5.2 73.8"/>
|
|
474
|
+
</g>
|
|
475
|
+
<g stroke-width="0.55">
|
|
476
|
+
<path d="M20 1.4 Q16.8 3.9 18.5 7.5 Q14.3 9.5 14.1 14.1 Q9.5 14.3 7.5 18.5 Q3.9 16.8 1.4 20"/>
|
|
477
|
+
<path d="M33.9 2.4 Q28.4 6.6 31.5 12.7 Q24.4 16.1 24 24 Q16.1 24.4 12.7 31.5 Q6.6 28.4 2.4 33.9"/>
|
|
478
|
+
<path d="M49.9 3.5 Q41.9 9.7 46.4 18.7 Q35.9 23.7 35.4 35.4 Q23.7 35.9 18.7 46.4 Q9.7 41.9 3.5 49.9"/>
|
|
479
|
+
<path d="M67.8 4.8 Q57 13.2 63 25.5 Q48.8 32.3 48.1 48.1 Q32.3 48.8 25.5 63 Q13.2 57 4.8 67.8"/>
|
|
480
|
+
</g>
|
|
481
|
+
<path d="M48.1 48.1 Q49.5 54 48.6 59.5" stroke-width="0.5"/>
|
|
482
|
+
</svg></div>
|
|
510
483
|
<div class="sprite-layer" id="spriteLayer"></div>
|
|
511
484
|
<div class="thread-tip" id="threadTip"></div>
|
|
512
485
|
</div>
|
|
513
486
|
<div class="tuning" id="tuning">
|
|
514
|
-
<h2>live tuning &
|
|
487
|
+
<h2>live tuning · mass loss, spawn rate, vision, per class</h2>
|
|
515
488
|
<div class="tuning-grid">
|
|
516
489
|
<div class="tuning-col spider">
|
|
517
490
|
<h3>spider</h3>
|
|
@@ -567,12 +540,25 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
567
540
|
(function () {
|
|
568
541
|
"use strict";
|
|
569
542
|
const createTicker = ${createTicker.toString()};
|
|
570
|
-
const
|
|
543
|
+
const loadWinkVendor = ${loadWinkVendor.toString()};
|
|
544
|
+
const agentKindOf = ${agentKindOf.toString()};
|
|
571
545
|
const threadCellsForSpiderPlan = ${threadCellsForSpiderPlan.toString()};
|
|
572
546
|
const facingDegreesFor = ${facingDegreesFor.toString()};
|
|
573
|
-
const
|
|
547
|
+
const resolveSpriteRequest = ${resolveSpriteRequest.toString()};
|
|
574
548
|
const nextCorpses = ${nextCorpses.toString()};
|
|
575
|
-
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) })();
|
|
576
562
|
const el = (id) => document.getElementById(id);
|
|
577
563
|
const boardFrame = el("boardFrame");
|
|
578
564
|
const boardCanvas = el("board");
|
|
@@ -636,7 +622,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
636
622
|
const goalById = {};
|
|
637
623
|
const spriteEls = {};
|
|
638
624
|
const facingByAgent = {};
|
|
639
|
-
const
|
|
625
|
+
const moodByAgent = {};
|
|
640
626
|
let corpses = {};
|
|
641
627
|
const corpseEls = {};
|
|
642
628
|
let selectedAddresseeId = null;
|
|
@@ -651,30 +637,37 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
651
637
|
function removeStaleSprites(agents) {
|
|
652
638
|
for (const id of Object.keys(spriteEls)) {
|
|
653
639
|
if (!agents[id]) {
|
|
654
|
-
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];
|
|
655
641
|
}
|
|
656
642
|
}
|
|
657
643
|
}
|
|
658
644
|
|
|
659
|
-
//
|
|
660
|
-
//
|
|
661
|
-
//
|
|
662
|
-
//
|
|
663
|
-
//
|
|
664
|
-
//
|
|
665
|
-
//
|
|
666
|
-
//
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
function
|
|
671
|
-
if (cls !== "spider" && cls !== "fly") return
|
|
672
|
-
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";
|
|
673
659
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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 || "";
|
|
678
671
|
}
|
|
679
672
|
|
|
680
673
|
function togglePov(id) {
|
|
@@ -693,18 +686,18 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
693
686
|
node.className = "sprite";
|
|
694
687
|
node.dataset.cls = cls;
|
|
695
688
|
// Property-aware resolution (sprite-templates.mjs's resolveSpriteAsset):
|
|
696
|
-
// a spider/fly's live mgx:feels
|
|
697
|
-
//
|
|
698
|
-
//
|
|
699
|
-
//
|
|
700
|
-
const
|
|
701
|
-
|
|
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;
|
|
702
695
|
// The sprite SVG lives in its own inner wrapper so plan-driven facing
|
|
703
696
|
// (a CSS rotate on THIS wrapper) never fights the outer .sprite node's
|
|
704
697
|
// own translate(-50%,-50%) positioning transform.
|
|
705
698
|
const face = document.createElement("div");
|
|
706
699
|
face.className = "sprite-face";
|
|
707
|
-
face.innerHTML = resolveSpriteFace(cls,
|
|
700
|
+
face.innerHTML = resolveSpriteFace({ class: cls, expression: mood });
|
|
708
701
|
node.appendChild(face);
|
|
709
702
|
if (!preview) {
|
|
710
703
|
node.tabIndex = 0;
|
|
@@ -723,7 +716,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
723
716
|
function applyAgents(agents) {
|
|
724
717
|
removeStaleSprites(agents);
|
|
725
718
|
for (const [id, a] of Object.entries(agents)) {
|
|
726
|
-
const cls =
|
|
719
|
+
const cls = agentKindOf(id);
|
|
727
720
|
const node = ensureSpriteEl(id, cls, a);
|
|
728
721
|
const parsed = tmctSpiderFly.parseCellId(a.cell);
|
|
729
722
|
if (!parsed) continue;
|
|
@@ -739,16 +732,15 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
739
732
|
const face = node.querySelector(".sprite-face");
|
|
740
733
|
if (face) {
|
|
741
734
|
face.style.transform = "rotate(" + facingByAgent[id] + "deg)";
|
|
742
|
-
//
|
|
735
|
+
// Mood changes tick-to-tick (a fly's fear spikes the instant a
|
|
743
736
|
// spider comes into view, a spider's joy fires the instant it eats),
|
|
744
737
|
// so this must re-resolve every redraw, not just at sprite creation —
|
|
745
|
-
// but only actually replace the DOM when the
|
|
738
|
+
// but only actually replace the DOM when the mood word itself
|
|
746
739
|
// changed since last render, never on every tick regardless.
|
|
747
|
-
const
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
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 });
|
|
752
744
|
}
|
|
753
745
|
}
|
|
754
746
|
if (a.goal) goalById[id] = a.goal;
|
|
@@ -781,7 +773,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
781
773
|
// never emotion-parameterized at all (a rotting carcass has none),
|
|
782
774
|
// just its class's own plain template under the grayscale/fade the
|
|
783
775
|
// .sprite.corpse CSS rule already applies.
|
|
784
|
-
face.innerHTML = resolveSpriteFace(corpse.cls
|
|
776
|
+
face.innerHTML = resolveSpriteFace({ class: corpse.cls });
|
|
785
777
|
node.appendChild(face);
|
|
786
778
|
spriteLayer.appendChild(node);
|
|
787
779
|
corpseEls[id] = node;
|
|
@@ -794,13 +786,6 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
794
786
|
}
|
|
795
787
|
}
|
|
796
788
|
|
|
797
|
-
function massBarHtml(cls, mass) {
|
|
798
|
-
const maxMass = cls === "spider" ? SPIDERFLY.maxSpiderMass : cls === "fly" ? SPIDERFLY.maxFlyMass : null;
|
|
799
|
-
if (typeof mass !== "number" || !maxMass) return "";
|
|
800
|
-
const pct = Math.max(0, Math.min(100, (mass / maxMass) * 100));
|
|
801
|
-
return '<div class="mass-track"><div class="mass-fill ' + esc(cls) + '" style="width:' + pct + '%"></div></div>';
|
|
802
|
-
}
|
|
803
|
-
|
|
804
789
|
// The last-created plan as a plain arrow chain ("west → west"), or
|
|
805
790
|
// "holding." when the agent's plan this tick is empty — mirrors exactly
|
|
806
791
|
// what drove this tick's sprite facing (facingDegreesFor reads the same
|
|
@@ -826,15 +811,15 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
826
811
|
|
|
827
812
|
// The full text rendering behind the click-expand panel: one sentence per
|
|
828
813
|
// other candidate the observer currently has a belief about, or "has not
|
|
829
|
-
// been observed" for a candidate it doesn't —
|
|
830
|
-
//
|
|
831
|
-
//
|
|
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.
|
|
832
819
|
function observedFactsHtml(observerId, belief) {
|
|
833
820
|
const entries = Object.entries(belief || {});
|
|
834
821
|
const lines = entries.length
|
|
835
|
-
? entries.map(([id, cell]) => '<div class="hud-detail-line">'
|
|
836
|
-
+ (cell ? esc(id) + " is at " + esc(cell) + "." : esc(id) + " has not been observed.")
|
|
837
|
-
+ "</div>").join("")
|
|
822
|
+
? entries.map(([id, cell]) => '<div class="hud-detail-line">' + esc(believedFactSentence(id, cell)) + "</div>").join("")
|
|
838
823
|
: '<div class="hud-detail-line">nothing else is on the board yet.</div>';
|
|
839
824
|
return '<div class="hud-detail"><div class="hud-detail-title">' + esc(observerId) + " observes</div>" + lines + "</div>";
|
|
840
825
|
}
|
|
@@ -843,13 +828,13 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
843
828
|
const ids = Object.keys(lastAgents).sort();
|
|
844
829
|
if (!ids.length) { hudEl.innerHTML = '<div class="hud-empty">no agents on the board.</div>'; return; }
|
|
845
830
|
hudEl.innerHTML = ids.map((id) => {
|
|
846
|
-
const cls =
|
|
831
|
+
const cls = agentKindOf(id);
|
|
847
832
|
const a = lastAgents[id];
|
|
848
833
|
const clickable = cls === "spider" || cls === "fly";
|
|
849
834
|
const expanded = clickable && expandedAgents.has(id);
|
|
850
835
|
const main = '<div class="hud-main"><span class="hud-id ' + esc(cls) + '">' + esc(id) + '</span>'
|
|
851
836
|
+ '<span class="hud-goal">' + esc(goalById[id] || "watching\\u2026") + "</span>"
|
|
852
|
-
+
|
|
837
|
+
+ meterBarHtml(cls, a.mass, massScaleFor(cls, { maxSpiderMass: SPIDERFLY.maxSpiderMass, maxFlyMass: SPIDERFLY.maxFlyMass }))
|
|
853
838
|
+ planLineHtml(a.plan)
|
|
854
839
|
+ beliefLineHtml(a.belief)
|
|
855
840
|
+ "</div>";
|
|
@@ -939,7 +924,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
939
924
|
// engine's own shipped default before the session has finished
|
|
940
925
|
// booting) — a POV toggle always reflects whatever vision range this
|
|
941
926
|
// class currently actually has, not a fixed constant.
|
|
942
|
-
const radius =
|
|
927
|
+
const radius = agentKindOf(povAgentId) === "spider"
|
|
943
928
|
? (liveConfig.spiderVisionRadius ?? tmctSpiderFly.DEFAULT_VISION_RADIUS)
|
|
944
929
|
: (liveConfig.flyVisionRadius ?? tmctSpiderFly.DEFAULT_VISION_RADIUS);
|
|
945
930
|
const visible = new Set(tmctSpiderFly.visibleCells(p.x, p.y, radius));
|