From 4483622746c7446db822badfda2725615caee244 Mon Sep 17 00:00:00 2001 From: Sam Keen Date: Thu, 8 Oct 2026 14:34:27 -0700 Subject: [PATCH] Run The Sunken Crypt on three decision models, and say what the latencies measure godot-jev: compare_backends.gd takes extra Open Jev servers with `--openjev name=url`, records how many options each question offered and the thresholds used, and counts agreement for every pair of backends. The 16 session lines were run for 3 rounds on TypeSafe Jev (jev-1.13.0), Open Jev 27B and Open Jev Flash 9B: all three landed 48 of 48 with no wrong move, and Flash 9B, being less sure, made the game ask on two lines. The run is written up in eval/results-2026-10-08.md and recorded under check 2 in the README. Both result files it rests on are committed, including the 2026-09-30 run it is compared with. docs: a godot-jev results page in the style of the model-router one. Its data comes from docs/tools/build-godot-jev-data.gd, which replays every recorded answer through the game's own parser, at the run's thresholds and at each setting the page's sliders reach, and refuses to write if an outcome or a summary differs from the run. The styles, tooltips, scoreboard and latency chart the two results pages share move to docs/assets/results.css and results.js. Latency: the local models' timings are from 4-bit MLX builds on one laptop running macOS with other applications open, and the hosted ones include the network. The landing page, both results pages and the READMEs now say that these are readings of a setup, not of how fast the models are. Co-Authored-By: Claude Opus 5.5 --- README.md | 10 +- docs/README.md | 17 +- docs/{model-router => assets}/results.css | 33 +- docs/assets/results.js | 158 + docs/godot-jev/app.js | 808 +++ docs/godot-jev/data.js | 2 + docs/godot-jev/index.html | 593 +++ docs/godot-jev/page.css | 192 + docs/godot-jev/screenshot.png | Bin 0 -> 33800 bytes docs/index.html | 102 +- docs/model-router/app.js | 153 +- docs/model-router/index.html | 29 +- docs/tools/build-godot-jev-data.gd | 269 + godot-jev/README.md | 39 +- godot-jev/eval/compare.gd | 28 + godot-jev/eval/compare_backends.gd | 63 +- godot-jev/eval/results-2026-09-30T101313.json | 2928 +++++++++++ godot-jev/eval/results-2026-10-08.md | 214 + godot-jev/eval/results-2026-10-08T134205.json | 4561 +++++++++++++++++ godot-jev/tests/test_compare.gd | 37 + 20 files changed, 10014 insertions(+), 222 deletions(-) rename docs/{model-router => assets}/results.css (96%) create mode 100644 docs/assets/results.js create mode 100644 docs/godot-jev/app.js create mode 100644 docs/godot-jev/data.js create mode 100644 docs/godot-jev/index.html create mode 100644 docs/godot-jev/page.css create mode 100644 docs/godot-jev/screenshot.png create mode 100644 docs/tools/build-godot-jev-data.gd create mode 100644 godot-jev/eval/results-2026-09-30T101313.json create mode 100644 godot-jev/eval/results-2026-10-08.md create mode 100644 godot-jev/eval/results-2026-10-08T134205.json diff --git a/README.md b/README.md index 8ab397d..187e121 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ confirm it, and tests that run without an API key. The first three were picked f | Experiment | The idea | Stack | Status | | --- | --- | --- | --- | | [misconception-gates](misconception-gates/) | Grade a learner's free-text answer by *which misconception* it shows, and jump to the lesson section that fixes it | Node, no dependencies (drops into js-animation-sandbox) | Skeleton | -| [godot-jev](godot-jev/) | A Godot 4 add-on for Jev, and a text adventure whose parser understands anything but can only choose moves the author wrote | Godot 4.7, GDScript | Live: TypeSafe Jev and Open Jev | +| [godot-jev](godot-jev/) | A Godot 4 add-on for Jev, and a text adventure whose parser understands anything but can only choose moves the author wrote | Godot 4.7, GDScript | Live: TypeSafe Jev, Open Jev 27B and Flash 9B, on 16 scripted lines | | [semver-judge](semver-judge/) | Decide the next version from the commits since the last tag, flagging only the uncertain commits that could change the answer | Python, GitHub Action | Skeleton | | [model-router](model-router/) | Pick the model for a sub-agent task in an agent harness: rules first, then a Score over the configured routes, moving up when a weaker model is too likely to fall short | Node, no dependencies | Live: TypeSafe Jev, Open Jev 27B and Flash 9B, on starter tasks only | @@ -53,8 +53,12 @@ cd model-router && node --test && node eval/run.mjs ``` godot-jev and model-router can use a local Open Jev server instead (`JEV_BACKEND=openjev`). -godot-jev's `eval/compare_backends.gd` asks both the same questions, and model-router's -`eval/compare.mjs` puts runs side by side; see their READMEs. +godot-jev's `eval/compare_backends.gd` asks every backend the same questions, and model-router's +`eval/compare.mjs` puts runs side by side; see their READMEs. Both recorded runs are walked +through on the [docs site](https://alteredcraft.github.io/system-one-experiments/). + +Latencies recorded for the local models describe one laptop (4-bit MLX builds on an Apple M5 Pro, +macOS, other applications open), not how fast those models are. ## Limits that apply to all of them diff --git a/docs/README.md b/docs/README.md index 230832d..6e6e927 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,9 +10,11 @@ folder `/docs`. | Path | What it is | | --- | --- | | `index.html` | Landing page: what System One models are, the four experiments, the protocol, the notebook log | -| `assets/` | Shared styles (`site.css`) and the theme toggle (`site.js`) | -| `model-router/` | The 2026-10-08 model-router run: `index.html`, `results.css`, `app.js`, and generated `data.js` | +| `assets/` | Shared by every page: styles (`site.css`) and the theme toggle (`site.js`). Shared by the results pages: `results.css`, and `results.js` (tooltips, chart helpers, the side contents) | +| `model-router/` | The 2026-10-08 model-router run: `index.html`, `app.js`, and generated `data.js` | +| `godot-jev/` | The 2026-10-08 three-model godot-jev run: `index.html`, `page.css`, `app.js`, a screenshot of the game, and generated `data.js` | | `tools/build-model-router-data.mjs` | Builds `model-router/data.js` | +| `tools/build-godot-jev-data.gd` | Builds `godot-jev/data.js` | `model-router/data.js` is generated, not written by hand. The script reads the three recorded runs in `model-router/eval/results-2026-10-08T*.json`, routes every answer with model-router's own policy @@ -23,4 +25,15 @@ ones each run saved. After a change to those runs or to the policy, regenerate i node docs/tools/build-model-router-data.mjs ``` +`godot-jev/data.js` is generated the same way, by a Godot script, because the policy it replays is +the game's parser. It reads `godot-jev/eval/results-2026-10-08T134205.json`, puts every recorded +answer back through `demo/parser.gd` in the game state where it was asked, at the run's thresholds +and at every setting the page's sliders reach, and refuses to write if an outcome or a summary +differs from what the run saved: + +```bash +godot --headless --path godot-jev --import +godot --headless --path godot-jev --script ../docs/tools/build-godot-jev-data.gd +``` + To preview locally: `python3 -m http.server --directory docs`. diff --git a/docs/model-router/results.css b/docs/assets/results.css similarity index 96% rename from docs/model-router/results.css rename to docs/assets/results.css index fa6aba2..ee3834b 100644 --- a/docs/model-router/results.css +++ b/docs/assets/results.css @@ -1,4 +1,4 @@ -/* model-router results page. Shared tokens are in ../assets/site.css. */ +/* Shared by the results pages in docs/. Tokens are in site.css. */ @media (max-width: 520px) { .hide-xs { @@ -1210,6 +1210,37 @@ input[type="range"] { } } +/* A note the reader needs before the figures beside it. */ +.callout { + background: color-mix(in srgb, var(--warning) 8%, var(--surface)); + border: 1px solid color-mix(in srgb, var(--warning) 45%, var(--rule)); + border-left-width: 3px; + border-radius: var(--radius); + padding: 16px 20px; + margin: 0 0 20px; + max-width: 900px; + font-size: 0.95rem; + color: var(--ink-2); +} +.callout .label { + display: block; + margin-bottom: 6px; + color: var(--warning-text); +} +.callout p { + margin: 0 0 0.6em; +} +.callout p:last-child { + margin-bottom: 0; +} +.callout b { + color: var(--ink); + font-weight: 600; +} +.hero-stats .k a { + white-space: nowrap; +} + /* Caveats and details */ .caveats { display: grid; diff --git a/docs/assets/results.js b/docs/assets/results.js new file mode 100644 index 0000000..4acd246 --- /dev/null +++ b/docs/assets/results.js @@ -0,0 +1,158 @@ +/* Shared by the results pages: tooltips, the scoreboard, the latency lanes, + chart scales, the side contents with the reading-progress bar, and + redrawing charts on resize. + Load it after the page's #tip element and before the page's own script. */ + +window.S1X = (function () { + "use strict"; + + const $ = (sel) => document.querySelector(sel); + const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]); + const f2 = (x) => x.toFixed(2); + const mColor = (run) => `var(--m-${run.id})`; + const swatch = (run) => ``; + + /* ---------- tooltip: any element with data-tip ---------- */ + const tip = $("#tip"); + const tipAttr = (html) => `data-tip="${esc(html)}" tabindex="0"`; + function placeTip(x, y) { + const pad = 14; + const w = tip.offsetWidth; + const h = tip.offsetHeight; + let left = x + pad; + let top = y + pad; + if (left + w > innerWidth - 8) left = Math.max(8, x - w - pad); + if (top + h > innerHeight - 8) top = Math.max(8, y - h - pad); + tip.style.left = `${left}px`; + tip.style.top = `${top}px`; + } + function showTip(el, x, y) { + tip.innerHTML = el.dataset.tip; + tip.hidden = false; + if (x == null) { + const r = el.getBoundingClientRect(); + x = r.left + r.width / 2; + y = r.bottom; + } + placeTip(x, y); + } + document.addEventListener("pointerover", (e) => { + const el = e.target.closest("[data-tip]"); + if (el) showTip(el, e.clientX, e.clientY); + }); + document.addEventListener("pointermove", (e) => { + if (!tip.hidden && e.target.closest("[data-tip]")) placeTip(e.clientX, e.clientY); + }); + document.addEventListener("pointerout", (e) => { + if (e.target.closest("[data-tip]") && !e.relatedTarget?.closest?.("[data-tip]")) tip.hidden = true; + }); + document.addEventListener("focusin", (e) => { + const el = e.target.closest("[data-tip]"); + if (el) showTip(el); + }); + document.addEventListener("focusout", () => (tip.hidden = true)); + addEventListener("scroll", () => (tip.hidden = true), { passive: true }); + + /* ---------- small SVG helpers ---------- */ + const lin = (d0, d1, r0, r1) => (v) => r0 + ((v - d0) / (d1 - d0)) * (r1 - r0); + const widthOf = (el) => Math.max(280, el.clientWidth); + + /* One card per run, each metric beside its difference from `ref`. A metric is + { k, v(run), f(value), better: 1 | -1 | 0 } with optional key, frac + (a fraction, shown to two places) and ratio (shown as a multiple of ref). */ + function scoreCards(runs, ref, metrics) { + return runs + .map((r) => { + const rows = metrics + .map((m) => { + const v = m.v(r); + let d = ""; + let worse = false; + if (r !== ref) { + const rv = m.v(ref); + if (m.ratio) { + d = `×${(v / rv).toFixed(1)}`; + worse = v > rv; + } else { + const diff = v - rv; + const near = Math.abs(diff) < (m.frac ? 0.005 : 0.5); + d = near ? "=" : `${diff > 0 ? "+" : "−"}${m.frac ? f2(Math.abs(diff)) : Math.abs(diff)}`; + worse = !near && m.better !== 0 && Math.sign(diff) === -m.better; + } + } + return `
${m.k}${m.f(v)}${d}
`; + }) + .join(""); + return `

${esc(r.name)}${r === ref ? 'reference' : ""}

${rows}
`; + }) + .join(""); + } + + /* One lane per run with a dot per row, ticks at the run's latency.p50 and + latency.p95, and the speed check as a dashed line. `time(row)` is a row's + milliseconds, `tip(run, row, i)` its tooltip, `per` what was timed. */ + function laneChart(el, runs, { time, tip, per, checkMs }) { + const W = widthOf(el); + const narrow = W < 560; + const rowH = 84; + const m = { l: narrow ? 70 : 130, r: 16, t: 26, b: 36 }; + const H = m.t + rowH * runs.length + m.b; + const maxMs = Math.ceil(Math.max(...runs.map((r) => r.latency.max), checkMs) / 100) * 100; + const x = lin(0, maxMs, m.l, W - m.r); + let g = ""; + for (let t = 0; t <= maxMs; t += 100) { + g += `${t}`; + } + g += `ms per ${per} →`; + g += `check 2: p95 ≤ ${checkMs} ms`; + let body = ""; + runs.forEach((run, k) => { + const cy = m.t + rowH * k + rowH / 2; + body += `${esc(narrow ? run.short : run.name)}`; + body += ``; + run.rows.forEach((row, i) => { + const jitter = ((i * 7) % 5) - 2; + body += ``; + }); + // p50 labelled above its tick, p95 below, so close ticks don't collide. + for (const [k2, v, ty] of [ + ["p50", run.latency.p50, cy - 24], + ["p95", run.latency.p95, cy + 32], + ]) { + body += `${k2}`; + } + }); + el.innerHTML = `${g}${body}`; + } + + function tocAndProgress() { + const links = [...document.querySelectorAll("#toc a")]; + const secs = links.map((a) => document.querySelector(a.getAttribute("href"))); + const bar = $("#progress"); + const onScroll = () => { + const h = document.documentElement; + bar.style.width = `${(h.scrollTop / Math.max(1, h.scrollHeight - h.clientHeight)) * 100}%`; + let active = -1; + secs.forEach((s, i) => { + if (s.getBoundingClientRect().top < innerHeight * 0.35) active = i; + }); + links.forEach((a, i) => a.classList.toggle("active", i === active)); + }; + addEventListener("scroll", onScroll, { passive: true }); + onScroll(); + } + + /* Charts are sized to their container, so they are redrawn when the width changes. */ + function redrawOnResize(draw) { + let timer; + let lastW = innerWidth; + addEventListener("resize", () => { + if (innerWidth === lastW) return; + lastW = innerWidth; + clearTimeout(timer); + timer = setTimeout(draw, 150); + }); + } + + return { $, esc, f2, mColor, swatch, tipAttr, lin, widthOf, scoreCards, laneChart, tocAndProgress, redrawOnResize }; +})(); diff --git a/docs/godot-jev/app.js b/docs/godot-jev/app.js new file mode 100644 index 0000000..5463b3c --- /dev/null +++ b/docs/godot-jev/app.js @@ -0,0 +1,808 @@ +/* godot-jev results page. Draws window.GJ_DATA (data.js), which + docs/tools/build-godot-jev-data.gd generates by replaying the recorded run + through the game's own parser. Nothing here re-decides a turn: the outcome + at any pair of thresholds comes from rows[].actMax and rows[].clarifyMax, + the highest slider settings at which the parser still acted or still asked. + Tooltips and chart helpers are in ../assets/results.js. */ + +(function () { + "use strict"; + + const D = window.GJ_DATA; + const runs = D.runs; + const lines = D.lines; + const L = lines.length; + const R = D.rounds; + const N = L * R; + const ACT = D.defaultAct; + const CLARIFY = D.defaultClarify; + const LAST = D.slider.length - 1; + const byId = Object.fromEntries(runs.map((r) => [r.id, r])); + const ref = runs[0]; // TypeSafe Jev: the reference for deltas + const reduceMotion = matchMedia("(prefers-reduced-motion: reduce)").matches; + + const { $, esc, f2, mColor, swatch, tipAttr, lin, widthOf, scoreCards, laneChart, tocAndProgress, redrawOnResize } = window.S1X; + const ms = (x) => `${Math.round(x).toLocaleString("en-US")} ms`; + const mean = (xs) => xs.reduce((a, b) => a + b, 0) / xs.length; + + const rowAt = (run, line, round) => run.rows[line * R + round]; + const rowsOf = (run, line) => run.rows.slice(line * R, line * R + R); + const meant = (line) => lines[line].means || D.none; + const textOf = (line, id) => (id === D.none ? "none of these" : (lines[line].options.find((o) => o.id === id)?.text ?? id)); + + /* The parser's outcome for a recorded answer with the sliders at a (act) and c (clarify). */ + const KINDS = ["unknown", "clarify", "act"]; // also the order of the shade ramp, --r-0 to --r-2 + const kindAt = (row, a, c) => (a <= row.actMax ? "act" : c <= row.clarifyMax ? "clarify" : "unknown"); + const kColor = (kind) => `var(--r-${KINDS.indexOf(kind)})`; + const kInk = (kind) => `var(--r-ink-${KINDS.indexOf(kind)})`; + + /* How the turn went for the player: ok, ask, dead (a dead end) or wrong. */ + function turnAt(row, a, c) { + const kind = kindAt(row, a, c); + if (row.lands[kind]) return kind === "clarify" ? "ask" : "ok"; + return kind === "unknown" ? "dead" : "wrong"; + } + const GLYPH = { ok: "✓", ask: "?", dead: "▼", wrong: "✗" }; + const O_CLASS = { ok: "o-agree", ask: "o-high", dead: "o-low", wrong: "o-low" }; + function turnWord(row, a, c) { + const kind = kindAt(row, a, c); + const turn = turnAt(row, a, c); + if (turn === "ok") return kind === "act" ? "acted on the move the line means" : "unknown, as it should be"; + if (turn === "ask") return "asked, offering the move the line means"; + if (turn === "dead") return "unknown: a dead end"; + return kind === "act" ? "acted on a move the line doesn't mean" : "asked without offering the move"; + } + const countsAt = (run, a, c) => { + const n = { ok: 0, ask: 0, dead: 0, wrong: 0 }; + for (const row of run.rows) n[turnAt(row, a, c)] += 1; + return n; + }; + const mark = (row, a, c) => { + const t = turnAt(row, a, c); + return `${GLYPH[t]}`; + }; + + function lineTip(line, run) { + const rows = rowsOf(run, line); + const first = rows[0]; + return ( + `Line ${line + 1} · ${esc(run.name)}
"${esc(lines[line].say)}"` + + `means: ${esc(lines[line].meansText || "nothing the author wrote")}` + + `${first.ranked.map(([id, p]) => `${esc(textOf(line, id))} ${f2(p)}`).join(" · ")}` + + `rounds: ${rows.map((r) => `${kindAt(r, ACT, CLARIFY)} ${f2(r.confidence)}`).join(" · ")}` + ); + } + + /* ---------- hero ---------- */ + function hero() { + const at = runs.map((r) => countsAt(r, ACT, CLARIFY)); + const wrong = at.reduce((a, c) => a + c.wrong + c.dead, 0); + const allTopRight = runs.every((r) => r.rows.every((row) => row.top === meant(row.line))); + const asks = at.reduce((a, c) => a + c.ask, 0); + const askers = runs.filter((r, i) => at[i].ask > 0); + const askLines = new Set(askers.flatMap((r) => r.rows.filter((row) => turnAt(row, ACT, CLARIFY) === "ask").map((row) => row.line))); + $("#hero-stats").innerHTML = ` +
${L} lines × ${R}
lines of slang that take the dungeon from the stairs to the crown, each asked ${R} times of ${runs.length} decision models
+
${wrong}
wrong moves or dead ends in ${N * runs.length} turns.${allTopRight ? " Every model ranked the right option first, every time." : ""}
+
${asks}
turns where the game had to ask "did you mean?": all ${askers.map((r) => esc(r.short)).join(" and ")}, on ${askLines.size} of the ${L} lines
+
${runs + .map((r) => `${swatch(r)}${r.latency.p50}`) + .join("")}ms
median time per turn in this run: TypeSafe hosted, then Flash 9B and 27B on one laptop. Not the models' speed.
`; + $("#legend-models").innerHTML = runs + .map((r) => `${swatch(r)}${esc(r.name)} · ${r.where === "Local" ? `local, ${esc(r.size)}` : "hosted"}`) + .join(""); + } + + /* ---------- checks ---------- */ + function checks() { + const speedPass = runs.map((r) => r.latency.p95 <= D.speedCheckMs); + const speedStatus = runs.filter((r, i) => !speedPass[i]).map((r) => `${r.short} misses by ${r.latency.p95 - D.speedCheckMs} ms`); + const rowsHtml = [ + [ + "1 · Understanding", + "On ≥ 100 player inputs, ≥ 90% land on the intended move or a clarify that offers it, and ≥ 90% of impossible ones come back unknown", + runs.map((r) => `${r.summary.landed} of ${N} landed`), + `Not yet evidence
${L} lines in one voice, one of them impossible`, + ], + [ + "2 · Speed", + `p95 ≤ ${D.speedCheckMs} ms per turn`, + runs.map((r, i) => `${ms(r.latency.p95)}`), + `${speedStatus.length ? `${runs.length - speedStatus.length} of ${runs.length} pass. ${speedStatus.join("; ")}.` : "All pass."}
on this setup: see the note under Speed`, + ], + [ + "3 · Nothing invented", + "The options offered are exactly the possible moves, plus none", + runs.map(() => `by construction`), + `Pass
a unit test over the demo's states`, + ], + [ + "4 · Shippable", + "An exported build works with no key in the client, through a small proxy", + runs.map(() => `—`), + `Not started: needs the proxy`, + ], + ]; + $("#checks-table tbody").innerHTML = rowsHtml + .map(([c, done, cells, status]) => `${c}${done}${cells.map((x) => `${x}`).join("")}${status}`) + .join(""); + } + + /* ---------- model cards ---------- */ + function modelCards() { + $("#model-cards").innerHTML = runs + .map( + (r) => ` +
+ ${esc(r.where)} +

${esc(r.name)}

+
+
Served model
${esc(r.servedModel)}
+
Size
${esc(r.size)}
+ ${r.temperature ? `
Calibration
T=${esc(r.temperature)}
` : ""} +
Median
${ms(r.latency.p50)} per turn${r.where === "Local" ? ", on this laptop" : ", network included"}
+
Rounds
${r.sameEveryRound ? "the same answer to a line every round" : "confidence moved a little between rounds"}
+
+

${esc(r.note)}

+
`, + ) + .join(""); + } + + /* ---------- §4 the question ---------- */ + function question() { + const steps = $("#q-steps"); + steps.innerHTML = lines.map((_, i) => ``).join(""); + let at = 8; + function render() { + const line = lines[at]; + steps.querySelectorAll(".btn").forEach((b) => b.setAttribute("aria-pressed", String(+b.dataset.i === at))); + $("#q-say").innerHTML = `${at + 1}. ${esc(line.say)}· in the ${esc(line.room)}`; + // Godot writes the request with its keys sorted, so this is the order on the wire. + const state = { ...line.state, player_typed: line.say }; + const sorted = Object.fromEntries(Object.keys(state).sort().map((k) => [k, state[k]])); + $("#q-state").textContent = JSON.stringify(sorted, null, 2); + $("#q-text").textContent = D.question; + $("#q-options").innerHTML = + line.options + .map( + (o) => + `
  • ${esc(o.id)}${esc(o.text)}${o.id === line.means ? 'what this line means' : ""}
  • `, + ) + .join("") + + `
  • ${esc(D.none)}${esc(D.noneText)}${line.means ? "" : 'what this line means: nothing the author wrote'}
  • `; + } + steps.addEventListener("click", (e) => { + const b = e.target.closest(".btn"); + if (!b) return; + at = +b.dataset.i; + render(); + }); + render(); + const offered = lines.map((l) => l.options.length + 1); + $("#q-cap").innerHTML = + `Fig. 3The request for each of the ${L} lines, as the game built it. The list runs from ${Math.min(...offered)} to ${Math.max(...offered)} options along the route, ` + + `counting "none of these". The model's answer can only be one of them.`; + } + + /* ---------- an answer as a stacked bar: the top three, shaded by what they are ---------- */ + function role(line, id) { + if (id === meant(line)) return 2; + return id === D.none ? 0 : 1; + } + function segments(row, { big = false } = {}) { + const rest = Math.max(0, 1 - row.ranked.reduce((a, [, p]) => a + p, 0)); + const segs = row.ranked + .map(([id, p]) => { + if (p < 0.005) return ""; + const l = role(row.line, id); + const label = big && p >= 0.14 ? `${p >= 0.3 ? esc(textOf(row.line, id)) + " " : ""}${f2(p)}` : ""; + return `
    ${label}
    `; + }) + .join(""); + return segs + (rest >= 0.005 ? `
    ` : ""); + } + const stack = (row) => `
    ${segments(row)}
    `; + + /* ---------- §5 explorer ---------- */ + function explorer() { + const PRESETS = [ + { m: "flash", l: 5, act: ACT, clarify: CLARIFY, txt: "Line 6 · Flash 9B: asks" }, + { m: "flash", l: 6, act: ACT, clarify: CLARIFY, txt: "Line 7 · Flash 9B: 0.01 short" }, + { m: "27b", l: 8, act: ACT, clarify: CLARIFY, txt: "Line 9 · 27B: sure" }, + { m: "flash", l: 0, act: ACT, clarify: CLARIFY, txt: "Line 1 · Flash 9B: the gin line" }, + { m: "typesafe", l: 7, act: 90, clarify: CLARIFY, txt: "Line 8 · TypeSafe: a stricter game" }, + ]; + const selM = $("#ex-model"); + const selL = $("#ex-line"); + const selR = $("#ex-round"); + const actR = $("#ex-act"); + const clarR = $("#ex-clarify"); + selM.innerHTML = runs.map((r) => ``).join(""); + selL.innerHTML = lines.map((l, i) => ``).join(""); + selR.innerHTML = Array.from({ length: R }, (_, i) => ``).join(""); + $("#presets").innerHTML = PRESETS.map((p, i) => ``).join(""); + + const state = { m: "flash", l: 5, round: 0, act: ACT, clarify: CLARIFY }; + function sync() { + selM.value = state.m; + selL.value = String(state.l); + selR.value = String(state.round); + actR.value = String(state.act); + clarR.value = String(state.clarify); + document.querySelectorAll("#presets .btn").forEach((b) => { + const p = PRESETS[+b.dataset.i]; + b.setAttribute("aria-pressed", String(p.m === state.m && p.l === state.l && p.act === state.act && p.clarify === state.clarify && state.round === 0)); + }); + render(); + } + function render() { + const run = byId[state.m]; + const row = rowAt(run, state.l, state.round); + const line = lines[state.l]; + const act = D.slider[state.act]; + const clarify = D.slider[state.clarify]; + const kind = kindAt(row, state.act, state.clarify); + const none = row.top === D.none; + $("#ex-act-out").textContent = f2(act); + $("#ex-clarify-out").textContent = f2(clarify); + $("#ex-tasktext").innerHTML = `${state.l + 1}. ${esc(line.say)}· means: ${esc(line.meansText || "nothing the author wrote")}`; + $("#ex-stack").innerHTML = segments(row, { big: true }); + $("#ex-top3").innerHTML = row.ranked + .map(([id, p]) => `
  • ${esc(textOf(state.l, id))}${f2(p)}
  • `) + .join(""); + $("#ex-likeliest").innerHTML = + `Chose ${esc(textOf(state.l, row.top))} with a confidence of ${f2(row.confidence)}. ` + + `${esc(run.name)} ${row.confidence >= D.sureAt ? "was sure." : row.confidence < act ? "was not sure enough to act on it." : "leaned clearly one way."}`; + + const rungs = [ + { + kind: "act", + threshold: act, + pass: state.act <= row.actMax, + why: `Act if the confidence, ${f2(row.confidence)}, is at least ${f2(act)}.`, + }, + { + kind: "clarify", + threshold: clarify, + pass: state.clarify <= row.clarifyMax, + why: `Otherwise ask if it is at least ${f2(clarify)}.`, + }, + ]; + $("#ex-ladder").innerHTML = + rungs + .map((g) => { + const res = none ? `chose "none" ✗` : `${f2(row.confidence)} ${g.pass ? "✓ pass" : "✗"}`; + return ` +
    ${g.kind === "act" ? "Act" : "Ask"}?
    ${g.why}${none ? '
    The choice is "none of these", which is unknown at any confidence.' : ""}`)}> + ${g.kind} + + ${res} +
    `; + }) + .join("") + + `
    Unknown
    What is left when neither threshold is met, or when the choice is "none of these".`)}> + unknown + + ${kind === "unknown" ? "what's left" : "not needed"} +
    `; + + const turn = turnAt(row, state.act, state.clarify); + const verdictMark = `${GLYPH[turn]} ${turnWord(row, state.act, state.clarify)}`; + let head; + let note; + if (kind === "act") { + head = `→ Acts: ${esc(textOf(state.l, row.top))}`; + note = "The game applies the move and prints the response the author wrote for it."; + } else if (kind === "clarify") { + head = `→ Asks: Did you mean: ${row.asks.map((id, i) => `${i + 1}. ${esc(textOf(state.l, id))}`).join("  ")}`; + note = + state.act - 1 <= row.actMax + ? `The right move is option 1, and the confidence is ${f2(act - row.confidence)} under the act threshold: one step lower and the game acts.` + : "The player answers with one keypress. Nothing in the game has changed yet."; + } else if (none) { + head = `→ Unknown: the game prints an authored line such as "Nothing here answers to that."`; + note = `The model chose "none of these" at ${f2(row.ranked[0][1])}. That is unknown whatever the thresholds are.`; + } else { + head = `→ Unknown: the game prints an authored line such as "Nothing here answers to that."`; + note = `The model's top choice was right, at ${f2(row.confidence)}, but the ask threshold is above it, so the player gets no question to answer.`; + } + $("#ex-verdict").innerHTML = `${head} ${verdictMark}${note}`; + } + $("#presets").addEventListener("click", (e) => { + const b = e.target.closest(".btn"); + if (!b) return; + const p = PRESETS[+b.dataset.i]; + Object.assign(state, { m: p.m, l: p.l, round: 0, act: p.act, clarify: p.clarify }); + sync(); + }); + selM.addEventListener("change", () => ((state.m = selM.value), sync())); + selL.addEventListener("change", () => ((state.l = +selL.value), sync())); + selR.addEventListener("change", () => ((state.round = +selR.value), sync())); + actR.addEventListener("input", () => { + state.act = +actR.value; + state.clarify = Math.min(state.clarify, state.act); + sync(); + }); + clarR.addEventListener("input", () => { + state.clarify = +clarR.value; + state.act = Math.max(state.act, state.clarify); + sync(); + }); + sync(); + } + + /* ---------- §6 scoreboard ---------- */ + function scoreboard() { + const at = (r) => countsAt(r, ACT, CLARIFY); + const metrics = [ + { k: "Lines landed", v: (r) => r.summary.landed, f: (v) => `${v} of ${N}`, better: 1, key: true }, + { k: "Wrong moves and dead ends", v: (r) => at(r).wrong + at(r).dead, f: String, better: -1, key: true }, + { k: "Acted at once", v: (r) => r.summary.act, f: String, better: 1 }, + { k: 'Asked "did you mean?"', v: (r) => r.summary.clarify, f: String, better: -1 }, + { k: "Unknown (the gin line)", v: (r) => r.summary.unknown, f: String, better: 0 }, + { k: `Sure (confidence ≥ ${D.sureAt})`, v: (r) => r.sure, f: (v) => `${v} of ${N}`, better: 1 }, + { k: "Mean confidence", v: (r) => r.meanConfidence, f: f2, better: 1, frac: true }, + { k: "Lowest confidence in a move", v: (r) => r.lowestOnAMove, f: f2, better: 1, frac: true }, + { k: "Median per turn, this setup", v: (r) => r.latency.p50, f: ms, better: -1, ratio: true }, + { k: "p95 per turn, this setup", v: (r) => r.latency.p95, f: ms, better: -1, ratio: true }, + ]; + $("#scoreboard").innerHTML = scoreCards(runs, ref, metrics); + + const moves = lines.filter((l) => l.means).length; + const askers = runs.filter((r) => r.summary.clarify > 0); + const askText = askers + .map((r) => { + const asked = [...new Set(r.rows.filter((row) => kindAt(row, ACT, CLARIFY) === "clarify").map((row) => row.line))]; + return `${r.name} asked on ${asked.length} of the ${moves} lines that mean a move (${asked + .map((l) => `line ${l + 1} at ${f2(rowAt(r, l, 0).confidence)}`) + .join(", ")}), in each of the ${R} rounds, and offered the right move first both times.`; + }) + .join(" "); + const local = runs.filter((r) => r.where === "Local").sort((a, b) => a.latency.p50 - b.latency.p50); + $("#findings").innerHTML = [ + `No wrong move in ${N * runs.length} turns. Every model ranked the move the line means first on every turn, and chose "none of these" for the gin line every time.`, + `They differ in certainty. Turns with a confidence of ${D.sureAt} or more: ${runs.map((r) => `${r.short} ${r.sure}`).join(", ")}, out of ${N}.`, + `Less certainty shows up as questions. ${askText} The other two acted on every one.`, + `On this setup the hosted model and ${local[0].short} took about the same time, a median of ${ms(ref.latency.p50)} and ${ms(local[0].latency.p50)}, and the ${local[1].short} took about ${(local[1].latency.p50 / local[0].latency.p50).toFixed(1)} times as long as ${local[0].short}. These are timings from one laptop with other things running, and not a measure of how fast the models are.`, + ] + .map((t) => `
  • ${t}
  • `) + .join(""); + } + + /* ---------- §7a confidence along the route ---------- */ + let sureKey = "route"; + function sureChart() { + const el = $("#sure-chart"); + const W = widthOf(el); + const narrow = W < 560; + const H = narrow ? 300 : 340; + const m = { l: 40, r: narrow ? 16 : 150, t: 30, b: 40 }; + const x = lin(1, L, m.l + 8, W - m.r - 8); + const y = lin(0.3, 1, H - m.b, m.t); + let g = ""; + for (const t of [0.3, 0.5, 0.7, 0.9, 1]) { + g += `${t.toFixed(2)}`; + } + for (const [v, word] of [ + [D.thresholds.act, "act"], + [D.thresholds.clarify, "ask"], + ]) { + g += ``; + g += `${word} at ≥ ${f2(v)}`; + } + if (sureKey === "route") { + for (let i = 1; i <= L; i++) g += `${i}`; + g += `line, in the order the route types them →`; + } else { + g += `lines, each model's sorted from most to least sure →`; + } + let dots = ""; + let paths = ""; + let labels = ""; + const ends = []; + runs.forEach((run) => { + let pts = lines.map((_, i) => ({ line: i, v: mean(rowsOf(run, i).map((r) => r.confidence)) })); + if (sureKey === "sorted") pts = pts.sort((a, b) => b.v - a.v || a.line - b.line); + const d = pts.map((p, i) => `${i ? "L" : "M"}${x(i + 1).toFixed(1)},${y(p.v).toFixed(1)}`).join(""); + paths += ``; + pts.forEach((p, i) => { + dots += ``; + }); + ends.push({ run, y: y(pts[pts.length - 1].v), text: `${run.short}: mean ${f2(run.meanConfidence)}` }); + }); + if (!narrow) { + ends.sort((a, b) => a.y - b.y); + for (let i = 1; i < ends.length; i++) ends[i].y = Math.max(ends[i].y, ends[i - 1].y + 16); + ends.forEach((e) => { + labels += `${esc(e.text)}`; + }); + } + el.innerHTML = `${g}${paths}${dots}${labels}${ + narrow ? `
    ${ends.map((e) => `${swatch(e.run)}${esc(e.text)}`).join("")}
    ` : "" + }`; + const flash = byId.flash; + $("#sure-cap").innerHTML = + sureKey === "route" + ? `Fig. 5The confidence each model reported for its choice on each line, averaged over the ${R} rounds. Line 1 is the gin line, where the choice is "none of these". Flash 9B dips under the act threshold on lines 6 and 7 (${f2(rowAt(flash, 5, 0).confidence)} and ${f2(rowAt(flash, 6, 0).confidence)}); the other two stay above it throughout. Hover or tap a dot for the line and the full answer.` + : `Fig. 5The same confidences, each model's sorted. The 27B sits near 1.00 for almost every line, TypeSafe Jev drops to ${f2(ref.lowestOnAMove)} at its least sure, and Flash 9B declines from the start. Hover or tap a dot for the line.`; + } + + /* ---------- §7b line grid ---------- */ + const differSet = new Set(); + const unsureSet = new Set(); + for (let i = 0; i < L; i++) { + const kinds = new Set(runs.flatMap((r) => rowsOf(r, i).map((row) => kindAt(row, ACT, CLARIFY)))); + if (kinds.size > 1) differSet.add(i); + if (runs.some((r) => rowsOf(r, i).some((row) => row.confidence < D.thresholds.act))) unsureSet.add(i); + } + let gridFilter = "all"; + function lineGrid() { + const keep = (i) => gridFilter === "all" || (gridFilter === "differ" ? differSet.has(i) : unsureSet.has(i)); + const head = `
    #The player typesMeans${runs + .map((r) => `${swatch(r)}${esc(r.short)}`) + .join("")}
    `; + const rows = lines + .map((line, i) => { + if (!keep(i)) return ""; + const cells = runs + .map((r) => { + const row = rowAt(r, i, 0); + return `
    ${esc(r.short)}${stack(row)}${mark(row, ACT, CLARIFY)}
    `; + }) + .join(""); + return `
    ${i + 1}${esc(line.say)}${esc(line.room)} · ${line.options.length + 1} options${esc(line.meansText || "nothing the author wrote")}${cells}
    `; + }) + .join(""); + $("#taskgrid").innerHTML = head + rows; + } + + function deltaNotes() { + const mini = (i) => + runs + .map((r) => `
    ${swatch(r)}${esc(r.short)}${stack(rowAt(r, i, 0))}${f2(rowAt(r, i, 0).confidence)}
    `) + .join(""); + const c = (id, i) => f2(rowAt(byId[id], i, 0).confidence); + const p = (id, i) => f2(rowAt(byId[id], i, 0).ranked[0][1]); + const gin = rowAt(byId.flash, 0, 0); + $("#delta-notes").innerHTML = ` +
    + Lines 6 and 7 · Flash 9B asked +

    "${esc(lines[5].say)}" and "${esc(lines[6].say)}"

    +

    Flash 9B ranked the right move first on both, with a confidence of ${c("flash", 5)} and ${c("flash", 6)}, under the act threshold of ${f2(D.thresholds.act)}. The game asked "Did you mean:" with the right move as option 1. TypeSafe Jev (${c("typesafe", 5)}, ${c("typesafe", 6)}) and the 27B (${c("27b", 5)}, ${c("27b", 6)}) acted. Line 7 was ${f2(D.thresholds.act - rowAt(byId.flash, 6, 0).confidence)} short: at an act threshold of ${f2(D.slider[rowAt(byId.flash, 6, 0).actMax])} it acts.

    +
    ${[5, 6].map((i) => `
    Line ${i + 1}
    ${mini(i)}`).join("")}
    +
    +
    + Line 1 · the request the author never wrote +

    "${esc(lines[0].say)}"

    +

    All three chose "none of these", so the game printed an authored refusal. TypeSafe Jev put ${p("typesafe", 0)} on it and the 27B ${p("27b", 0)}. Flash 9B put ${p("flash", 0)}, with ${f2(gin.ranked[1][1])} on "${esc(textOf(0, gin.ranked[1][0]))}" and ${f2(gin.ranked[2][1])} on "${esc(textOf(0, gin.ranked[2][0]))}". The parser treats a choice of "none" as unknown at any confidence, so the thresholds played no part. One line is too few to say how often Flash 9B would pick a move here.

    +
    ${mini(0)}
    +
    `; + } + + /* ---------- §7c pairs ---------- */ + function pairs() { + $("#pairs").innerHTML = D.pairs + .map((p) => { + const a = byId[p.a]; + const b = byId[p.b]; + const track = (same, say) => + a.rows + .map((r, i) => ``) + .join(""); + return `
    +
    ${swatch(a)}${esc(a.short)} vs ${swatch(b)}${esc(b.short)}
    +
    Same top choice${track( + (r, s) => r.top === s.top, + (r) => esc(textOf(r.line, r.top)), + )}${p.sameTop}/${p.turns}
    +
    Same outcome${track( + (r, s) => r.kind === s.kind, + (r) => r.kind, + )}${p.sameKind}/${p.turns}
    +
    `; + }) + .join(""); + const allTop = D.pairs.every((p) => p.sameTop === p.turns); + $("#pairs-cap").innerHTML = + `Fig. 7Out of ${N} turns. "Same top choice" is the option each model ranked first; "same outcome" is whether the game acted, asked or said unknown at its thresholds. ` + + (allTop ? "Every pair ranked the same option first on every turn, so the only differences come from confidence." : ""); + } + + /* ---------- §8 move the thresholds ---------- */ + let actIdx = ACT; + let clarifyIdx = CLARIFY; + function barPanels() { + const OC = { ask: "var(--warning)", dead: "var(--critical)", wrong: "var(--critical)" }; + const CELL = { ok: "", ask: "high", dead: "low", wrong: "low" }; + $("#bar-panels").innerHTML = runs + .map((run) => { + const lanes = Array.from({ length: R }, (_, round) => { + const cells = lines + .map((_, i) => { + const row = rowAt(run, i, round); + const kind = kindAt(row, actIdx, clarifyIdx); + const turn = turnAt(row, actIdx, clarifyIdx); + return `Line ${i + 1}, round ${round + 1} · ${esc(run.name)}
    "${esc(lines[i].say)}"confidence ${f2(row.confidence)} → ${kind}: ${turnWord(row, actIdx, clarifyIdx)}`, + )}>${turn === "ok" ? "" : GLYPH[turn]}
    `; + }) + .join(""); + return `
    round ${round + 1}${cells}
    `; + }).join(""); + const c = countsAt(run, actIdx, clarifyIdx); + return `

    ${esc(run.name)}

    ${lanes} +
    +
    ${c.ok + c.ask}landed
    +
    ${c.ask}asked ?
    +
    ${c.dead}dead ends ▼
    +
    ${c.wrong}wrong ✗
    +
    `; + }) + .join(""); + $("#act-out").textContent = f2(D.slider[actIdx]); + $("#clarify-out").textContent = f2(D.slider[clarifyIdx]); + $("#act-range").value = String(actIdx); + $("#clarify-range").value = String(clarifyIdx); + // Each chart moves one threshold and carries the other along, as the sliders do. + drawCountChart($("#ask-chart"), "ask", actIdx, (i) => [i, Math.min(clarifyIdx, i)]); + drawCountChart($("#dead-chart"), "dead", clarifyIdx, (i) => [Math.max(actIdx, i), i]); + } + function drawCountChart(el, key, cursor, settingAt) { + const W = widthOf(el); + const H = 210; + const m = { l: 34, r: 12, t: 12, b: 34 }; + const series = runs.map((run) => D.slider.map((_, i) => countsAt(run, ...settingAt(i))[key])); + const yMax = Math.ceil(Math.max(4, ...series.flat()) / 4) * 4; + const x = lin(0, 1, m.l, W - m.r); + const y = lin(0, yMax, H - m.b, m.t); + const word = key === "ask" ? "answered with a question" : "dead ends"; + let g = ""; + for (let t = 0; t <= yMax; t += yMax / 4) { + g += `${t}`; + } + for (const b of [0, 0.2, 0.4, 0.6, 0.8, 1]) { + g += `${b.toFixed(1)}`; + } + g += `${key === "ask" ? "act" : "ask"} threshold →`; + const def = D.slider[key === "ask" ? ACT : CLARIFY]; + g += ``; + // Step lines, offset by a pixel or two so overlapping series stay visible. + const off = [-1.5, 0, 1.5]; + const paths = series + .map((s, k) => { + let d = `M${x(D.slider[0])},${y(s[0]) + off[k]}`; + for (let i = 1; i < s.length; i++) d += `H${x(D.slider[i])}V${y(s[i]) + off[k]}`; + return ``; + }) + .join(""); + const cx = x(D.slider[cursor]); + const markers = series.map((s, k) => ``).join(""); + const hits = D.slider + .map((b, i) => { + const x0 = i === 0 ? m.l : (x(D.slider[i - 1]) + x(b)) / 2; + const x1 = i === LAST ? W - m.r : (x(b) + x(D.slider[i + 1])) / 2; + return `${key === "ask" ? "Act" : "Ask"} threshold ${f2(b)} · ${word}
    ${runs.map((r, k) => `${esc(r.short)}: ${series[k][i]} of ${N}`).join("
    ")}`, + )}/>`; + }) + .join(""); + el.innerHTML = `${g}${paths}${markers}${hits}`; + } + function barNotes() { + const moves = (run) => run.rows.filter((r) => r.top !== D.none); + const notes = runs.map((run) => { + const actsUpTo = Math.min(...moves(run).map((r) => r.actMax)); + const asksUpTo = Math.min(...moves(run).map((r) => r.clarifyMax)); + return `${esc(run.name)}: acts at once on all ${moves(run).length} turns that mean a move while the act threshold is ${f2(D.slider[actsUpTo])} or lower, and starts to ask above it. No dead end until the ask threshold passes ${f2(D.slider[asksUpTo])}.`; + }); + const canGoWrong = runs.some((run) => run.rows.some((r) => (r.actMax >= 0 && !r.lands.act) || (r.clarifyMax >= 0 && !r.lands.clarify))); + if (!canGoWrong) { + notes.push( + `No setting produces a wrong move in this run, because every model ranked the right option first. Here the thresholds only trade acting against asking, and asking against a dead end. They protect against a confident wrong choice, and these lines never produced one.`, + ); + } + notes.push(`The gin line is unknown at every setting. All three chose "none of these", and the parser doesn't weigh that choice against a threshold.`); + $("#bar-notes").innerHTML = notes.map((t) => `
  • ${t}
  • `).join(""); + } + function barControls() { + const actR = $("#act-range"); + const clarR = $("#clarify-range"); + const play = $("#bar-play"); + let timer = null; + const stop = () => { + clearInterval(timer); + timer = null; + play.textContent = "▶ Sweep the act threshold"; + play.setAttribute("aria-pressed", "false"); + }; + const setAct = (i) => { + actIdx = i; + clarifyIdx = Math.min(clarifyIdx, i); + }; + const setClarify = (i) => { + clarifyIdx = i; + actIdx = Math.max(actIdx, i); + }; + actR.addEventListener("input", () => { + stop(); + setAct(+actR.value); + barPanels(); + }); + clarR.addEventListener("input", () => { + stop(); + setClarify(+clarR.value); + barPanels(); + }); + $("#bar-reset").addEventListener("click", () => { + stop(); + actIdx = ACT; + clarifyIdx = CLARIFY; + barPanels(); + }); + play.addEventListener("click", () => { + if (timer) return stop(); + if (actIdx >= LAST) actIdx = clarifyIdx; + play.textContent = "❚❚ Pause"; + play.setAttribute("aria-pressed", "true"); + timer = setInterval( + () => { + actIdx += 1; + barPanels(); + if (actIdx >= LAST) stop(); + }, + reduceMotion ? 250 : 110, + ); + }); + for (const [id, set] of [ + ["#ask-chart", setAct], + ["#dead-chart", setClarify], + ]) { + $(id).addEventListener("click", (e) => { + const hit = e.target.closest(".hit"); + if (!hit) return; + stop(); + set(+hit.dataset.idx); + barPanels(); + }); + } + } + + /* ---------- §9 latency ---------- */ + function latChart() { + laneChart($("#lat-chart"), runs, { + time: (row) => row.ms, + tip: (run, row) => + `Line ${row.line + 1}, round ${row.round + 1} · ${esc(run.name)}
    ${ms(row.ms)}"${esc(lines[row.line].say)}" · ${row.offered} options`, + per: "turn", + checkMs: D.speedCheckMs, + }); + $("#lat-cap").innerHTML = + `Fig. 9Each dot is one turn: ${N} per model. Ticks mark p50 and p95. The three models were asked each line one after another, so they shared whatever else the laptop was doing at that moment. ` + + `Times are taken in the game loop, which ticks about every 7 ms in a headless run, so dots line up in steps of that size.`; + } + function routeChart() { + const el = $("#route-chart"); + const W = widthOf(el); + const narrow = W < 560; + const H = 320; + const m = { l: 44, r: narrow ? 12 : 120, t: 16, b: 54 }; + const maxMs = Math.ceil(Math.max(...runs.map((r) => r.latency.max)) / 100) * 100; + const x = lin(1, L, m.l + 10, W - m.r - 10); + const y = lin(0, maxMs, H - m.b, m.t); + let g = ""; + for (let t = 0; t <= maxMs; t += 100) { + g += `${t}`; + } + lines.forEach((line, i) => { + g += `${i + 1}`; + g += `${line.options.length + 1}`; + }); + g += `lineoptions`; + g += `ms per turn`; + let body = ""; + const ends = []; + runs.forEach((run) => { + const means = lines.map((_, i) => mean(rowsOf(run, i).map((r) => r.ms))); + body += ``; + run.rows.forEach((row) => { + body += `Line ${row.line + 1}, round ${row.round + 1} · ${esc(run.name)}
    ${ms(row.ms)}${row.offered} options · in the ${esc(lines[row.line].room)}`, + )}/>`; + }); + ends.push({ run, y: y(means[L - 1]) + 4 }); + }); + if (!narrow) { + ends.sort((a, b) => a.y - b.y); + for (let i = 1; i < ends.length; i++) ends[i].y = Math.max(ends[i].y, ends[i - 1].y + 16); + ends.forEach((e) => (body += `${esc(e.run.short)}`)); + } + el.innerHTML = `${g}${body}${ + narrow ? `
    ${runs.map((r) => `${swatch(r)}${esc(r.short)}`).join("")}
    ` : "" + }`; + const span = (run) => { + const first = mean(rowsOf(run, 0).map((r) => r.ms)); + const last = mean(rowsOf(run, L - 1).map((r) => r.ms)); + return `from about ${Math.round(first)} ms on line 1 to about ${Math.round(last)} ms on line ${L}`; + }; + $("#route-cap").innerHTML = + `Fig. 10The same turns in route order, with the number of options each question offered. The local models take longer as the request grows, with more options and more items in the state: ` + + `the 27B ${span(byId["27b"])}, Flash 9B ${span(byId.flash)}. The hosted model shows no such trend at this size.`; + } + function latTable() { + const check = (p95) => { + const pass = p95 <= D.speedCheckMs; + return `${pass ? "pass" : `misses by ${p95 - D.speedCheckMs} ms`}`; + }; + const day = (when) => when.slice(0, 10); + const now = runs.map((r) => { + const T = r.latency; + return `${swatch(r)}${esc(r.name)}${day(D.when)}${[T.min, T.p50, T.mean, T.p95, T.max].map((v) => `${ms(v)}`).join("")}${check(T.p95)}`; + }); + const before = runs + .filter((r) => D.earlier[r.id]) + .map((r) => { + const s = D.earlier[r.id]; + return `${swatch(r)}${esc(r.name)}${day(D.earlier.when)}—${ms(s.p50_ms)}—${ms(s.p95_ms)}${ms(s.max_ms)}${check(s.p95_ms)}`; + }); + $("#lat-table tbody").innerHTML = [...now, ...before].join(""); + const e = D.earlier; + $("#lat-table-cap").innerHTML = + `Table 2This run, and the same ${L} lines and ${e.rounds} rounds on ${day(e.when)} with the same pinned TypeSafe model and the same 27B build. ` + + `The hosted p95 went from ${ms(e.typesafe.p95_ms)} to ${ms(ref.latency.p95)}: that run had two slow round trips (the slowest ${ms(e.typesafe.max_ms)}), this one had none. ` + + `The 27B's median went from ${ms(e["27b"].p50_ms)} to ${ms(byId["27b"].latency.p50)}. The model versions were the same on both days and the conditions were not, which is why these read as measurements of a setup.`; + } + + /* ---------- misc ---------- */ + function sources() { + $("#sources").innerHTML = `${esc(D.generatedFrom.split("/").pop())}`; + } + function routeKey() { + $("#route-key").innerHTML = + `what the line meansanother move` + + `none of thesethe other options`; + } + function toggles() { + $("#sure-toggle").addEventListener("click", (e) => { + const b = e.target.closest(".btn"); + if (!b) return; + sureKey = b.dataset.k; + document.querySelectorAll("#sure-toggle .btn").forEach((x) => x.setAttribute("aria-pressed", String(x === b))); + sureChart(); + }); + $("#grid-filter").addEventListener("click", (e) => { + const b = e.target.closest(".btn"); + if (!b) return; + gridFilter = b.dataset.f; + document.querySelectorAll("#grid-filter .btn").forEach((x) => x.setAttribute("aria-pressed", String(x === b))); + lineGrid(); + }); + } + + /* Charts sized to their container; redrawn when the width changes. */ + function drawCharts() { + sureChart(); + barPanels(); + latChart(); + routeChart(); + } + + hero(); + checks(); + modelCards(); + question(); + explorer(); + scoreboard(); + routeKey(); + lineGrid(); + deltaNotes(); + pairs(); + sources(); + latTable(); + barNotes(); + toggles(); + barControls(); + drawCharts(); + tocAndProgress(); + redrawOnResize(drawCharts); +})(); diff --git a/docs/godot-jev/data.js b/docs/godot-jev/data.js new file mode 100644 index 0000000..483e819 --- /dev/null +++ b/docs/godot-jev/data.js @@ -0,0 +1,2 @@ +/* Generated by docs/tools/build-godot-jev-data.gd from godot-jev's eval run. Don't edit. */ +window.GJ_DATA = {"defaultAct":75,"defaultClarify":40,"earlier":{"27b":{"act":45,"clarify":0,"error":0,"landed":48,"max_ms":716,"p50_ms":600,"p95_ms":683,"turns":48,"unknown":3},"rounds":3,"typesafe":{"act":45,"clarify":0,"error":0,"landed":48,"max_ms":1144,"p50_ms":213,"p95_ms":502,"turns":48,"unknown":3},"when":"2026-09-30T17:13:13"},"generatedFrom":"godot-jev/eval/results-2026-10-08T134205.json","lines":[{"means":"","meansText":"","options":[{"id":"look__around","text":"look around the room"},{"id":"inventory__self","text":"check what I'm carrying"},{"id":"go__north","text":"go north"},{"id":"take__torch","text":"take the torch"},{"id":"examine__torch","text":"examine the torch"}],"room":"Crypt stairs","say":"ayo where the gin and juice at, nephew?","state":{"carrying":[],"dark":false,"location":"Crypt stairs","visible":["torch"]}},{"means":"take__torch","meansText":"take the torch","options":[{"id":"look__around","text":"look around the room"},{"id":"inventory__self","text":"check what I'm carrying"},{"id":"go__north","text":"go north"},{"id":"take__torch","text":"take the torch"},{"id":"examine__torch","text":"examine the torch"}],"room":"Crypt stairs","say":"fo shizzle, scoop up that torch-izzle off the flo","state":{"carrying":[],"dark":false,"location":"Crypt stairs","visible":["torch"]}},{"means":"light__torch","meansText":"light the torch in the brazier","options":[{"id":"look__around","text":"look around the room"},{"id":"inventory__self","text":"check what I'm carrying"},{"id":"go__north","text":"go north"},{"id":"examine__torch","text":"examine the torch"},{"id":"drop__torch","text":"drop the torch"},{"id":"light__torch","text":"light the torch in the brazier"}],"room":"Crypt stairs","say":"light that thang up in the brazier, ya dig","state":{"carrying":["torch"],"dark":false,"location":"Crypt stairs","visible":[]}},{"means":"go__north","meansText":"go north","options":[{"id":"look__around","text":"look around the room"},{"id":"inventory__self","text":"check what I'm carrying"},{"id":"go__north","text":"go north"},{"id":"examine__torch","text":"examine the torch"},{"id":"drop__torch","text":"drop the torch"}],"room":"Crypt stairs","say":"roll on up north, cuz","state":{"carrying":["torch"],"dark":false,"location":"Crypt stairs","visible":[]}},{"means":"go__east","meansText":"go east","options":[{"id":"look__around","text":"look around the room"},{"id":"inventory__self","text":"check what I'm carrying"},{"id":"go__south","text":"go south"},{"id":"go__east","text":"go east"},{"id":"go__west","text":"go west"},{"id":"go__north","text":"go north"},{"id":"examine__torch","text":"examine the torch"},{"id":"drop__torch","text":"drop the torch"}],"room":"Hall of pillars","say":"slide east to where the steel be stashed","state":{"carrying":["torch"],"dark":false,"location":"Hall of pillars","visible":[]}},{"means":"take__sword","meansText":"take the notched shortsword","options":[{"id":"look__around","text":"look around the room"},{"id":"inventory__self","text":"check what I'm carrying"},{"id":"go__west","text":"go west"},{"id":"take__sword","text":"take the notched shortsword"},{"id":"take__vellum","text":"take the scrap of vellum"},{"id":"examine__sword","text":"examine the notched shortsword"},{"id":"examine__vellum","text":"examine the scrap of vellum"},{"id":"examine__torch","text":"examine the torch"},{"id":"drop__torch","text":"drop the torch"}],"room":"Armory","say":"cop that shortsword, fo shizzle","state":{"carrying":["torch"],"dark":false,"location":"Armory","visible":["notched shortsword","scrap of vellum"]}},{"means":"examine__vellum","meansText":"examine the scrap of vellum","options":[{"id":"look__around","text":"look around the room"},{"id":"inventory__self","text":"check what I'm carrying"},{"id":"go__west","text":"go west"},{"id":"take__vellum","text":"take the scrap of vellum"},{"id":"examine__vellum","text":"examine the scrap of vellum"},{"id":"examine__torch","text":"examine the torch"},{"id":"examine__sword","text":"examine the notched shortsword"},{"id":"drop__torch","text":"drop the torch"},{"id":"drop__sword","text":"drop the notched shortsword"}],"room":"Armory","say":"what that lil paper say, homie?","state":{"carrying":["torch","notched shortsword"],"dark":false,"location":"Armory","visible":["scrap of vellum"]}},{"means":"go__west","meansText":"go west","options":[{"id":"look__around","text":"look around the room"},{"id":"inventory__self","text":"check what I'm carrying"},{"id":"go__west","text":"go west"},{"id":"take__vellum","text":"take the scrap of vellum"},{"id":"examine__vellum","text":"examine the scrap of vellum"},{"id":"examine__torch","text":"examine the torch"},{"id":"examine__sword","text":"examine the notched shortsword"},{"id":"drop__torch","text":"drop the torch"},{"id":"drop__sword","text":"drop the notched shortsword"}],"room":"Armory","say":"bounce back west, ya heard","state":{"carrying":["torch","notched shortsword"],"dark":false,"location":"Armory","visible":["scrap of vellum"]}},{"means":"attack__skeleton","meansText":"attack the skeleton with the shortsword","options":[{"id":"look__around","text":"look around the room"},{"id":"inventory__self","text":"check what I'm carrying"},{"id":"go__south","text":"go south"},{"id":"go__east","text":"go east"},{"id":"go__west","text":"go west"},{"id":"go__north","text":"go north"},{"id":"examine__torch","text":"examine the torch"},{"id":"examine__sword","text":"examine the notched shortsword"},{"id":"drop__torch","text":"drop the torch"},{"id":"drop__sword","text":"drop the notched shortsword"},{"id":"attack__skeleton","text":"attack the skeleton with the shortsword"}],"room":"Hall of pillars","say":"lay the smack down on Mr. Bones wit the blade","state":{"carrying":["torch","notched shortsword"],"dark":false,"location":"Hall of pillars","visible":[]}},{"means":"go__west","meansText":"go west","options":[{"id":"look__around","text":"look around the room"},{"id":"inventory__self","text":"check what I'm carrying"},{"id":"go__south","text":"go south"},{"id":"go__east","text":"go east"},{"id":"go__west","text":"go west"},{"id":"go__north","text":"go north"},{"id":"examine__torch","text":"examine the torch"},{"id":"examine__sword","text":"examine the notched shortsword"},{"id":"drop__torch","text":"drop the torch"},{"id":"drop__sword","text":"drop the notched shortsword"}],"room":"Hall of pillars","say":"creep west into the dark, torch-izzle blazin","state":{"carrying":["torch","notched shortsword"],"dark":false,"location":"Hall of pillars","visible":[]}},{"means":"take__key","meansText":"take the silver key","options":[{"id":"look__around","text":"look around the room"},{"id":"inventory__self","text":"check what I'm carrying"},{"id":"go__east","text":"go east"},{"id":"take__key","text":"take the silver key"},{"id":"examine__key","text":"examine the silver key"},{"id":"examine__torch","text":"examine the torch"},{"id":"examine__sword","text":"examine the notched shortsword"},{"id":"drop__torch","text":"drop the torch"},{"id":"drop__sword","text":"drop the notched shortsword"}],"room":"Ossuary","say":"snatch that silver key off the altar, baby","state":{"carrying":["torch","notched shortsword"],"dark":false,"location":"Ossuary","visible":["silver key"]}},{"means":"go__east","meansText":"go east","options":[{"id":"look__around","text":"look around the room"},{"id":"inventory__self","text":"check what I'm carrying"},{"id":"go__east","text":"go east"},{"id":"examine__torch","text":"examine the torch"},{"id":"examine__sword","text":"examine the notched shortsword"},{"id":"examine__key","text":"examine the silver key"},{"id":"drop__torch","text":"drop the torch"},{"id":"drop__sword","text":"drop the notched shortsword"},{"id":"drop__key","text":"drop the silver key"}],"room":"Ossuary","say":"back east, back east","state":{"carrying":["torch","notched shortsword","silver key"],"dark":false,"location":"Ossuary","visible":[]}},{"means":"go__north","meansText":"go north","options":[{"id":"look__around","text":"look around the room"},{"id":"inventory__self","text":"check what I'm carrying"},{"id":"go__south","text":"go south"},{"id":"go__east","text":"go east"},{"id":"go__west","text":"go west"},{"id":"go__north","text":"go north"},{"id":"examine__torch","text":"examine the torch"},{"id":"examine__sword","text":"examine the notched shortsword"},{"id":"examine__key","text":"examine the silver key"},{"id":"drop__torch","text":"drop the torch"},{"id":"drop__sword","text":"drop the notched shortsword"},{"id":"drop__key","text":"drop the silver key"}],"room":"Hall of pillars","say":"head north, the Doggfather got business","state":{"carrying":["torch","notched shortsword","silver key"],"dark":false,"location":"Hall of pillars","visible":[]}},{"means":"unlock__door","meansText":"unlock the black door with the silver key","options":[{"id":"look__around","text":"look around the room"},{"id":"inventory__self","text":"check what I'm carrying"},{"id":"go__south","text":"go south"},{"id":"go__north","text":"go north"},{"id":"examine__torch","text":"examine the torch"},{"id":"examine__sword","text":"examine the notched shortsword"},{"id":"examine__key","text":"examine the silver key"},{"id":"drop__torch","text":"drop the torch"},{"id":"drop__sword","text":"drop the notched shortsword"},{"id":"drop__key","text":"drop the silver key"},{"id":"unlock__door","text":"unlock the black door with the silver key"}],"room":"Antechamber","say":"unlock that black door wit the key-izzle","state":{"carrying":["torch","notched shortsword","silver key"],"dark":false,"location":"Antechamber","visible":[]}},{"means":"go__north","meansText":"go north","options":[{"id":"look__around","text":"look around the room"},{"id":"inventory__self","text":"check what I'm carrying"},{"id":"go__south","text":"go south"},{"id":"go__north","text":"go north"},{"id":"examine__torch","text":"examine the torch"},{"id":"examine__sword","text":"examine the notched shortsword"},{"id":"examine__key","text":"examine the silver key"},{"id":"drop__torch","text":"drop the torch"},{"id":"drop__sword","text":"drop the notched shortsword"},{"id":"drop__key","text":"drop the silver key"}],"room":"Antechamber","say":"roll north thru that door, it's time to get paid","state":{"carrying":["torch","notched shortsword","silver key"],"dark":false,"location":"Antechamber","visible":[]}},{"means":"take__crown","meansText":"take the jeweled crown","options":[{"id":"look__around","text":"look around the room"},{"id":"inventory__self","text":"check what I'm carrying"},{"id":"go__south","text":"go south"},{"id":"take__crown","text":"take the jeweled crown"},{"id":"examine__crown","text":"examine the jeweled crown"},{"id":"examine__torch","text":"examine the torch"},{"id":"examine__sword","text":"examine the notched shortsword"},{"id":"examine__key","text":"examine the silver key"},{"id":"drop__torch","text":"drop the torch"},{"id":"drop__sword","text":"drop the notched shortsword"},{"id":"drop__key","text":"drop the silver key"}],"room":"Vault","say":"cop that crown, it's the Doggfather's now","state":{"carrying":["torch","notched shortsword","silver key"],"dark":false,"location":"Vault","visible":["jeweled crown"]}}],"none":"none_of_these","noneText":"The player isn't asking for any of these: a question, small talk, or something not possible here.","pairs":[{"a":"typesafe","b":"flash","sameKind":42,"sameTop":48,"turns":48},{"a":"typesafe","b":"27b","sameKind":48,"sameTop":48,"turns":48},{"a":"flash","b":"27b","sameKind":42,"sameTop":48,"turns":48}],"question":"Which action is the player asking to take?","rounds":3,"runs":[{"backend":"typesafe","id":"typesafe","latency":{"max":213,"mean":167.791666666667,"min":138,"p50":165,"p95":200},"lowestOnAMove":0.81,"meanConfidence":0.954375,"name":"TypeSafe Jev","note":"Pinned to jev-1.13.0. A turn's time includes the network round trip from the laptop.","rows":[{"actMax":-1,"asks":[],"clarifyMax":-1,"confidence":0.96,"kind":"unknown","landed":true,"lands":{"act":false,"clarify":false,"unknown":true},"line":0,"ms":179,"offered":6,"ranked":[["none_of_these",0.97],["look__around",0.02],["take__torch",0.01]],"round":0,"top":"none_of_these"},{"actMax":-1,"asks":[],"clarifyMax":-1,"confidence":0.95,"kind":"unknown","landed":true,"lands":{"act":false,"clarify":false,"unknown":true},"line":0,"ms":166,"offered":6,"ranked":[["none_of_these",0.96],["look__around",0.03],["take__torch",0.01]],"round":1,"top":"none_of_these"},{"actMax":-1,"asks":[],"clarifyMax":-1,"confidence":0.96,"kind":"unknown","landed":true,"lands":{"act":false,"clarify":false,"unknown":true},"line":0,"ms":165,"offered":6,"ranked":[["none_of_these",0.97],["look__around",0.02],["take__torch",0.01]],"round":2,"top":"none_of_these"},{"actMax":100,"asks":["take__torch","inventory__self"],"clarifyMax":100,"confidence":1.0,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":1,"ms":151,"offered":6,"ranked":[["take__torch",1.0],["inventory__self",0.0],["examine__torch",0.0]],"round":0,"top":"take__torch"},{"actMax":100,"asks":["take__torch","examine__torch"],"clarifyMax":100,"confidence":1.0,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":1,"ms":213,"offered":6,"ranked":[["take__torch",1.0],["examine__torch",0.0],["go__north",0.0]],"round":1,"top":"take__torch"},{"actMax":100,"asks":["take__torch","examine__torch"],"clarifyMax":100,"confidence":1.0,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":1,"ms":158,"offered":6,"ranked":[["take__torch",1.0],["examine__torch",0.0],["go__north",0.0]],"round":2,"top":"take__torch"},{"actMax":99,"asks":["light__torch","look__around"],"clarifyMax":99,"confidence":0.99,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":2,"ms":165,"offered":7,"ranked":[["light__torch",0.99],["none_of_these",0.01],["look__around",0.0]],"round":0,"top":"light__torch"},{"actMax":99,"asks":["light__torch","examine__torch"],"clarifyMax":99,"confidence":0.99,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":2,"ms":172,"offered":7,"ranked":[["light__torch",0.99],["none_of_these",0.01],["examine__torch",0.0]],"round":1,"top":"light__torch"},{"actMax":99,"asks":["light__torch","drop__torch"],"clarifyMax":99,"confidence":0.99,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":2,"ms":178,"offered":7,"ranked":[["light__torch",0.99],["none_of_these",0.01],["drop__torch",0.0]],"round":2,"top":"light__torch"},{"actMax":89,"asks":["go__north","look__around"],"clarifyMax":89,"confidence":0.89,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":3,"ms":165,"offered":6,"ranked":[["go__north",0.91],["none_of_these",0.08],["look__around",0.01]],"round":0,"top":"go__north"},{"actMax":91,"asks":["go__north","look__around"],"clarifyMax":91,"confidence":0.91,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":3,"ms":160,"offered":6,"ranked":[["go__north",0.93],["none_of_these",0.06],["look__around",0.01]],"round":1,"top":"go__north"},{"actMax":91,"asks":["go__north","look__around"],"clarifyMax":91,"confidence":0.91,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":3,"ms":166,"offered":6,"ranked":[["go__north",0.92],["none_of_these",0.07],["look__around",0.01]],"round":2,"top":"go__north"},{"actMax":87,"asks":["go__east","go__west"],"clarifyMax":87,"confidence":0.87,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":4,"ms":158,"offered":9,"ranked":[["go__east",0.88],["none_of_these",0.12],["go__west",0.0]],"round":0,"top":"go__east"},{"actMax":85,"asks":["go__east","drop__torch"],"clarifyMax":85,"confidence":0.85,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":4,"ms":144,"offered":9,"ranked":[["go__east",0.87],["none_of_these",0.13],["drop__torch",0.0]],"round":1,"top":"go__east"},{"actMax":89,"asks":["go__east","drop__torch"],"clarifyMax":89,"confidence":0.89,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":4,"ms":151,"offered":9,"ranked":[["go__east",0.91],["none_of_these",0.09],["drop__torch",0.0]],"round":2,"top":"go__east"},{"actMax":87,"asks":["take__sword","examine__sword"],"clarifyMax":87,"confidence":0.87,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":5,"ms":165,"offered":10,"ranked":[["take__sword",0.88],["none_of_these",0.11],["examine__sword",0.01]],"round":0,"top":"take__sword"},{"actMax":89,"asks":["take__sword","examine__sword"],"clarifyMax":89,"confidence":0.89,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":5,"ms":166,"offered":10,"ranked":[["take__sword",0.91],["none_of_these",0.08],["examine__sword",0.01]],"round":1,"top":"take__sword"},{"actMax":88,"asks":["take__sword","examine__sword"],"clarifyMax":88,"confidence":0.88,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":5,"ms":144,"offered":10,"ranked":[["take__sword",0.89],["none_of_these",0.1],["examine__sword",0.01]],"round":2,"top":"take__sword"},{"actMax":94,"asks":["examine__vellum","take__vellum"],"clarifyMax":94,"confidence":0.94,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":6,"ms":200,"offered":10,"ranked":[["examine__vellum",0.95],["none_of_these",0.04],["take__vellum",0.01]],"round":0,"top":"examine__vellum"},{"actMax":96,"asks":["examine__vellum","take__vellum"],"clarifyMax":96,"confidence":0.96,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":6,"ms":193,"offered":10,"ranked":[["examine__vellum",0.96],["none_of_these",0.03],["take__vellum",0.01]],"round":1,"top":"examine__vellum"},{"actMax":95,"asks":["examine__vellum","take__vellum"],"clarifyMax":95,"confidence":0.95,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":6,"ms":158,"offered":10,"ranked":[["examine__vellum",0.96],["none_of_these",0.03],["take__vellum",0.01]],"round":2,"top":"examine__vellum"},{"actMax":81,"asks":["go__west","drop__sword"],"clarifyMax":81,"confidence":0.81,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":7,"ms":193,"offered":10,"ranked":[["go__west",0.84],["none_of_these",0.16],["drop__sword",0.0]],"round":0,"top":"go__west"},{"actMax":87,"asks":["go__west","take__vellum"],"clarifyMax":87,"confidence":0.87,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":7,"ms":193,"offered":10,"ranked":[["go__west",0.88],["none_of_these",0.12],["take__vellum",0.0]],"round":1,"top":"go__west"},{"actMax":83,"asks":["go__west","examine__sword"],"clarifyMax":83,"confidence":0.83,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":7,"ms":172,"offered":10,"ranked":[["go__west",0.85],["none_of_these",0.15],["examine__sword",0.0]],"round":2,"top":"go__west"},{"actMax":100,"asks":["attack__skeleton","drop__sword"],"clarifyMax":100,"confidence":1.0,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":8,"ms":151,"offered":12,"ranked":[["attack__skeleton",1.0],["drop__sword",0.0],["drop__torch",0.0]],"round":0,"top":"attack__skeleton"},{"actMax":100,"asks":["attack__skeleton","go__north"],"clarifyMax":100,"confidence":1.0,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":8,"ms":152,"offered":12,"ranked":[["attack__skeleton",1.0],["none_of_these",0.0],["go__north",0.0]],"round":1,"top":"attack__skeleton"},{"actMax":100,"asks":["attack__skeleton","drop__sword"],"clarifyMax":100,"confidence":1.0,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":8,"ms":179,"offered":12,"ranked":[["attack__skeleton",1.0],["drop__sword",0.0],["drop__torch",0.0]],"round":2,"top":"attack__skeleton"},{"actMax":99,"asks":["go__west","drop__sword"],"clarifyMax":99,"confidence":0.99,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":9,"ms":158,"offered":11,"ranked":[["go__west",0.99],["none_of_these",0.01],["drop__sword",0.0]],"round":0,"top":"go__west"},{"actMax":99,"asks":["go__west","drop__sword"],"clarifyMax":99,"confidence":0.99,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":9,"ms":180,"offered":11,"ranked":[["go__west",0.99],["none_of_these",0.01],["drop__sword",0.0]],"round":1,"top":"go__west"},{"actMax":99,"asks":["go__west","go__east"],"clarifyMax":99,"confidence":0.99,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":9,"ms":186,"offered":11,"ranked":[["go__west",0.99],["none_of_these",0.01],["go__east",0.0]],"round":2,"top":"go__west"},{"actMax":100,"asks":["take__key","drop__sword"],"clarifyMax":100,"confidence":1.0,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":10,"ms":186,"offered":10,"ranked":[["take__key",1.0],["drop__sword",0.0],["drop__torch",0.0]],"round":0,"top":"take__key"},{"actMax":100,"asks":["take__key","drop__sword"],"clarifyMax":100,"confidence":1.0,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":10,"ms":138,"offered":10,"ranked":[["take__key",1.0],["drop__sword",0.0],["drop__torch",0.0]],"round":1,"top":"take__key"},{"actMax":100,"asks":["take__key","drop__sword"],"clarifyMax":100,"confidence":1.0,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":10,"ms":158,"offered":10,"ranked":[["take__key",1.0],["drop__sword",0.0],["drop__torch",0.0]],"round":2,"top":"take__key"},{"actMax":97,"asks":["go__east","drop__key"],"clarifyMax":97,"confidence":0.97,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":11,"ms":151,"offered":10,"ranked":[["go__east",0.98],["none_of_these",0.02],["drop__key",0.0]],"round":0,"top":"go__east"},{"actMax":97,"asks":["go__east","drop__key"],"clarifyMax":97,"confidence":0.97,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":11,"ms":152,"offered":10,"ranked":[["go__east",0.98],["none_of_these",0.02],["drop__key",0.0]],"round":1,"top":"go__east"},{"actMax":98,"asks":["go__east","examine__key"],"clarifyMax":98,"confidence":0.98,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":11,"ms":213,"offered":10,"ranked":[["go__east",0.98],["none_of_these",0.02],["examine__key",0.0]],"round":2,"top":"go__east"},{"actMax":99,"asks":["go__north","examine__key"],"clarifyMax":99,"confidence":0.99,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":12,"ms":172,"offered":13,"ranked":[["go__north",0.99],["none_of_these",0.01],["examine__key",0.0]],"round":0,"top":"go__north"},{"actMax":98,"asks":["go__north","drop__key"],"clarifyMax":98,"confidence":0.98,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":12,"ms":158,"offered":13,"ranked":[["go__north",0.99],["none_of_these",0.01],["drop__key",0.0]],"round":1,"top":"go__north"},{"actMax":99,"asks":["go__north","drop__key"],"clarifyMax":99,"confidence":0.99,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":12,"ms":179,"offered":13,"ranked":[["go__north",0.99],["none_of_these",0.01],["drop__key",0.0]],"round":2,"top":"go__north"},{"actMax":100,"asks":["unlock__door","drop__sword"],"clarifyMax":100,"confidence":1.0,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":13,"ms":179,"offered":12,"ranked":[["unlock__door",1.0],["drop__sword",0.0],["none_of_these",0.0]],"round":0,"top":"unlock__door"},{"actMax":100,"asks":["unlock__door","drop__key"],"clarifyMax":100,"confidence":1.0,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":13,"ms":186,"offered":12,"ranked":[["unlock__door",1.0],["drop__key",0.0],["drop__sword",0.0]],"round":1,"top":"unlock__door"},{"actMax":100,"asks":["unlock__door","drop__key"],"clarifyMax":100,"confidence":1.0,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":13,"ms":145,"offered":12,"ranked":[["unlock__door",1.0],["drop__key",0.0],["drop__sword",0.0]],"round":2,"top":"unlock__door"},{"actMax":98,"asks":["go__north","drop__key"],"clarifyMax":98,"confidence":0.98,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":14,"ms":151,"offered":11,"ranked":[["go__north",0.98],["none_of_these",0.02],["drop__key",0.0]],"round":0,"top":"go__north"},{"actMax":98,"asks":["go__north","drop__key"],"clarifyMax":98,"confidence":0.98,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":14,"ms":151,"offered":11,"ranked":[["go__north",0.98],["none_of_these",0.02],["drop__key",0.0]],"round":1,"top":"go__north"},{"actMax":99,"asks":["go__north","drop__sword"],"clarifyMax":99,"confidence":0.99,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":14,"ms":158,"offered":11,"ranked":[["go__north",0.99],["none_of_these",0.01],["drop__sword",0.0]],"round":2,"top":"go__north"},{"actMax":94,"asks":["take__crown","drop__key"],"clarifyMax":94,"confidence":0.94,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":15,"ms":180,"offered":12,"ranked":[["take__crown",0.95],["none_of_these",0.05],["drop__key",0.0]],"round":0,"top":"take__crown"},{"actMax":96,"asks":["take__crown","drop__key"],"clarifyMax":96,"confidence":0.96,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":15,"ms":165,"offered":12,"ranked":[["take__crown",0.96],["none_of_these",0.04],["drop__key",0.0]],"round":1,"top":"take__crown"},{"actMax":95,"asks":["take__crown","drop__sword"],"clarifyMax":95,"confidence":0.95,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":15,"ms":151,"offered":12,"ranked":[["take__crown",0.95],["none_of_these",0.05],["drop__sword",0.0]],"round":2,"top":"take__crown"}],"sameEveryRound":false,"servedModel":"jev-1.13.0","short":"TypeSafe","size":"Hosted API","summary":{"act":45,"clarify":0,"error":0,"landed":48,"max_ms":213,"p50_ms":165,"p95_ms":200,"turns":48,"unknown":3},"sure":34,"temperature":"","where":"Hosted"},{"backend":"flash9b","id":"flash","latency":{"max":228,"mean":175.5,"min":143,"p50":185,"p95":200},"lowestOnAMove":0.4833,"meanConfidence":0.833537499999999,"name":"Open Jev Flash 9B","note":"MLX 4-bit on an Apple M5 Pro laptop, with the 27B loaded beside it.","rows":[{"actMax":-1,"asks":[],"clarifyMax":-1,"confidence":0.5173,"kind":"unknown","landed":true,"lands":{"act":false,"clarify":false,"unknown":true},"line":0,"ms":158,"offered":6,"ranked":[["none_of_these",0.5977],["inventory__self",0.1036],["examine__torch",0.0922]],"round":0,"top":"none_of_these"},{"actMax":-1,"asks":[],"clarifyMax":-1,"confidence":0.5173,"kind":"unknown","landed":true,"lands":{"act":false,"clarify":false,"unknown":true},"line":0,"ms":144,"offered":6,"ranked":[["none_of_these",0.5977],["inventory__self",0.1036],["examine__torch",0.0922]],"round":1,"top":"none_of_these"},{"actMax":-1,"asks":[],"clarifyMax":-1,"confidence":0.5173,"kind":"unknown","landed":true,"lands":{"act":false,"clarify":false,"unknown":true},"line":0,"ms":145,"offered":6,"ranked":[["none_of_these",0.5977],["inventory__self",0.1036],["examine__torch",0.0922]],"round":2,"top":"none_of_these"},{"actMax":96,"asks":["take__torch","examine__torch"],"clarifyMax":96,"confidence":0.9619,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":1,"ms":151,"offered":6,"ranked":[["take__torch",0.9682],["none_of_these",0.0205],["examine__torch",0.0072]],"round":0,"top":"take__torch"},{"actMax":96,"asks":["take__torch","examine__torch"],"clarifyMax":96,"confidence":0.9619,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":1,"ms":151,"offered":6,"ranked":[["take__torch",0.9682],["none_of_these",0.0205],["examine__torch",0.0072]],"round":1,"top":"take__torch"},{"actMax":96,"asks":["take__torch","examine__torch"],"clarifyMax":96,"confidence":0.9619,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":1,"ms":144,"offered":6,"ranked":[["take__torch",0.9682],["none_of_these",0.0205],["examine__torch",0.0072]],"round":2,"top":"take__torch"},{"actMax":94,"asks":["light__torch","drop__torch"],"clarifyMax":94,"confidence":0.9462,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":2,"ms":158,"offered":7,"ranked":[["light__torch",0.9539],["none_of_these",0.016],["drop__torch",0.0113]],"round":0,"top":"light__torch"},{"actMax":94,"asks":["light__torch","drop__torch"],"clarifyMax":94,"confidence":0.9462,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":2,"ms":144,"offered":7,"ranked":[["light__torch",0.9539],["none_of_these",0.016],["drop__torch",0.0113]],"round":1,"top":"light__torch"},{"actMax":94,"asks":["light__torch","drop__torch"],"clarifyMax":94,"confidence":0.9462,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":2,"ms":152,"offered":7,"ranked":[["light__torch",0.9539],["none_of_these",0.016],["drop__torch",0.0113]],"round":2,"top":"light__torch"},{"actMax":89,"asks":["go__north","look__around"],"clarifyMax":89,"confidence":0.897,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":3,"ms":143,"offered":6,"ranked":[["go__north",0.9142],["none_of_these",0.0309],["look__around",0.0244]],"round":0,"top":"go__north"},{"actMax":89,"asks":["go__north","look__around"],"clarifyMax":89,"confidence":0.897,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":3,"ms":144,"offered":6,"ranked":[["go__north",0.9142],["none_of_these",0.0309],["look__around",0.0244]],"round":1,"top":"go__north"},{"actMax":89,"asks":["go__north","look__around"],"clarifyMax":89,"confidence":0.897,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":3,"ms":144,"offered":6,"ranked":[["go__north",0.9142],["none_of_these",0.0309],["look__around",0.0244]],"round":2,"top":"go__north"},{"actMax":79,"asks":["go__east","go__west"],"clarifyMax":79,"confidence":0.7915,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":4,"ms":151,"offered":9,"ranked":[["go__east",0.8147],["none_of_these",0.0623],["go__west",0.0275]],"round":0,"top":"go__east"},{"actMax":79,"asks":["go__east","go__west"],"clarifyMax":79,"confidence":0.7915,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":4,"ms":151,"offered":9,"ranked":[["go__east",0.8147],["none_of_these",0.0623],["go__west",0.0275]],"round":1,"top":"go__east"},{"actMax":79,"asks":["go__east","go__west"],"clarifyMax":79,"confidence":0.7915,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":4,"ms":151,"offered":9,"ranked":[["go__east",0.8147],["none_of_these",0.0623],["go__west",0.0275]],"round":2,"top":"go__east"},{"actMax":48,"asks":["take__sword","examine__sword"],"clarifyMax":48,"confidence":0.4833,"kind":"clarify","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":5,"ms":185,"offered":10,"ranked":[["take__sword",0.535],["examine__sword",0.1317],["take__vellum",0.1172]],"round":0,"top":"take__sword"},{"actMax":48,"asks":["take__sword","examine__sword"],"clarifyMax":48,"confidence":0.4833,"kind":"clarify","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":5,"ms":179,"offered":10,"ranked":[["take__sword",0.535],["examine__sword",0.1317],["take__vellum",0.1172]],"round":1,"top":"take__sword"},{"actMax":48,"asks":["take__sword","examine__sword"],"clarifyMax":48,"confidence":0.4833,"kind":"clarify","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":5,"ms":178,"offered":10,"ranked":[["take__sword",0.535],["examine__sword",0.1317],["take__vellum",0.1172]],"round":2,"top":"take__sword"},{"actMax":74,"asks":["examine__vellum","take__vellum"],"clarifyMax":74,"confidence":0.7447,"kind":"clarify","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":6,"ms":186,"offered":10,"ranked":[["examine__vellum",0.7702],["take__vellum",0.1501],["none_of_these",0.0292]],"round":0,"top":"examine__vellum"},{"actMax":74,"asks":["examine__vellum","take__vellum"],"clarifyMax":74,"confidence":0.7447,"kind":"clarify","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":6,"ms":179,"offered":10,"ranked":[["examine__vellum",0.7702],["take__vellum",0.1501],["none_of_these",0.0292]],"round":1,"top":"examine__vellum"},{"actMax":74,"asks":["examine__vellum","take__vellum"],"clarifyMax":74,"confidence":0.7447,"kind":"clarify","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":6,"ms":180,"offered":10,"ranked":[["examine__vellum",0.7702],["take__vellum",0.1501],["none_of_these",0.0292]],"round":2,"top":"examine__vellum"},{"actMax":82,"asks":["go__west","take__vellum"],"clarifyMax":82,"confidence":0.8215,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":7,"ms":179,"offered":10,"ranked":[["go__west",0.8393],["take__vellum",0.0508],["examine__vellum",0.0358]],"round":0,"top":"go__west"},{"actMax":82,"asks":["go__west","take__vellum"],"clarifyMax":82,"confidence":0.8215,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":7,"ms":179,"offered":10,"ranked":[["go__west",0.8393],["take__vellum",0.0508],["examine__vellum",0.0358]],"round":1,"top":"go__west"},{"actMax":82,"asks":["go__west","take__vellum"],"clarifyMax":82,"confidence":0.8215,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":7,"ms":179,"offered":10,"ranked":[["go__west",0.8393],["take__vellum",0.0508],["examine__vellum",0.0358]],"round":2,"top":"go__west"},{"actMax":84,"asks":["attack__skeleton","drop__sword"],"clarifyMax":84,"confidence":0.8434,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":8,"ms":185,"offered":12,"ranked":[["attack__skeleton",0.8565],["drop__sword",0.0655],["none_of_these",0.0273]],"round":0,"top":"attack__skeleton"},{"actMax":84,"asks":["attack__skeleton","drop__sword"],"clarifyMax":84,"confidence":0.8434,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":8,"ms":185,"offered":12,"ranked":[["attack__skeleton",0.8565],["drop__sword",0.0655],["none_of_these",0.0273]],"round":1,"top":"attack__skeleton"},{"actMax":84,"asks":["attack__skeleton","drop__sword"],"clarifyMax":84,"confidence":0.8434,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":8,"ms":186,"offered":12,"ranked":[["attack__skeleton",0.8565],["drop__sword",0.0655],["none_of_these",0.0273]],"round":2,"top":"attack__skeleton"},{"actMax":86,"asks":["go__west","go__east"],"clarifyMax":86,"confidence":0.8649,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":9,"ms":185,"offered":11,"ranked":[["go__west",0.8772],["none_of_these",0.0473],["go__east",0.0249]],"round":0,"top":"go__west"},{"actMax":86,"asks":["go__west","go__east"],"clarifyMax":86,"confidence":0.8649,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":9,"ms":178,"offered":11,"ranked":[["go__west",0.8772],["none_of_these",0.0473],["go__east",0.0249]],"round":1,"top":"go__west"},{"actMax":86,"asks":["go__west","go__east"],"clarifyMax":86,"confidence":0.8649,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":9,"ms":186,"offered":11,"ranked":[["go__west",0.8772],["none_of_these",0.0473],["go__east",0.0249]],"round":2,"top":"go__west"},{"actMax":97,"asks":["take__key","examine__key"],"clarifyMax":97,"confidence":0.9755,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":10,"ms":186,"offered":10,"ranked":[["take__key",0.9779],["examine__key",0.0103],["none_of_these",0.0061]],"round":0,"top":"take__key"},{"actMax":97,"asks":["take__key","examine__key"],"clarifyMax":97,"confidence":0.9755,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":10,"ms":186,"offered":10,"ranked":[["take__key",0.9779],["examine__key",0.0103],["none_of_these",0.0061]],"round":1,"top":"take__key"},{"actMax":97,"asks":["take__key","examine__key"],"clarifyMax":97,"confidence":0.9755,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":10,"ms":186,"offered":10,"ranked":[["take__key",0.9779],["examine__key",0.0103],["none_of_these",0.0061]],"round":2,"top":"take__key"},{"actMax":91,"asks":["go__east","look__around"],"clarifyMax":91,"confidence":0.9122,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":11,"ms":186,"offered":10,"ranked":[["go__east",0.921],["look__around",0.0184],["none_of_these",0.0173]],"round":0,"top":"go__east"},{"actMax":91,"asks":["go__east","look__around"],"clarifyMax":91,"confidence":0.9122,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":11,"ms":186,"offered":10,"ranked":[["go__east",0.921],["look__around",0.0184],["none_of_these",0.0173]],"round":1,"top":"go__east"},{"actMax":91,"asks":["go__east","look__around"],"clarifyMax":91,"confidence":0.9122,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":11,"ms":186,"offered":10,"ranked":[["go__east",0.921],["look__around",0.0184],["none_of_these",0.0173]],"round":2,"top":"go__east"},{"actMax":87,"asks":["go__north","go__east"],"clarifyMax":87,"confidence":0.8754,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":12,"ms":228,"offered":13,"ranked":[["go__north",0.885],["none_of_these",0.0299],["go__east",0.0177]],"round":0,"top":"go__north"},{"actMax":87,"asks":["go__north","go__east"],"clarifyMax":87,"confidence":0.8754,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":12,"ms":186,"offered":13,"ranked":[["go__north",0.885],["none_of_these",0.0299],["go__east",0.0177]],"round":1,"top":"go__north"},{"actMax":87,"asks":["go__north","go__east"],"clarifyMax":87,"confidence":0.8754,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":12,"ms":193,"offered":13,"ranked":[["go__north",0.885],["none_of_these",0.0299],["go__east",0.0177]],"round":2,"top":"go__north"},{"actMax":96,"asks":["unlock__door","drop__key"],"clarifyMax":96,"confidence":0.9629,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":13,"ms":193,"offered":12,"ranked":[["unlock__door",0.966],["none_of_these",0.0162],["drop__key",0.0057]],"round":0,"top":"unlock__door"},{"actMax":96,"asks":["unlock__door","drop__key"],"clarifyMax":96,"confidence":0.9629,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":13,"ms":185,"offered":12,"ranked":[["unlock__door",0.966],["none_of_these",0.0162],["drop__key",0.0057]],"round":1,"top":"unlock__door"},{"actMax":96,"asks":["unlock__door","drop__key"],"clarifyMax":96,"confidence":0.9629,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":13,"ms":186,"offered":12,"ranked":[["unlock__door",0.966],["none_of_these",0.0162],["drop__key",0.0057]],"round":2,"top":"unlock__door"},{"actMax":92,"asks":["go__north","go__south"],"clarifyMax":92,"confidence":0.9295,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":14,"ms":192,"offered":11,"ranked":[["go__north",0.9359],["none_of_these",0.021],["go__south",0.0083]],"round":0,"top":"go__north"},{"actMax":92,"asks":["go__north","go__south"],"clarifyMax":92,"confidence":0.9295,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":14,"ms":193,"offered":11,"ranked":[["go__north",0.9359],["none_of_these",0.021],["go__south",0.0083]],"round":1,"top":"go__north"},{"actMax":92,"asks":["go__north","go__south"],"clarifyMax":92,"confidence":0.9295,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":14,"ms":192,"offered":11,"ranked":[["go__north",0.9359],["none_of_these",0.021],["go__south",0.0083]],"round":2,"top":"go__north"},{"actMax":80,"asks":["take__crown","examine__crown"],"clarifyMax":80,"confidence":0.8094,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":15,"ms":200,"offered":12,"ranked":[["take__crown",0.8253],["examine__crown",0.0897],["none_of_these",0.0296]],"round":0,"top":"take__crown"},{"actMax":80,"asks":["take__crown","examine__crown"],"clarifyMax":80,"confidence":0.8094,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":15,"ms":200,"offered":12,"ranked":[["take__crown",0.8253],["examine__crown",0.0897],["none_of_these",0.0296]],"round":1,"top":"take__crown"},{"actMax":80,"asks":["take__crown","examine__crown"],"clarifyMax":80,"confidence":0.8094,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":15,"ms":200,"offered":12,"ranked":[["take__crown",0.8253],["examine__crown",0.0897],["none_of_these",0.0296]],"round":2,"top":"take__crown"}],"sameEveryRound":true,"servedModel":"OpenJev-Flash-9B-MLX-4bit","short":"Flash 9B","size":"5 GB","summary":{"act":39,"clarify":6,"error":0,"landed":48,"max_ms":228,"p50_ms":185,"p95_ms":200,"turns":48,"unknown":3},"sure":9,"temperature":"1.07","where":"Local"},{"backend":"openjev","id":"27b","latency":{"max":634,"mean":545.604166666667,"min":434,"p50":579,"p95":620},"lowestOnAMove":0.9328,"meanConfidence":0.9847375,"name":"Open Jev 27B","note":"MLX 4-bit on an Apple M5 Pro laptop, with Flash 9B loaded beside it.","rows":[{"actMax":-1,"asks":[],"clarifyMax":-1,"confidence":0.974,"kind":"unknown","landed":true,"lands":{"act":false,"clarify":false,"unknown":true},"line":0,"ms":462,"offered":6,"ranked":[["none_of_these",0.9783],["take__torch",0.0102],["look__around",0.0057]],"round":0,"top":"none_of_these"},{"actMax":-1,"asks":[],"clarifyMax":-1,"confidence":0.974,"kind":"unknown","landed":true,"lands":{"act":false,"clarify":false,"unknown":true},"line":0,"ms":441,"offered":6,"ranked":[["none_of_these",0.9783],["take__torch",0.0102],["look__around",0.0057]],"round":1,"top":"none_of_these"},{"actMax":-1,"asks":[],"clarifyMax":-1,"confidence":0.974,"kind":"unknown","landed":true,"lands":{"act":false,"clarify":false,"unknown":true},"line":0,"ms":441,"offered":6,"ranked":[["none_of_these",0.9783],["take__torch",0.0102],["look__around",0.0057]],"round":2,"top":"none_of_these"},{"actMax":99,"asks":["take__torch","examine__torch"],"clarifyMax":99,"confidence":0.9999,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":1,"ms":441,"offered":6,"ranked":[["take__torch",0.9999],["examine__torch",0.0001],["go__north",0.0]],"round":0,"top":"take__torch"},{"actMax":99,"asks":["take__torch","examine__torch"],"clarifyMax":99,"confidence":0.9999,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":1,"ms":441,"offered":6,"ranked":[["take__torch",0.9999],["examine__torch",0.0001],["go__north",0.0]],"round":1,"top":"take__torch"},{"actMax":99,"asks":["take__torch","examine__torch"],"clarifyMax":99,"confidence":0.9999,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":1,"ms":434,"offered":6,"ranked":[["take__torch",0.9999],["examine__torch",0.0001],["go__north",0.0]],"round":2,"top":"take__torch"},{"actMax":93,"asks":["light__torch","drop__torch"],"clarifyMax":93,"confidence":0.9328,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":2,"ms":455,"offered":7,"ranked":[["light__torch",0.9424],["none_of_these",0.043],["drop__torch",0.0132]],"round":0,"top":"light__torch"},{"actMax":93,"asks":["light__torch","drop__torch"],"clarifyMax":93,"confidence":0.9328,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":2,"ms":462,"offered":7,"ranked":[["light__torch",0.9424],["none_of_these",0.043],["drop__torch",0.0132]],"round":1,"top":"light__torch"},{"actMax":93,"asks":["light__torch","drop__torch"],"clarifyMax":93,"confidence":0.9328,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":2,"ms":456,"offered":7,"ranked":[["light__torch",0.9424],["none_of_these",0.043],["drop__torch",0.0132]],"round":2,"top":"light__torch"},{"actMax":98,"asks":["go__north","look__around"],"clarifyMax":98,"confidence":0.9898,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":3,"ms":441,"offered":6,"ranked":[["go__north",0.9915],["none_of_these",0.0077],["look__around",0.0005]],"round":0,"top":"go__north"},{"actMax":98,"asks":["go__north","look__around"],"clarifyMax":98,"confidence":0.9898,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":3,"ms":434,"offered":6,"ranked":[["go__north",0.9915],["none_of_these",0.0077],["look__around",0.0005]],"round":1,"top":"go__north"},{"actMax":98,"asks":["go__north","look__around"],"clarifyMax":98,"confidence":0.9898,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":3,"ms":441,"offered":6,"ranked":[["go__north",0.9915],["none_of_these",0.0077],["look__around",0.0005]],"round":2,"top":"go__north"},{"actMax":96,"asks":["go__east","go__west"],"clarifyMax":96,"confidence":0.9645,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":4,"ms":468,"offered":9,"ranked":[["go__east",0.9685],["none_of_these",0.0284],["go__west",0.0015]],"round":0,"top":"go__east"},{"actMax":96,"asks":["go__east","go__west"],"clarifyMax":96,"confidence":0.9645,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":4,"ms":462,"offered":9,"ranked":[["go__east",0.9685],["none_of_these",0.0284],["go__west",0.0015]],"round":1,"top":"go__east"},{"actMax":96,"asks":["go__east","go__west"],"clarifyMax":96,"confidence":0.9645,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":4,"ms":462,"offered":9,"ranked":[["go__east",0.9685],["none_of_these",0.0284],["go__west",0.0015]],"round":2,"top":"go__east"},{"actMax":99,"asks":["take__sword","take__vellum"],"clarifyMax":99,"confidence":0.993,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":5,"ms":634,"offered":10,"ranked":[["take__sword",0.9937],["none_of_these",0.0028],["take__vellum",0.0024]],"round":0,"top":"take__sword"},{"actMax":99,"asks":["take__sword","take__vellum"],"clarifyMax":99,"confidence":0.993,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":5,"ms":572,"offered":10,"ranked":[["take__sword",0.9937],["none_of_these",0.0028],["take__vellum",0.0024]],"round":1,"top":"take__sword"},{"actMax":99,"asks":["take__sword","take__vellum"],"clarifyMax":99,"confidence":0.993,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":5,"ms":572,"offered":10,"ranked":[["take__sword",0.9937],["none_of_these",0.0028],["take__vellum",0.0024]],"round":2,"top":"take__sword"},{"actMax":99,"asks":["examine__vellum","take__vellum"],"clarifyMax":99,"confidence":0.9948,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":6,"ms":572,"offered":10,"ranked":[["examine__vellum",0.9954],["take__vellum",0.0032],["none_of_these",0.001]],"round":0,"top":"examine__vellum"},{"actMax":99,"asks":["examine__vellum","take__vellum"],"clarifyMax":99,"confidence":0.9948,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":6,"ms":572,"offered":10,"ranked":[["examine__vellum",0.9954],["take__vellum",0.0032],["none_of_these",0.001]],"round":1,"top":"examine__vellum"},{"actMax":99,"asks":["examine__vellum","take__vellum"],"clarifyMax":99,"confidence":0.9948,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":6,"ms":571,"offered":10,"ranked":[["examine__vellum",0.9954],["take__vellum",0.0032],["none_of_these",0.001]],"round":2,"top":"examine__vellum"},{"actMax":97,"asks":["go__west","take__vellum"],"clarifyMax":97,"confidence":0.9783,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":7,"ms":586,"offered":10,"ranked":[["go__west",0.9805],["none_of_these",0.016],["take__vellum",0.0013]],"round":0,"top":"go__west"},{"actMax":97,"asks":["go__west","take__vellum"],"clarifyMax":97,"confidence":0.9783,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":7,"ms":572,"offered":10,"ranked":[["go__west",0.9805],["none_of_these",0.016],["take__vellum",0.0013]],"round":1,"top":"go__west"},{"actMax":97,"asks":["go__west","take__vellum"],"clarifyMax":97,"confidence":0.9783,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":7,"ms":572,"offered":10,"ranked":[["go__west",0.9805],["none_of_these",0.016],["take__vellum",0.0013]],"round":2,"top":"go__west"},{"actMax":97,"asks":["attack__skeleton","drop__sword"],"clarifyMax":97,"confidence":0.9726,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":8,"ms":586,"offered":12,"ranked":[["attack__skeleton",0.9749],["none_of_these",0.0247],["drop__sword",0.0002]],"round":0,"top":"attack__skeleton"},{"actMax":97,"asks":["attack__skeleton","drop__sword"],"clarifyMax":97,"confidence":0.9726,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":8,"ms":586,"offered":12,"ranked":[["attack__skeleton",0.9749],["none_of_these",0.0247],["drop__sword",0.0002]],"round":1,"top":"attack__skeleton"},{"actMax":97,"asks":["attack__skeleton","drop__sword"],"clarifyMax":97,"confidence":0.9726,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":8,"ms":586,"offered":12,"ranked":[["attack__skeleton",0.9749],["none_of_these",0.0247],["drop__sword",0.0002]],"round":2,"top":"attack__skeleton"},{"actMax":99,"asks":["go__west","go__north"],"clarifyMax":99,"confidence":0.9956,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":9,"ms":579,"offered":11,"ranked":[["go__west",0.996],["none_of_these",0.0037],["go__north",0.0001]],"round":0,"top":"go__west"},{"actMax":99,"asks":["go__west","go__north"],"clarifyMax":99,"confidence":0.9956,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":9,"ms":572,"offered":11,"ranked":[["go__west",0.996],["none_of_these",0.0037],["go__north",0.0001]],"round":1,"top":"go__west"},{"actMax":99,"asks":["go__west","go__north"],"clarifyMax":99,"confidence":0.9956,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":9,"ms":579,"offered":11,"ranked":[["go__west",0.996],["none_of_these",0.0037],["go__north",0.0001]],"round":2,"top":"go__west"},{"actMax":99,"asks":["take__key","examine__key"],"clarifyMax":99,"confidence":0.9997,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":10,"ms":586,"offered":10,"ranked":[["take__key",0.9997],["examine__key",0.0001],["none_of_these",0.0001]],"round":0,"top":"take__key"},{"actMax":99,"asks":["take__key","examine__key"],"clarifyMax":99,"confidence":0.9997,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":10,"ms":586,"offered":10,"ranked":[["take__key",0.9997],["examine__key",0.0001],["none_of_these",0.0001]],"round":1,"top":"take__key"},{"actMax":99,"asks":["take__key","examine__key"],"clarifyMax":99,"confidence":0.9997,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":10,"ms":579,"offered":10,"ranked":[["take__key",0.9997],["examine__key",0.0001],["none_of_these",0.0001]],"round":2,"top":"take__key"},{"actMax":99,"asks":["go__east","look__around"],"clarifyMax":99,"confidence":0.9975,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":11,"ms":579,"offered":10,"ranked":[["go__east",0.9977],["none_of_these",0.0021],["look__around",0.0001]],"round":0,"top":"go__east"},{"actMax":99,"asks":["go__east","look__around"],"clarifyMax":99,"confidence":0.9975,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":11,"ms":579,"offered":10,"ranked":[["go__east",0.9977],["none_of_these",0.0021],["look__around",0.0001]],"round":1,"top":"go__east"},{"actMax":99,"asks":["go__east","look__around"],"clarifyMax":99,"confidence":0.9975,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":11,"ms":586,"offered":10,"ranked":[["go__east",0.9977],["none_of_these",0.0021],["look__around",0.0001]],"round":2,"top":"go__east"},{"actMax":98,"asks":["go__north","drop__key"],"clarifyMax":98,"confidence":0.9881,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":12,"ms":612,"offered":13,"ranked":[["go__north",0.989],["none_of_these",0.0104],["drop__key",0.0001]],"round":0,"top":"go__north"},{"actMax":98,"asks":["go__north","drop__key"],"clarifyMax":98,"confidence":0.9881,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":12,"ms":599,"offered":13,"ranked":[["go__north",0.989],["none_of_these",0.0104],["drop__key",0.0001]],"round":1,"top":"go__north"},{"actMax":98,"asks":["go__north","drop__key"],"clarifyMax":98,"confidence":0.9881,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":12,"ms":600,"offered":13,"ranked":[["go__north",0.989],["none_of_these",0.0104],["drop__key",0.0001]],"round":2,"top":"go__north"},{"actMax":99,"asks":["unlock__door","drop__key"],"clarifyMax":99,"confidence":0.9988,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":13,"ms":599,"offered":12,"ranked":[["unlock__door",0.9989],["none_of_these",0.001],["drop__key",0.0]],"round":0,"top":"unlock__door"},{"actMax":99,"asks":["unlock__door","drop__key"],"clarifyMax":99,"confidence":0.9988,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":13,"ms":600,"offered":12,"ranked":[["unlock__door",0.9989],["none_of_these",0.001],["drop__key",0.0]],"round":1,"top":"unlock__door"},{"actMax":99,"asks":["unlock__door","drop__key"],"clarifyMax":99,"confidence":0.9988,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":13,"ms":600,"offered":12,"ranked":[["unlock__door",0.9989],["none_of_these",0.001],["drop__key",0.0]],"round":2,"top":"unlock__door"},{"actMax":97,"asks":["go__north","go__south"],"clarifyMax":97,"confidence":0.9771,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":14,"ms":593,"offered":11,"ranked":[["go__north",0.9792],["none_of_these",0.0185],["go__south",0.001]],"round":0,"top":"go__north"},{"actMax":97,"asks":["go__north","go__south"],"clarifyMax":97,"confidence":0.9771,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":14,"ms":593,"offered":11,"ranked":[["go__north",0.9792],["none_of_these",0.0185],["go__south",0.001]],"round":1,"top":"go__north"},{"actMax":97,"asks":["go__north","go__south"],"clarifyMax":97,"confidence":0.9771,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":14,"ms":593,"offered":11,"ranked":[["go__north",0.9792],["none_of_these",0.0185],["go__south",0.001]],"round":2,"top":"go__north"},{"actMax":99,"asks":["take__crown","examine__crown"],"clarifyMax":99,"confidence":0.9993,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":15,"ms":613,"offered":12,"ranked":[["take__crown",0.9994],["none_of_these",0.0004],["examine__crown",0.0001]],"round":0,"top":"take__crown"},{"actMax":99,"asks":["take__crown","examine__crown"],"clarifyMax":99,"confidence":0.9993,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":15,"ms":620,"offered":12,"ranked":[["take__crown",0.9994],["none_of_these",0.0004],["examine__crown",0.0001]],"round":1,"top":"take__crown"},{"actMax":99,"asks":["take__crown","examine__crown"],"clarifyMax":99,"confidence":0.9993,"kind":"act","landed":true,"lands":{"act":true,"clarify":true,"unknown":false},"line":15,"ms":620,"offered":12,"ranked":[["take__crown",0.9994],["none_of_these",0.0004],["examine__crown",0.0001]],"round":2,"top":"take__crown"}],"sameEveryRound":true,"servedModel":"openjev-MLX-4bit","short":"27B","size":"14 GB","summary":{"act":45,"clarify":0,"error":0,"landed":48,"max_ms":634,"p50_ms":579,"p95_ms":620,"turns":48,"unknown":3},"sure":45,"temperature":"0.85","where":"Local"}],"slider":[0.0,0.01,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09,0.1,0.11,0.12,0.13,0.14,0.15,0.16,0.17,0.18,0.19,0.2,0.21,0.22,0.23,0.24,0.25,0.26,0.27,0.28,0.29,0.3,0.31,0.32,0.33,0.34,0.35,0.36,0.37,0.38,0.39,0.4,0.41,0.42,0.43,0.44,0.45,0.46,0.47,0.48,0.49,0.5,0.51,0.52,0.53,0.54,0.55,0.56,0.57,0.58,0.59,0.6,0.61,0.62,0.63,0.64,0.65,0.66,0.67,0.68,0.69,0.7,0.71,0.72,0.73,0.74,0.75,0.76,0.77,0.78,0.79,0.8,0.81,0.82,0.83,0.84,0.85,0.86,0.87,0.88,0.89,0.9,0.91,0.92,0.93,0.94,0.95,0.96,0.97,0.98,0.99,1.0],"speedCheckMs":500,"sureAt":0.95,"thresholds":{"act":0.75,"clarify":0.4},"when":"2026-10-08T20:42:05"}; diff --git a/docs/godot-jev/index.html b/docs/godot-jev/index.html new file mode 100644 index 0000000..100b5a8 --- /dev/null +++ b/docs/godot-jev/index.html @@ -0,0 +1,593 @@ + + + + + + Parsing With Jev + + + + + + + + + + + +
    + + +
    + + + +
    +
    +
    +
    EXP-02 · godot-jev · three-model run · 2026-10-08
    +

    Three Jevs parse 16 lines of player slang.

    +

    + The Sunken Crypt is a dungeon crawl whose parser sends whatever the player typed to a System One + model, with the moves possible right now as the options of one Choice. We gave three Jevs, a hosted one + and two running on a laptop, the same 16 lines in the same game states, three times each. All three got + the player to the crown without a wrong move. They differ in how sure they were, how often the game had + to ask what the player meant, and how long a turn took on this setup. +

    +
    +
    +
    +
    + + +
    +
    +
    +
    01
    +
    +

    The problem: a parser that understands, and can't invent

    +

    + Classic parser games failed on vocabulary: "I don't know the word 'grab'." Games driven by a + text-generating model fail the other way, inventing doors and items that don't exist. Here the author + writes every room, item and line. Each turn, the moves possible right now become the options of one + Choice, plus "none of these". The model can't pick a door that isn't there, because it isn't an + option. +

    +
    +
    + +
    + The game's hall of pillars: a skeleton guards the north arch, and the transcript shows the parser asking which torch action the player meant. +
    +

    + Fig. 1The game. Room art on the left, the transcript on the right, and under the art a panel that + shows what Jev heard: the outcome, the move, and a confidence meter with ticks at the two thresholds. +

    + + +

    + Fig. 2One turn. Acting applies the move and prints its authored response. Clarifying prints "Did you + mean:" with the two likeliest moves. Unknown prints one of three authored lines such as "Nothing here + answers to that." If the model can't be reached, the game says the parser is offline and nothing happens. +

    +
    +
    + + +
    +
    +
    +
    02
    +
    +

    Hypothesis, and what would confirm it

    +
    +
    +
    + Hypothesis + A Choice over the currently possible moves lets players + type naturally with few dead ends, while + the game stays fully authored, and it's + fast enough to feel like a parser, not a chat. +
    +
    + + + + + + + + + + + + +
    CheckDone whenTypeSafeFlash 9B27BStatus
    +
    +

    + Table 1Checks from the godot-jev README, with this run's numbers at the game's thresholds. Check 1 + needs a corpus of at least 100 player inputs with typos, indirect requests and impossible ones. This run + had 16 lines written to solve the dungeon, so its count of lines landed is not evidence for it yet. +

    +
    +
    + + +
    +
    +
    +
    03
    +
    +

    The three decision models

    +

    + Same API, same game states, same 16 lines. One model is hosted by TypeSafe; two are + Open Jev models running 4-bit on an Apple M5 Pro laptop. + The first run of these lines, on 2026-09-30, had two of the three. Flash 9B is new since then. +

    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    04
    +
    +

    What Jev was asked

    +

    + Each request carries a small state, the player's words, and one Choice whose options are rebuilt every + turn from the world's rules. The option text is the author's ("attack the skeleton with the + shortsword"), and it is all the model has to match the player's words against. Step through the 16 + lines to see the question change as the player moves and picks things up. +

    +
    +
    +
    +
    +

    +
    +
    + State sent with it +
    +
    +
    + The question +

    +
      +
      +
      +
      +

      +
      +
      + + +
      +
      +
      +
      05
      +
      +

      How an answer becomes a turn

      +

      + Jev returns a probability for every option and a confidence in the one it chose. The parser doesn't + just take the choice. At 0.75 or more it acts. From 0.40 it asks + "Did you mean:" and offers the two likeliest moves. Below that, or when the choice is "none of these", + the game says the request isn't possible. A wrong move changes the game's state; a question costs the + player one keypress. +

      +
      +
      + +
      +
      +
      +
      + + + +
      +
      +

      +
      +
      + Jev's answer: its three likeliest options +
      +
        +
        +
        +
        +
        + Act at a confidence of at least + 0.75 +
        + +
        + Ask at a confidence of at least + 0.40 +
        + +
        +
        +
        +
        +
        +

        + Fig. 4Pick an example, or any line, model and round. The bar shows the three likeliest options as + the run recorded them; the pale remainder is every other option together. The outcome at each setting + comes from the game's own parser, replayed over the recorded answer; no new requests are made. The ask + threshold can't be above the act threshold, so moving one past the other carries it along. +

        +
        +
        + + +
        +
        +
        +
        06
        +
        +

        Result: nobody made a wrong move

        +

        At the game's thresholds of 0.75 and 0.40, with differences shown against TypeSafe Jev.

        +
        +
        +
        +
          +
          +
          + + +
          +
          +
          +
          07
          +
          +

          The deltas between the Jevs

          +

          + Same top choice on every turn, different confidence in it. That difference is what the player would + notice: a model that is less sure makes the game ask more often. +

          +
          +
          + +

          7aHow sure each model was

          +
          +
          +
          + + +
          +
          +
          +
          +

          + +

          7bEvery line, side by side

          +
          +
          +
          + + + +
          +
          +
          +
          +
          +

          + Fig. 6Each bar is one model's first-round answer: its three likeliest options, shaded by what they + are. The mark gives the turn at the game's thresholds: ✓ acted on the move the + line means, or came back unknown for the line that means nothing; ? asked, with + the move among the two offered; ▼ a dead end; ✗ a + wrong move. Hover or tap a bar for the options and all three rounds. +

          + +
          + +

          7cHow often each pair agreed

          +
          +

          +
          +
          + + +
          +
          +
          +
          08
          +
          +

          Move the thresholds

          +

          + The thresholds are two numbers in the parser, applied to answers already returned, so they can be + tried after the fact. Raise the act threshold and a less sure model asks about more lines. Raise the + ask threshold and its questions turn into dead ends. +

          +
          +
          +
          +
          + Act at ≥ + + 0.75 + Ask at ≥ + + 0.40 +
          +
          + + +
          +
          +
          +
          + Turns answered with a question, by act threshold +
          +
          +
          + Dead ends, by ask threshold +
          +
          +
          +
          +

          + Fig. 8Panels: one row per round, one cell per line in route order, shaded by what the game did at + these thresholds. Charts: how many of each model's 48 turns ask, or dead-end, as one threshold moves and + the other stays where its slider is; the vertical rule follows the slider. A dead end is a line that + means a move and comes back unknown. +

          +
            +
            +
            + + +
            +
            +
            +
            09
            +
            +

            Speed: one model per lane

            +

            + Check 2 asks for p95 ≤ 500 ms per turn, from the player's Enter to the game's response. Measured in + the game loop around each parse, one request at a time, after one untimed warm-up per model. +

            +
            +
            +
            + Before reading these numbers +

            + They are not a statement of how fast these models are. The two Open Jev figures are what one + laptop measured: 4-bit MLX builds on an Apple M5 Pro running macOS, a desktop operating system and not a + server. Both local models were loaded at the same time, and an editor, a browser and other applications + were running. The same weights on server hardware, at another quantisation or under another runtime + would time differently. +

            +

            + The TypeSafe figure is a round trip over the network from that laptop to a hosted API, so it mixes + their serving time with our connection. Read all three as what a turn cost in this setup, on this day. +

            +
            +
            +
            +
            +

            + +

            9aTime per turn along the route

            +
            +
            +
            +

            + +
            + + + + + + + + + + + + + + +
            Decision modelRunminp50meanp95maxCheck 2
            +
            +

            +
            +
            + + +
            +
            +
            +
            10
            +
            +

            What this does and doesn't show

            +

            Read it as "the mechanism works on three models", not as a measured accuracy.

            +
            +
            +
            +
            + It shows +
              +
            • The same game, parser and thresholds work unchanged on all three decision models.
            • +
            • A Choice over the possible moves maps heavy slang to the right authored move.
            • +
            • When a model is less sure, the thresholds turn that into a question, not a wrong move.
            • +
            • The thresholds can be tried after the fact without asking again.
            • +
            +
            +
            + It doesn't show +
              +
            • 16 lines, in one voice. They were written to solve the dungeon in order for a recording, not sampled from what players type. One line is 6 percentage points.
            • +
            • Three rounds are not three samples. The two local models gave the same answer to a line every round, so Flash 9B's 6 questions are 2 lines asked 3 times.
            • +
            • One impossible request. Check 1 needs many: wrong-room requests, items that aren't there, small talk. Flash 9B put only 0.60 on "none of these" for the one it got.
            • +
            • No typos, no indirect requests, and nothing ambiguous between two moves in the same room.
            • +
            • Short option lists. Between 6 and 13 options a turn, against the 255 a Choice can take.
            • +
            • The lines don't separate the models. All three landed 48 of 48, so this says nothing about which understands best.
            • +
            • How fast the models are. The local timings come from 4-bit builds on one laptop running a desktop OS and other applications; the hosted one includes the network. See the note under Speed.
            • +
            +
            +
            + +
            +
            + + +
            +
            +
            +
            11
            +
            +

            Run details and reproducing it

            +
            +
            +
            +
            + + + + + + + + + + + + + +
            Date2026-10-08, 20:42 UTC
            TypeSafe Jevjev-1.13.0, pinned, hosted API
            Open Jev 27Bopenjev/openjev-MLX-4bit (MLX, 4-bit, 14 GB), T=0.85
            Open Jev Flash 9Bopenjev/OpenJev-Flash-9B-MLX-4bit (MLX, 4-bit, 5 GB), T=1.07
            Open Jev helpershim.py 81a22f1b for both, with each model card's calibration settings
            HardwareApple M5 Pro, 64 GB, macOS 27.2. Both local models loaded at once (ports 3002 and 3003), other applications running.
            Linestools/session.gd: 16, of which one asks for something the author never wrote
            Requests3 rounds. Each line goes to each model in turn, in the same game state, before the route moves on. First answer only, no retries. One untimed warm-up per model.
            ThresholdsAct at 0.75, clarify at 0.40, as in the game
            TimingGodot 4.7.2, headless, around each parse. The loop ticks about every 7 ms, so times fall on steps of that size.
            +
            +
            +
            cd godot-jev
            +export TYPESAFE_API_KEY=sk-... TYPESAFE_DEFAULT_MODEL=jev-1.13.0
            +# Open Jev 27B on :3002 and Flash 9B on :3003
            +godot --headless --path . --import
            +godot --headless --path . \
            +  --script res://eval/compare_backends.gd -- \
            +  --rounds 3 --openjev flash9b=http://127.0.0.1:3003
            +

            + Every number on this page is generated from the recorded run by + docs/tools/build-godot-jev-data.gd, which replays each answer through the game's own + parser and refuses to write if an outcome or a summary differs from what the run saved. Source: + . The written report is + results-2026-10-08.md. +

            +
            +
            +
            +
            +
            + + + + + + + + + + diff --git a/docs/godot-jev/page.css b/docs/godot-jev/page.css new file mode 100644 index 0000000..4f85b38 --- /dev/null +++ b/docs/godot-jev/page.css @@ -0,0 +1,192 @@ +/* godot-jev results page. Shared styles are in ../assets/results.css. */ + +/* Outcomes use the ordinal ramp from site.css: unknown (--r-0), clarify (--r-1), act (--r-2). + In an answer's bar the same three shades mean: "none of these", another move, the move the line means. */ + +.shot { + margin: 0 0 12px; +} +.shot img { + display: block; + width: 100%; + max-width: 900px; + height: auto; + border: 1px solid var(--rule); + border-radius: var(--radius); + box-shadow: var(--shadow-lg); +} + +/* The question: one line's state and options */ +.line-steps { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-bottom: 14px; +} +.line-steps .btn { + min-width: 34px; + padding: 6px 0; + text-align: center; + font-variant-numeric: tabular-nums; +} +.q-grid { + display: grid; + grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.4fr); + gap: 28px; + align-items: start; +} +.q-grid pre { + margin: 8px 0 0; + white-space: pre-wrap; + word-break: break-word; +} +.q-grid .qtext { + margin: 6px 0 12px; +} +@media (max-width: 760px) { + .q-grid { + grid-template-columns: 1fr; + } +} +.options-list { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 6px 16px; + font-size: 0.85rem; +} +.options-list li { + display: flex; + flex-direction: column; + gap: 2px; + padding: 8px 10px; + border: 1px solid var(--rule); + border-radius: 6px; + color: var(--ink-2); +} +.options-list li code { + align-self: flex-start; + font-size: 0.72rem; +} +.options-list li.meant { + border-color: color-mix(in srgb, var(--stamp) 55%, transparent); + background: color-mix(in srgb, var(--stamp) 6%, var(--surface)); + color: var(--ink); +} +.options-list li.none { + grid-column: 1 / -1; + border-style: dashed; + border-color: var(--rule-strong); +} +.options-list .tag { + font-size: 0.7rem; + font-weight: 500; + color: var(--accent-ink); +} +@media (max-width: 520px) { + .options-list { + grid-template-columns: 1fr; + } +} + +/* Explorer: two thresholds */ +.ex-top3 { + list-style: none; + margin: 0 0 10px; + padding: 0; + display: grid; + gap: 4px; + font-size: 0.85rem; + color: var(--ink-2); +} +.ex-top3 li { + display: grid; + grid-template-columns: 12px minmax(0, 1fr) 40px; + gap: 8px; + align-items: center; +} +.ex-top3 i { + width: 10px; + height: 10px; + border-radius: 2px; + background: var(--c); +} +.ex-top3 b { + text-align: right; + font-weight: 600; + color: var(--ink); + font-variant-numeric: tabular-nums; +} +.stackbar .seg.rest { + background: var(--surface-2); + box-shadow: inset 0 0 0 1px var(--rule); +} +.rung .rres { + min-width: 96px; +} +.ladder .rung { + grid-template-columns: 78px minmax(0, 1fr) auto; +} + +/* Line grid: the "means" column needs room for a sentence. */ +@media (min-width: 761px) { + .tg-head, + .tg-row { + grid-template-columns: 26px minmax(0, 1.25fr) minmax(0, 1fr) repeat(3, minmax(0, 0.95fr)); + } +} +.tg-row .t small { + display: block; + color: var(--muted); + font-size: 0.72rem; +} +.tg-row .means { + font-size: 0.78rem; + color: var(--ink-2); + line-height: 1.35; +} +.tg-row .means.nothing { + color: var(--muted); + font-style: italic; +} + +/* Threshold panels: one lane per round, one cell per line. */ +.thresholds { + display: grid; + grid-template-columns: auto minmax(0, 1fr) 54px; + gap: 8px 14px; + align-items: center; + margin-bottom: 14px; +} +.thresholds input { + margin: 0; +} +.thresholds .label { + white-space: nowrap; +} +.threshold-buttons { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 18px; +} +@media (max-width: 560px) { + .thresholds { + grid-template-columns: minmax(0, 1fr) 54px; + } + .thresholds .label { + grid-column: 1 / -1; + } +} +.bp .cells { + grid-template-columns: repeat(16, minmax(0, 1fr)); + gap: 2px; +} +.bp .cell { + font-size: 0.6rem; +} +.bp .lane { + grid-template-columns: 52px minmax(0, 1fr); +} diff --git a/docs/godot-jev/screenshot.png b/docs/godot-jev/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..8cd4b14056860ead0524ce471084ec03f9757dc4 GIT binary patch literal 33800 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 .specimen:last-child { - grid-column: 1 / -1; - } + .timing-note { + margin: 0; + font-size: 0.78rem; + color: var(--muted); } @media (max-width: 860px) { .specimens, .specimen.featured .grid { grid-template-columns: 1fr; } - .specimens > .specimen:last-child { - grid-column: auto; - } } @media (max-width: 480px) { .finding-row { @@ -532,7 +526,7 @@ Experiments Protocol Notebook - Latest results + Latest results GitHub