@polycode-projects/the-mechanical-code-talker 5.0.3 → 5.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -813,6 +813,7 @@ function pageScript() {
813
813
  const el = (id) => document.getElementById(id);
814
814
  let scenarioIndex = 0;
815
815
  const scenario = function () { return DATA.scenarios[scenarioIndex]; };
816
+ const gridSizeOf = function () { return scenario().gridSize || DATA.gridSize; };
816
817
  const rosterOf = function (s, role) {
817
818
  return (s.agents || []).filter(function (a) { return !role || a.role === role; }).map(function (a) { return a.id; });
818
819
  };
@@ -881,6 +882,9 @@ function pageScript() {
881
882
 
882
883
  function applyTickResult(result) {
883
884
  if (!result) return;
885
+ // The engine owns the count. Anything that advances a turn — the deck, a
886
+ // chat frame — lands here, so the page never keeps a rival tally.
887
+ if (typeof result.turn === "number") globalTurn = result.turn;
884
888
  if (result.agents) agentsById = result.agents;
885
889
  if (result.items) itemsById = result.items;
886
890
  callScene("applyTick", { agents: result.agents, items: result.items, ecology: result.ecology });
@@ -892,8 +896,7 @@ function pageScript() {
892
896
  async function runOneTick() {
893
897
  return serializeTick(async function () {
894
898
  if (!session) return null;
895
- globalTurn += 1;
896
- const result = await session.tick(globalTurn);
899
+ const result = await session.tick();
897
900
  applyTickResult(result);
898
901
  renderAll();
899
902
  return result;
@@ -921,8 +924,16 @@ function pageScript() {
921
924
  function sendCommand(line) {
922
925
  appendChat("u", line);
923
926
  if (!session) { appendChat("a", "no session is open yet \\u2014 reset to start one."); return Promise.resolve(); }
924
- return serializeTick(function () { return tmct.turn(line); }).then(function (res) {
927
+ // A chat line can run a real turn. An addressed told-fact does, and the
928
+ // visitor should watch the lie land, so the board is read back in the same
929
+ // queue slot. board() spends no turn, so a line that ran none costs
930
+ // nothing. It reports no goal or plan either, because a resting board has
931
+ // decided nothing, which is the blank boot() already draws at turn 0.
932
+ return serializeTick(async function () {
933
+ const res = await tmct.turn(line);
934
+ const board = await session.board();
925
935
  appendChat("a", res.answer);
936
+ applyTickResult(board);
926
937
  renderAll();
927
938
  return res;
928
939
  });
@@ -1027,7 +1038,7 @@ function pageScript() {
1027
1038
 
1028
1039
  // ---- the top-down map panel ---------------------------------------------
1029
1040
  function renderMapPanel() {
1030
- const dots = mapDotsFor(agentsList(), itemsList(), DATA.gridSize);
1041
+ const dots = mapDotsFor(agentsList(), itemsList(), gridSizeOf());
1031
1042
  el("mapPanelBoard").innerHTML = dots.map(function (d) {
1032
1043
  return '<span class="map-dot map-dot-' + esc(d.kind) + '" style="left:' + d.xPct + '%;top:' + d.yPct + '%" title="' + esc(d.id) + '"></span>';
1033
1044
  }).join("");
@@ -1216,7 +1227,7 @@ function pageScript() {
1216
1227
  agentsById = {};
1217
1228
  itemsById = {};
1218
1229
  el("chatInput").disabled = false;
1219
- await callScene("boot", { propPlacements: props, assetManifest: DATA.assetManifest, gridSize: DATA.gridSize, cellSize: 1 });
1230
+ await callScene("boot", { propPlacements: props, assetManifest: DATA.assetManifest, gridSize: gridSizeOf(), cellSize: 1 });
1220
1231
 
1221
1232
  // The opening board, drawn through the very path a tick takes. Without
1222
1233
  // this the page's first sight of where anything stands is the first tick,
@@ -1225,7 +1236,6 @@ function pageScript() {
1225
1236
  // the cells both come back from it rather than being guessed here.
1226
1237
  const opening = await session.board();
1227
1238
  if (seq !== bootSeq) return;
1228
- globalTurn = opening.turn || 0;
1229
1239
  camera.selectedId = Object.keys(opening.agents || {}).sort()[0] || null;
1230
1240
  applyTickResult(opening);
1231
1241
  renderAll();
@@ -1,18 +1,17 @@
1
- // spider-fly-viz.mjs — the spider-and-fly full-screen page (PLAN_SPIDER_FLY.md
2
- // §8/§9/§11): a self-contained document shaped exactly like ledger-viz.mjs/
3
- // plan-viz.mjs — one inlined <style> (importing viz-theme.mjs's shared
4
- // tokens), behaviour as an inlined IIFE but unlike those two, almost
5
- // nothing is embedded as build-time data: the whole game is LIVE client-side
6
- // state (spider-fly-browser-entry.mjs's createSpiderFlySession), so
7
- // renderSpiderFlyHtml only needs the page's own static grid geometry (which
8
- // never changes) plus a title. The engine, sprite resolver and chat turn
9
- // engine all arrive via ./spider-fly-browser.bundle.js, referenced with a
10
- // plain same-origin <script src> the same sibling-file arrangement
11
- // index.html already uses for chat-browser.bundle.js (both are the FULL
12
- // turn engine, both generated fresh per build, neither meant to be
13
- // committed), not memory-ask-browser.bundle.js's inlined-text arrangement
14
- // (that bundle is small and inlined specifically so ledger.html stays
15
- // portable on its own — that reason doesn't apply here).
1
+ // spider-fly-viz.mjs — the spider-and-fly full-screen page: a self-contained
2
+ // document shaped exactly like ledger-viz.mjs/plan-viz.mjs — one inlined <style>
3
+ // (importing viz-theme.mjs's shared tokens), behaviour as an inlined IIFE — but
4
+ // unlike those two, almost nothing is embedded as build-time data: the whole
5
+ // game is LIVE client-side state (spider-fly-browser-entry.mjs's
6
+ // createSpiderFlySession), so renderSpiderFlyHtml only needs the page's own
7
+ // static grid geometry (which never changes) plus a title. The engine, sprite
8
+ // resolver and chat turn engine all arrive via ./spider-fly-browser.bundle.js,
9
+ // referenced with a plain same-origin <script src> — the same sibling-file
10
+ // arrangement index.html already uses for chat-browser.bundle.js (both are the
11
+ // FULL turn engine, both generated fresh per build, neither meant to be
12
+ // committed), not memory-ask-browser.bundle.js's inlined-text arrangement (that
13
+ // bundle is small and inlined specifically so ledger.html stays portable on its
14
+ // own that reason doesn't apply here).
16
15
  //
17
16
  // renderSpiderFlyHtml() is pure: no I/O, deterministic output for identical
18
17
  // input. scripts/build-demo-site.mjs calls it directly and writes the result
@@ -125,19 +124,17 @@ export function facingDegreesFor(plan, previousDegrees) {
125
124
  }
126
125
 
127
126
  /**
128
- * The updated corpse set for one redraw (§A.2.5 — visual-only, entirely
129
- * client-side; the actual starve/eat removal already happened in the
130
- * engine). Every id present in `prevAgents` but absent from `agents` died
131
- * THIS tick — eaten or starved are the only two ways an agent ever leaves
132
- * the engine's own returned roster — and is added at its last-known cell
133
- * and class; every corpse already older than `lingerTurns` past its own
134
- * death turn is dropped first, so the set never grows without bound.
135
- * Returns a plain `{ [id]: { cls, cell, diedAtTurn } }` map. Pure.
136
- * `lingerTurns` defaults to a literal 4 (not the module-level
137
- * CORPSE_LINGER_TURNS constant) so this function stays fully
138
- * `.toString()`-splice safe every real caller (both the inlined page and
139
- * CORPSE_LINGER_TURNS's own callers elsewhere in this module) passes it
140
- * explicitly anyway.
127
+ * The updated corpse set for one redraw — visual-only, entirely client-side;
128
+ * the actual starve/eat removal already happened in the engine. Every id
129
+ * present in `prevAgents` but absent from `agents` died THIS tick — eaten or
130
+ * starved are the only two ways an agent ever leaves the engine's own returned
131
+ * roster — and is added at its last-known cell and class; every corpse already
132
+ * older than `lingerTurns` past its own death turn is dropped first, so the set
133
+ * never grows without bound. Returns a plain `{ [id]: { cls, cell, diedAtTurn }
134
+ * }` map. Pure. `lingerTurns` defaults to a literal 4 (not the module-level
135
+ * CORPSE_LINGER_TURNS constant) so this function stays fully `.toString()`-splice
136
+ * safe every real caller (both the inlined page and CORPSE_LINGER_TURNS's own
137
+ * callers elsewhere in this module) passes it explicitly anyway.
141
138
  */
142
139
  export function nextCorpses(prevCorpses, prevAgents, agents, turn, lingerTurns = 4) {
143
140
  const out = {};
@@ -166,9 +163,9 @@ export function nextCorpses(prevCorpses, prevAgents, agents, turn, lingerTurns =
166
163
  * back to the flat SPRITE_REGISTRY, unchanged from before this module
167
164
  * existed).
168
165
  * `?preview=1` on the page's own URL switches it into the small, auto-
169
- * playing, non-interactive mode the home page's hero iframe embeds (§11)
170
- * one file serves both the hero and the "open full-screen" link, matching
171
- * how ledger.html/plan.html are each one file embedded two ways.
166
+ * playing, non-interactive mode the home page's hero iframe embeds one file
167
+ * serves both the hero and the "open full-screen" link, matching how
168
+ * ledger.html/plan.html are each one file embedded two ways.
172
169
  * `engineBundleJs` (the built spider-fly-browser bundle's own text) inlines
173
170
  * the engine into the page instead of the sibling `<script src>`, for the
174
171
  * CLI's standalone export — one downloadable file that runs from file://
@@ -344,11 +341,11 @@ ${THEME_TOKENS_CSS}
344
341
  .hud-id.spider { color: var(--taught); } .hud-id.fly { color: var(--fly); } .hud-id.egg { color: var(--muted); }
345
342
  .hud-goal { font-size: .85rem; }
346
343
  .hud-plan, .hud-belief { font-family: ${MONO_STACK}; font-size: .66rem; color: var(--muted); margin-top: .25rem; line-height: 1.4; padding-left: .5rem; border-left: 2px solid var(--chrome-accent); }
347
- /* the click-expand facts panel (§28): beside the clicked spider/fly's own
348
- row, never a separate popover or a second panel elsewhere on the page —
349
- the same believedCellOf/beliefSnapshotFor read path spider-fly-turn.mjs
350
- already computes every tick for planning, rendered here as full
351
- sentences instead of the compact believes:-line above. */
344
+ /* the click-expand facts panel: beside the clicked spider/fly's own row,
345
+ never a separate popover or a second panel elsewhere on the page — the same
346
+ believedCellOf/beliefSnapshotFor read path spider-fly-turn.mjs already
347
+ computes every tick for planning, rendered here as full sentences instead of
348
+ the compact believes:-line above. */
352
349
  .hud-detail { flex: 1 1 auto; min-width: 0; font-family: ${MONO_STACK}; font-size: .64rem; line-height: 1.5; color: var(--chrome-well-ink); background: var(--chrome-well); border: 1px solid var(--chrome-edge-lo); box-shadow: var(--chrome-shadow-inset); border-radius: 2px; padding: .35rem .5rem; }
353
350
  .hud-detail-title { text-transform: uppercase; letter-spacing: .06em; opacity: .75; margin-bottom: .2rem; }
354
351
  /* A stat-readout track: an inset "LCD" well, filled with a segmented pip
@@ -378,11 +375,11 @@ ${THEME_TOKENS_CSS}
378
375
  .pill:hover:not(:disabled) { border-color: var(--chrome-accent); }
379
376
  .pill:disabled { opacity: .45; cursor: default; }
380
377
  .pill[data-role="addr"].active { border-color: var(--taught); color: var(--taught); }
381
- /* The dynamic deception-pill rail (§A.2.4): a true/false tag shown ONLY
382
- here, via border style/color and a small human-facing glyph — the
383
- submitted sentence itself (data-sentence, filled into #chatq on click)
384
- never carries the tag, so a clicked pill is indistinguishable from a
385
- hand-typed claim once it's in the input. */
378
+ /* The dynamic deception-pill rail: a true/false tag shown ONLY here, via
379
+ border style/color and a small human-facing glyph — the submitted sentence
380
+ itself (data-sentence, filled into #chatq on click) never carries the tag,
381
+ so a clicked pill is indistinguishable from a hand-typed claim once it's in
382
+ the input. */
386
383
  .dynpills { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .5rem; padding-top: .5rem; border-top: 1px solid var(--chrome-edge-lo); }
387
384
  .dynpills:empty { display: none; padding-top: 0; border-top: none; }
388
385
  .pill[data-role="dyn-addr"][data-active="1"] { border-color: var(--taught); color: var(--taught); }
@@ -745,13 +742,13 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
745
742
  lastAgents = agents;
746
743
  }
747
744
 
748
- // ---- corpses (§A.2.5): visual-only — the actual starve/eat removal
749
- // already happened in the engine before this redraw ever sees "agents".
750
- // A corpse sinks to the bottom row of the SAME board column it died in
751
- // ("drops to the bottom" — spider-fly-world.mjs's own x/y convention,
752
- // y = GRID_SIZE is the bottom row) and fades out once CORPSE_LINGER_TURNS
753
- // passes, drawn in the same sprite layer as every live sprite, just
754
- // grayscaled and non-interactive (see the .sprite.corpse CSS rule).
745
+ // ---- corpses: visual-only — the actual starve/eat removal already happened
746
+ // in the engine before this redraw ever sees "agents". A corpse sinks to the
747
+ // bottom row of the SAME board column it died in ("drops to the bottom" —
748
+ // spider-fly-world.mjs's own x/y convention, y = GRID_SIZE is the bottom row)
749
+ // and fades out once CORPSE_LINGER_TURNS passes, drawn in the same sprite
750
+ // layer as every live sprite, just grayscaled and non-interactive (see the
751
+ // .sprite.corpse CSS rule).
755
752
  function renderCorpses() {
756
753
  for (const id of Object.keys(corpseEls)) {
757
754
  if (!corpses[id]) { corpseEls[id].remove(); delete corpseEls[id]; }
@@ -793,12 +790,12 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
793
790
  return '<div class="hud-plan">plan: ' + text + ".</div>";
794
791
  }
795
792
 
796
- // The agent's own current world-knowledge-graph snapshot (§A.2.7) — every
797
- // OTHER live individual it believes it knows the position of, or
798
- // "unseen" when it has no belief at all. Deliberately never ground truth:
799
- // this is what the agent would actually ACT on, which a false pill or a
800
- // told fact can make visibly wrong compared to where that individual
801
- // really is — the gap IS the demonstration.
793
+ // The agent's own current world-knowledge-graph snapshot — every OTHER live
794
+ // individual it believes it knows the position of, or "unseen" when it has no
795
+ // belief at all. Deliberately never ground truth: this is what the agent
796
+ // would actually ACT on, which a false pill or a told fact can make visibly
797
+ // wrong compared to where that individual really is — the gap IS the
798
+ // demonstration.
802
799
  function beliefLineHtml(belief) {
803
800
  const entries = Object.entries(belief || {});
804
801
  if (!entries.length) return "";
@@ -1023,12 +1020,12 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1023
1020
  chatqEl.addEventListener("input", refreshPills);
1024
1021
  refreshPills();
1025
1022
 
1026
- // ---- deception pills (§A.2.4): a SEPARATE dynamic rail, alongside (never
1027
- // replacing) the static one above. tmct.page.pillsForSpiderFly is the
1028
- // exact same pure function spider-fly-turn.mjs exports — this page never
1029
- // reimplements the true/false claim logic, only renders its output and
1030
- // fills #chatq on click, same click-to-fill discipline as every other
1031
- // pill on this page (never auto-submits).
1023
+ // ---- deception pills: a SEPARATE dynamic rail, alongside (never replacing)
1024
+ // the static one above. tmct.page.pillsForSpiderFly is the exact same pure
1025
+ // function spider-fly-turn.mjs exports — this page never reimplements the
1026
+ // true/false claim logic, only renders its output and fills #chatq on click,
1027
+ // same click-to-fill discipline as every other pill on this page (never
1028
+ // auto-submits).
1032
1029
  function renderDynamicPills() {
1033
1030
  if (!session || !Object.keys(lastAgents).length) { dynamicPillsEl.innerHTML = ""; return; }
1034
1031
  const result = tmct.page.pillsForSpiderFly(lastAgents, selectedAddresseeId, {});