@polycode-projects/the-mechanical-code-talker 5.0.5 → 5.0.6
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 +72 -19
- package/package.json +1 -1
- package/src/domain/ask.mjs +2 -4
- package/src/domain/codegraph.mjs +23 -9
- package/src/domain/game-config.mjs +12 -0
- package/src/services/adventure-viz.mjs +5 -2
- package/src/services/chat-page-viz.mjs +5 -2
- package/src/services/chat.mjs +104 -16
- package/src/services/code-explorer-viz.mjs +3 -2
- package/src/services/ingest-viz.mjs +5 -4
- package/src/services/ledger-viz.mjs +5 -2
- package/src/services/mud-viz.mjs +4 -3
- package/src/services/mudiii-scene.mjs +136 -12
- package/src/services/mudiii-viz.mjs +540 -64
- package/src/services/p2p-room.mjs +1 -1
- package/src/services/plan-viz.mjs +3 -2
- package/src/services/predator-prey.mjs +65 -10
- package/src/services/research-viz.mjs +5 -4
- package/src/services/spider-fly-viz.mjs +3 -2
- package/src/services/sprite-catalog-viz.mjs +3 -2
- package/src/services/viz-theme.mjs +20 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +86 -86
- package/src/surfaces/web/mudiii-browser-entry.mjs +70 -34
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
// renderIngestHtml() is pure: no I/O, deterministic output for identical
|
|
26
26
|
// input. scripts/build-demo-site.mjs calls it directly and writes the result
|
|
27
27
|
// to public/ingest.html, after ingest-browser.bundle.js already exists.
|
|
28
|
-
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml } from "./viz-theme.mjs";
|
|
28
|
+
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml, demoEyebrowHtml, EYEBROW_LINKS_CSS } from "./viz-theme.mjs";
|
|
29
29
|
import {
|
|
30
30
|
bandLabelFor,
|
|
31
31
|
statsSummaryLine,
|
|
@@ -75,7 +75,8 @@ ${THEME_TOKENS_CSS}
|
|
|
75
75
|
header.topbar { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .7rem 1.1rem; border-bottom: 1px solid var(--line); flex-wrap: wrap; }
|
|
76
76
|
.brand { display: flex; flex-direction: column; gap: .1rem; }
|
|
77
77
|
.eyebrow { font-family: ${MONO_STACK}; font-size: .78rem; letter-spacing: .08em; color: var(--muted); }
|
|
78
|
-
|
|
78
|
+
${EYEBROW_LINKS_CSS}
|
|
79
|
+
.subtitle { margin: 0; font-size: .82rem; font-weight: 400; color: var(--muted); }
|
|
79
80
|
|
|
80
81
|
/* the live memory count, in the topbar rather than the status line: it is
|
|
81
82
|
the one number that says what this page's memory actually holds, and the
|
|
@@ -181,8 +182,8 @@ ${THEME_TOKENS_CSS}
|
|
|
181
182
|
<div class="ingestCol">
|
|
182
183
|
<header class="topbar">
|
|
183
184
|
<div class="brand">
|
|
184
|
-
<span class="eyebrow"
|
|
185
|
-
<
|
|
185
|
+
<span class="eyebrow">${demoEyebrowHtml("ingest", "ingest")}</span>
|
|
186
|
+
<h1 class="subtitle">ingest — paste or drop text. It keeps the facts it can ground and skips the rest.</h1>
|
|
186
187
|
</div>
|
|
187
188
|
<div class="topbar-right">
|
|
188
189
|
<span class="fact-pill" id="factPill" aria-live="polite"
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// doesn't do: it reads the checked-in chat-dock engine bundle.
|
|
16
16
|
|
|
17
17
|
import { loadMemory, readFactRows, findContradictions, normFactTerm } from "../adapters/memory/core.mjs";
|
|
18
|
-
import { THEME_TOKENS_CSS, MONO_STACK, escapeHtml, embedJson, countLabel, TOKENS } from "./viz-theme.mjs";
|
|
18
|
+
import { THEME_TOKENS_CSS, MONO_STACK, escapeHtml, embedJson, countLabel, TOKENS, demoEyebrowHtml, EYEBROW_LINKS_CSS } from "./viz-theme.mjs";
|
|
19
19
|
import { createTicker, prefersReducedMotion } from "./viz-ticker.mjs";
|
|
20
20
|
import { bfsLevels } from "../domain/planning.mjs";
|
|
21
21
|
import { pluralOf } from "../domain/inflect.mjs";
|
|
@@ -644,7 +644,9 @@ ${DASH_DARK_CHROME_CSS}
|
|
|
644
644
|
body { margin: 0; background: var(--bg); color: var(--ink); font-family: ${DASH_SANS_STACK}; font-size: 15px; line-height: 1.5; }
|
|
645
645
|
.mono { font-family: ${MONO_STACK}; }
|
|
646
646
|
main { max-width: 1200px; margin: 0 auto; padding: 1.4rem 1.2rem 3rem; }
|
|
647
|
+
.visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
|
|
647
648
|
.eyebrow { font-family: ${MONO_STACK}; font-size: .7rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); display: flex; flex-wrap: wrap; gap: .4em 1.2em; margin-bottom: .9rem; }
|
|
649
|
+
${EYEBROW_LINKS_CSS}
|
|
648
650
|
button { font: inherit; color: inherit; background: none; border: none; padding: 0; cursor: pointer; }
|
|
649
651
|
button:focus-visible, input:focus-visible { outline: 2px solid var(--corpus); outline-offset: 2px; border-radius: 4px; }
|
|
650
652
|
.topbar { display: flex; flex-wrap: wrap; align-items: center; gap: .8rem; background: var(--card); border: 1px solid var(--line); border-radius: 6px; padding: .5rem .7rem; margin-bottom: 1.1rem; }
|
|
@@ -789,7 +791,8 @@ ${DASH_DARK_CHROME_CSS}
|
|
|
789
791
|
</head>
|
|
790
792
|
<body>
|
|
791
793
|
<main>
|
|
792
|
-
<
|
|
794
|
+
<h1 class="visually-hidden">memory ledger</h1>
|
|
795
|
+
<div class="eyebrow"><span>${demoEyebrowHtml("ledger", "memory ledger")}</span><span id="counts"></span></div>
|
|
793
796
|
${dashboardHtml(stats)}
|
|
794
797
|
<div class="topbar">
|
|
795
798
|
<nav class="crumbs" id="crumbs" aria-label="Focus trail"></nav>
|
package/src/services/mud-viz.mjs
CHANGED
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
// actually executed, never a race between panes.
|
|
77
77
|
import {
|
|
78
78
|
THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml, embedJson, embedScriptText, scenarioLabel,
|
|
79
|
-
wordBeforeCursor, rowsForWorld, appendLogLine,
|
|
79
|
+
wordBeforeCursor, rowsForWorld, appendLogLine, demoEyebrowHtml, EYEBROW_LINKS_CSS,
|
|
80
80
|
} from "./viz-theme.mjs";
|
|
81
81
|
import { createTicker, createSerialQueue } from "./viz-ticker.mjs";
|
|
82
82
|
import { directedGridLayout, roomGraphSvg, levelsOf, EXIT_DELTA } from "./viz-room-graph.mjs";
|
|
@@ -392,7 +392,7 @@ ${MUD_SHARE_SKIN}
|
|
|
392
392
|
<body>
|
|
393
393
|
<main>
|
|
394
394
|
<header class="mud-topbar">
|
|
395
|
-
<h1 class="eyebrow"
|
|
395
|
+
<h1 class="eyebrow">${demoEyebrowHtml("mud", "mud")}</h1>
|
|
396
396
|
<div class="mud-topbar-actions">
|
|
397
397
|
<button type="button" class="state-pill" id="statePill" aria-expanded="false" aria-controls="netPanel"
|
|
398
398
|
title="whether this burrow is shared with anyone"><i class="state-dot"></i><span id="statePillWord">not shared</span></button>
|
|
@@ -411,7 +411,7 @@ ${scenarioList.length > 1 ? ` <select id="scenarioSelect" class="deck-sel
|
|
|
411
411
|
${scenarioList.map((s, i) => ` <option value="${i}"${i === 0 ? " selected" : ""}>${escapeHtml(s.label || scenarioLabel(s.worldPayload?.name))}</option>`).join("\n")}
|
|
412
412
|
</select>` : ""}
|
|
413
413
|
<button type="button" id="editModeBtn" aria-pressed="false">edit</button>
|
|
414
|
-
<label class="deck-teach" title="With this on, a sentence like "
|
|
414
|
+
<label class="deck-teach" title="With this on, a sentence like "Pebble lies in the garden." writes a fact into the world instead of running as a command.">
|
|
415
415
|
<input type="checkbox" id="teachToggle">
|
|
416
416
|
teach
|
|
417
417
|
</label>
|
|
@@ -590,6 +590,7 @@ const MUD_STYLE = `
|
|
|
590
590
|
.mono { font-family: ${MONO_STACK}; }
|
|
591
591
|
main { max-width: 1280px; margin: 0 auto; padding: 1.1rem 1.2rem 2.4rem; }
|
|
592
592
|
.eyebrow { font-family: ${MONO_STACK}; font-weight: 500; font-size: .72rem; letter-spacing: .16em; text-transform: uppercase; color: var(--parchment); opacity: .9; margin: 0 0 .8rem; }
|
|
593
|
+
${EYEBROW_LINKS_CSS}
|
|
593
594
|
|
|
594
595
|
/* ---- the header: brand on the left, the sharing chrome on the right ----
|
|
595
596
|
The same arrangement chat.html's topbar holds — sharing is page chrome,
|
|
@@ -42,12 +42,10 @@
|
|
|
42
42
|
// agent or item, never the mid-lerp position. This is what an e2e
|
|
43
43
|
// assertion reads, and what a later screenshot ready-check reads too
|
|
44
44
|
// (see test-e2e/pages-mudiii.test.mjs's own header).
|
|
45
|
+
// .flashCell(cellId) / .showRoute(cells) / .clearRoute() — the clicked
|
|
46
|
+
// cell and the route the followed agent is walking to it, drawn along
|
|
47
|
+
// the cells the world's own exit search returned.
|
|
45
48
|
// .ready() — whether boot() has finished at least once.
|
|
46
|
-
// These three calls are not yet wired into mudiii-viz.mjs's own
|
|
47
|
-
// boot()/applyTickResult()/camera handlers — that file is owned by the viz
|
|
48
|
-
// track, not this one; the coordinator's own report names the exact call
|
|
49
|
-
// sites needed, mirroring how window.mudiiiHandleSceneClick was already
|
|
50
|
-
// added for the reverse direction.
|
|
51
49
|
//
|
|
52
50
|
// Reused from mudiii-viz.mjs rather than re-derived, off its own frozen
|
|
53
51
|
// exports: `roleOfAgentId`, `cellToWorld`, `cellFromGroundPoint`,
|
|
@@ -366,6 +364,13 @@ export function mudiiiSceneScript({ canvasId, statusId, gridSize, cellSize } = {
|
|
|
366
364
|
// removal (source "ecology") apart from applyTick's own diff no-op
|
|
367
365
|
// (source "diff") without guessing from a vanished mesh alone.
|
|
368
366
|
var removalLog = [];
|
|
367
|
+
var routeLine = null, routeCells = [];
|
|
368
|
+
var flashMesh = null, flashUntil = 0;
|
|
369
|
+
var FLASH_MS = 600;
|
|
370
|
+
// The floor a one-shot flourish is held for when its own clip is shorter,
|
|
371
|
+
// long enough that a bite reads as a bite at the deck's fastest tick.
|
|
372
|
+
var ONE_SHOT_HOLD_MIN_MS = 450;
|
|
373
|
+
var tickRungs = {};
|
|
369
374
|
var cameraState = { mode: "overhead", selectedId: null };
|
|
370
375
|
var cameraTween = null, lookAtTween = null;
|
|
371
376
|
var lastFrameTs = null;
|
|
@@ -557,10 +562,8 @@ export function mudiiiSceneScript({ canvasId, statusId, gridSize, cellSize } = {
|
|
|
557
562
|
}
|
|
558
563
|
}
|
|
559
564
|
|
|
560
|
-
// ---- items: crumbs and morsels, the committed hay bale at
|
|
561
|
-
//
|
|
562
|
-
// matching the on-ground footprint the primitive spheres they replace were
|
|
563
|
-
// already tuned to. ---------------------------------------------------------
|
|
565
|
+
// ---- items: crumbs and morsels, both the committed hay bale, each at
|
|
566
|
+
// whatever targetHeight its own data/mudiii-assets.json row names. ----------
|
|
564
567
|
function itemAssetKeyFor(kind) { return kind === "morsel" ? "food-morsel" : "food-crumb"; }
|
|
565
568
|
|
|
566
569
|
function animateScaleTo(object3D, target, now) {
|
|
@@ -635,21 +638,56 @@ export function mudiiiSceneScript({ canvasId, statusId, gridSize, cellSize } = {
|
|
|
635
638
|
// correctly clone a SkinnedMesh, and at this roster size — a handful of
|
|
636
639
|
// foxes and goblins — the extra memory of a separate load per agent is
|
|
637
640
|
// trivial next to that whole class of bug). ---------------------------------
|
|
641
|
+
// The id, drawn to a canvas and hung above the agent as a sprite. Parenting
|
|
642
|
+
// it to the agent's own group means it rides the movement tween with no
|
|
643
|
+
// per-frame bookkeeping, and a sprite always faces the camera, so there is
|
|
644
|
+
// no facing maths either. sizeAttenuation off holds it at a constant size on
|
|
645
|
+
// screen — an id that shrinks to nothing as the camera pulls back is worse
|
|
646
|
+
// than no id at all — which is why the scale below is in screen fractions
|
|
647
|
+
// rather than world units.
|
|
648
|
+
var LABEL_SCREEN_WIDTH = 0.13;
|
|
649
|
+
function makeAgentLabel(id, height) {
|
|
650
|
+
var canvas = document.createElement("canvas");
|
|
651
|
+
canvas.width = 256;
|
|
652
|
+
canvas.height = 64;
|
|
653
|
+
var ctx = canvas.getContext("2d");
|
|
654
|
+
ctx.font = "bold 38px ui-monospace, SFMono-Regular, Menlo, monospace";
|
|
655
|
+
ctx.textAlign = "center";
|
|
656
|
+
ctx.textBaseline = "middle";
|
|
657
|
+
ctx.lineWidth = 8;
|
|
658
|
+
ctx.lineJoin = "round";
|
|
659
|
+
ctx.strokeStyle = "rgba(20,16,10,.85)";
|
|
660
|
+
ctx.strokeText(id, 128, 34);
|
|
661
|
+
ctx.fillStyle = "#F3ECDD";
|
|
662
|
+
ctx.fillText(id, 128, 34);
|
|
663
|
+
var material = new THREE.SpriteMaterial({
|
|
664
|
+
map: new THREE.CanvasTexture(canvas), transparent: true, sizeAttenuation: false, depthWrite: false,
|
|
665
|
+
});
|
|
666
|
+
var sprite = new THREE.Sprite(material);
|
|
667
|
+
sprite.scale.set(LABEL_SCREEN_WIDTH, LABEL_SCREEN_WIDTH / 4, 1);
|
|
668
|
+
sprite.position.y = height + 0.3;
|
|
669
|
+
sprite.name = "label-" + id;
|
|
670
|
+
return sprite;
|
|
671
|
+
}
|
|
672
|
+
|
|
638
673
|
function ensureAgent(id, agent) {
|
|
639
674
|
if (agentGroups[id]) return agentGroups[id];
|
|
640
675
|
var kind = roleOfAgentId(id);
|
|
641
676
|
var entry = {
|
|
642
677
|
group: new THREE.Group(), tween: null, cell: null, facing: agent.facing, role: agent.role,
|
|
643
678
|
kind: kind, mixer: null, actions: {}, currentClip: null, clipMap: null, oneShotAction: null,
|
|
679
|
+
oneShotUntil: 0, model: null,
|
|
644
680
|
};
|
|
645
681
|
entry.group.visible = false;
|
|
646
682
|
scene.add(entry.group);
|
|
647
683
|
agentGroups[id] = entry;
|
|
648
684
|
var asset = manifestByKind[kind];
|
|
685
|
+
entry.group.add(makeAgentLabel(id, asset ? asset.targetHeight : 1));
|
|
649
686
|
if (asset) {
|
|
650
687
|
loadGlbRaw(modelUrlFor(asset.destPath)).then(function (gltf) {
|
|
651
688
|
normalizeToHeight(gltf.scene, asset.targetHeight);
|
|
652
689
|
entry.group.add(gltf.scene);
|
|
690
|
+
entry.model = gltf.scene;
|
|
653
691
|
entry.group.visible = true;
|
|
654
692
|
entry.clipMap = asset.clips || {};
|
|
655
693
|
entry.mixer = new THREE.AnimationMixer(gltf.scene);
|
|
@@ -671,6 +709,12 @@ export function mudiiiSceneScript({ canvasId, statusId, gridSize, cellSize } = {
|
|
|
671
709
|
// keep blending into every clip after it, forever). Fade it out before
|
|
672
710
|
// any of the guards below can skip the rest of this call.
|
|
673
711
|
if (entry.oneShotAction) {
|
|
712
|
+
// A flourish holds for its own clip length, floored, before anything may
|
|
713
|
+
// fade it out. At the deck's 220ms default the next tick otherwise
|
|
714
|
+
// landed during the wind-up and a bite read as a twitch. This holds the
|
|
715
|
+
// ANIMATION only: the tick that arrived has already moved the agent, so
|
|
716
|
+
// the simulation never waits on a flourish.
|
|
717
|
+
if (performance.now() < entry.oneShotUntil) return;
|
|
674
718
|
entry.oneShotAction.fadeOut(0.15);
|
|
675
719
|
entry.oneShotAction = null;
|
|
676
720
|
}
|
|
@@ -702,6 +746,9 @@ export function mudiiiSceneScript({ canvasId, statusId, gridSize, cellSize } = {
|
|
|
702
746
|
action.fadeIn(0.15).play();
|
|
703
747
|
if (prev) prev.fadeOut(0.15);
|
|
704
748
|
entry.oneShotAction = action;
|
|
749
|
+
var clip = typeof action.getClip === "function" ? action.getClip() : null;
|
|
750
|
+
entry.oneShotUntil = performance.now()
|
|
751
|
+
+ Math.max(ONE_SHOT_HOLD_MIN_MS, clip && clip.duration ? clip.duration * 1000 : 0);
|
|
705
752
|
entry.currentClip = null;
|
|
706
753
|
}
|
|
707
754
|
|
|
@@ -731,7 +778,10 @@ export function mudiiiSceneScript({ canvasId, statusId, gridSize, cellSize } = {
|
|
|
731
778
|
}
|
|
732
779
|
if (entry.clipMap) {
|
|
733
780
|
var moving = singleHop;
|
|
734
|
-
|
|
781
|
+
// A hand-driven step is a walk, whatever the agent believes: it did not
|
|
782
|
+
// choose to chase or flee, the visitor chose for it.
|
|
783
|
+
var action = moving && tickRungs[id] === "driven" ? "driven" : currentActionFor(id, lastAgentsById, moving);
|
|
784
|
+
playClip(entry, clipForAction(agent.role, action, entry.clipMap));
|
|
735
785
|
}
|
|
736
786
|
}
|
|
737
787
|
|
|
@@ -766,6 +816,61 @@ export function mudiiiSceneScript({ canvasId, statusId, gridSize, cellSize } = {
|
|
|
766
816
|
}
|
|
767
817
|
}
|
|
768
818
|
|
|
819
|
+
// ---- the clicked cell, and the route to it --------------------------------
|
|
820
|
+
// The line follows the cells the world's own exit search returned, never a
|
|
821
|
+
// straight segment from agent to target: a straight one cuts through
|
|
822
|
+
// buildings and promises a walk the board would refuse.
|
|
823
|
+
function clearRoute() {
|
|
824
|
+
routeCells = [];
|
|
825
|
+
if (!routeLine) return;
|
|
826
|
+
if (routeLine.parent) routeLine.parent.remove(routeLine);
|
|
827
|
+
routeLine.geometry.dispose();
|
|
828
|
+
routeLine.material.dispose();
|
|
829
|
+
routeLine = null;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function showRoute(cells) {
|
|
833
|
+
clearRoute();
|
|
834
|
+
if (!scene || !cells || cells.length < 2) return;
|
|
835
|
+
var points = [];
|
|
836
|
+
for (var i = 0; i < cells.length; i += 1) {
|
|
837
|
+
var world = cellToWorld(cells[i], GRID_SIZE, CELL_SIZE);
|
|
838
|
+
if (!world) return;
|
|
839
|
+
points.push(new THREE.Vector3(world.x, 0.09, world.z));
|
|
840
|
+
}
|
|
841
|
+
routeLine = new THREE.Line(
|
|
842
|
+
new THREE.BufferGeometry().setFromPoints(points),
|
|
843
|
+
new THREE.LineBasicMaterial({ color: 0xd98a2b }),
|
|
844
|
+
);
|
|
845
|
+
routeLine.name = "route";
|
|
846
|
+
scene.add(routeLine);
|
|
847
|
+
routeCells = cells.slice();
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function clearFlash() {
|
|
851
|
+
if (!flashMesh) return;
|
|
852
|
+
if (flashMesh.parent) flashMesh.parent.remove(flashMesh);
|
|
853
|
+
flashMesh.geometry.dispose();
|
|
854
|
+
flashMesh.material.dispose();
|
|
855
|
+
flashMesh = null;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function flashCell(cell) {
|
|
859
|
+
if (!scene) return;
|
|
860
|
+
var world = cellToWorld(cell, GRID_SIZE, CELL_SIZE);
|
|
861
|
+
if (!world) return;
|
|
862
|
+
clearFlash();
|
|
863
|
+
flashMesh = new THREE.Mesh(
|
|
864
|
+
new THREE.PlaneGeometry(CELL_SIZE, CELL_SIZE),
|
|
865
|
+
new THREE.MeshBasicMaterial({ color: 0xd98a2b, transparent: true, opacity: 0.75 }),
|
|
866
|
+
);
|
|
867
|
+
flashMesh.name = "cell-flash";
|
|
868
|
+
flashMesh.rotation.x = -Math.PI / 2;
|
|
869
|
+
flashMesh.position.set(world.x, 0.03, world.z);
|
|
870
|
+
scene.add(flashMesh);
|
|
871
|
+
flashUntil = performance.now() + FLASH_MS;
|
|
872
|
+
}
|
|
873
|
+
|
|
769
874
|
// ---- camera ---------------------------------------------------------------
|
|
770
875
|
function agentSnapshotFor(id) {
|
|
771
876
|
var entry = agentGroups[id];
|
|
@@ -817,6 +922,9 @@ export function mudiiiSceneScript({ canvasId, statusId, gridSize, cellSize } = {
|
|
|
817
922
|
itemMeshes = {};
|
|
818
923
|
lastAgentsById = {};
|
|
819
924
|
lastItemsById = {};
|
|
925
|
+
tickRungs = {};
|
|
926
|
+
clearRoute();
|
|
927
|
+
clearFlash();
|
|
820
928
|
removalLog = [];
|
|
821
929
|
manifestByKind = buildManifestByKind(input && input.assetManifest);
|
|
822
930
|
await placeProps((input && input.propPlacements) || []);
|
|
@@ -829,6 +937,7 @@ export function mudiiiSceneScript({ canvasId, statusId, gridSize, cellSize } = {
|
|
|
829
937
|
var now = performance.now();
|
|
830
938
|
var agents = (tick && tick.agents) || {};
|
|
831
939
|
var items = (tick && tick.items) || {};
|
|
940
|
+
tickRungs = (tick && tick.rungs) || {};
|
|
832
941
|
for (var id in agents) if (Object.prototype.hasOwnProperty.call(agents, id)) applyAgentTick(id, agents[id], now);
|
|
833
942
|
for (var itemId in items) if (Object.prototype.hasOwnProperty.call(items, itemId)) applyItemTick(itemId, items[itemId], now);
|
|
834
943
|
// Ecology first: an eaten agent/item is already gone from this tick's own
|
|
@@ -859,6 +968,11 @@ export function mudiiiSceneScript({ canvasId, statusId, gridSize, cellSize } = {
|
|
|
859
968
|
}
|
|
860
969
|
if (entry.mixer) entry.mixer.update(deltaSec);
|
|
861
970
|
}
|
|
971
|
+
if (flashMesh) {
|
|
972
|
+
var flashLeft = flashUntil - performance.now();
|
|
973
|
+
if (flashLeft <= 0) clearFlash();
|
|
974
|
+
else flashMesh.material.opacity = 0.75 * (flashLeft / FLASH_MS);
|
|
975
|
+
}
|
|
862
976
|
if (cameraTween) { var cp = tweenStep(cameraTween, ts); camera3.position.set(cp.x, cp.y, cp.z); }
|
|
863
977
|
if (lookAtTween) { var lp = tweenStep(lookAtTween, ts); camera3.lookAt(lp.x, lp.y, lp.z); }
|
|
864
978
|
if (orbitControls && orbitControls.enabled) orbitControls.update();
|
|
@@ -869,6 +983,13 @@ export function mudiiiSceneScript({ canvasId, statusId, gridSize, cellSize } = {
|
|
|
869
983
|
boot: boot,
|
|
870
984
|
applyTick: applyTick,
|
|
871
985
|
setCamera: setCamera,
|
|
986
|
+
flashCell: flashCell,
|
|
987
|
+
showRoute: showRoute,
|
|
988
|
+
clearRoute: clearRoute,
|
|
989
|
+
// The route currently drawn, cell by cell — an e2e assertion's read, so
|
|
990
|
+
// it can check the line follows the board's own exits rather than
|
|
991
|
+
// counting pixels on a software renderer.
|
|
992
|
+
routeCellsDrawn: function () { return routeCells.slice(); },
|
|
872
993
|
cellOf: function (id) {
|
|
873
994
|
if (agentGroups[id]) return agentGroups[id].cell;
|
|
874
995
|
if (itemMeshes[id]) return itemMeshes[id].cell;
|
|
@@ -886,10 +1007,13 @@ export function mudiiiSceneScript({ canvasId, statusId, gridSize, cellSize } = {
|
|
|
886
1007
|
// manifest's own targetHeight to compare it against — an e2e assertion's
|
|
887
1008
|
// read, so it goes through the group actually in the scene rather than a
|
|
888
1009
|
// second, locally invented measurement.
|
|
1010
|
+
// Measured through the loaded MODEL, never the whole group: the group
|
|
1011
|
+
// also carries the id label, whose sprite geometry would widen the box
|
|
1012
|
+
// and report a height nobody rendered.
|
|
889
1013
|
meshHeightOf: function (id) {
|
|
890
1014
|
var entry = agentGroups[id];
|
|
891
|
-
if (!entry || !entry.
|
|
892
|
-
var box = new THREE.Box3().setFromObject(entry.
|
|
1015
|
+
if (!entry || !entry.model) return null;
|
|
1016
|
+
var box = new THREE.Box3().setFromObject(entry.model);
|
|
893
1017
|
var size = new THREE.Vector3();
|
|
894
1018
|
box.getSize(size);
|
|
895
1019
|
var asset = manifestByKind[entry.kind];
|