File size: 27,502 Bytes
c1bec47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
933c910
c1bec47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
(function (global) {
  'use strict';
  const DEFINITIONS = {
    reroute: { number: '01', title: 'One word changed.', short: 'Follow the routing', summary: 'Compare two prompts and the measured expert selections they produced.' },
    quant: { number: '02', title: 'Spot the 4-bit model.', short: 'A blind precision test', summary: 'Read two answers before seeing their precision, timings and checks.' },
    surgery: { number: '03', title: 'What else broke?', short: 'A controlled intervention', summary: 'Inspect a behavior intervention and its effects across the same test cases.' },
    memory: { number: '04', title: 'Remembered, but outdated.', short: 'The correction detective', summary: 'Predict the right answer, then see whether the model used a correction or repeated an old fact.' },
    sound: { number: '05', title: 'A model, heard.', short: 'Listen to real routing', summary: 'Turn measured model activity into a musical score you can inspect.' }
  };
  function assert(condition, message) { if (!condition) throw new Error(message); }
  function validateResults(data) {
    assert(data && data.schema_version === 1, 'This results file uses an unsupported format.');
    assert(data.experiments && typeof data.experiments === 'object', 'Experiment results are missing.');
    for (const key of ['reroute', 'quant', 'surgery', 'memory']) {
      const exp = data.experiments[key];
      assert(exp && Array.isArray(exp.cases) && exp.cases.length > 0, `The ${key} experiment has no cases.`);
      const seen = new Set();
      for (const item of exp.cases) {
        assert(item && typeof item.id === 'string' && item.id.length > 0 && !seen.has(item.id), `Invalid or repeated case ID in ${key}.`);
        seen.add(item.id);
        assert(Array.isArray(item.conditions) && item.conditions.length >= 1 && item.conditions.length <= 12, `Conditions are missing in ${key}/${item.id}.`);
        const conditionIds = new Set();
        for (const condition of item.conditions) {
          assert(condition && typeof condition.id === 'string' && !conditionIds.has(condition.id), `Invalid condition ID in ${key}/${item.id}.`);
          conditionIds.add(condition.id);
          assert(typeof condition.output === 'string', `A captured answer is missing in ${key}/${item.id}.`);
          if (condition.routing && condition.routing.tokens !== undefined) {
            assert(Array.isArray(condition.routing.tokens), `Routing tokens must be an array in ${key}/${item.id}.`);
            for (const frame of condition.routing.tokens) {
              assert(frame && typeof frame.token === 'string' && Array.isArray(frame.experts), 'A routing frame is invalid.');
              assert(frame.experts.every(id => Number.isInteger(id) && id >= 0 && id < (condition.routing.expert_count || 256)), 'An expert index is outside this model.');
              if (frame.weights !== undefined) assert(Array.isArray(frame.weights) && frame.weights.length === frame.experts.length && frame.weights.every(w => Number.isFinite(w) && w >= 0), 'Routing weights are invalid.');
            }
          }
        }
        if (key === 'quant') assert(item.conditions.length === 2, 'The blind comparison needs exactly two answers.');
        if (key === 'memory') assert(Array.isArray(item.choices) && item.choices.length >= 2 && item.choices.every(c => typeof c === 'string'), 'The memory case needs answer choices.');
      }
    }
    return data;
  }
  function stablePairOrder(id, conditions) {
    let hash = 2166136261;
    for (const char of String(id)) hash = Math.imul(hash ^ char.charCodeAt(0), 16777619) >>> 0;
    return hash % 2 ? conditions.slice().reverse() : conditions.slice();
  }
  function isLowBit(condition) { return /(?:^|[^0-9])q?4(?:[^0-9]|$)/i.test(String(condition && condition.quant || '')); }
  function formatMetric(value) {
    if (typeof value === 'number') return Number.isInteger(value) ? value.toLocaleString('en-US') : value.toLocaleString('en-US', { maximumFractionDigits: 5 });
    if (typeof value === 'boolean') return value ? 'Yes' : 'No';
    return typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value ?? '');
  }
  function readableKey(key) { return key.replace(/_/g, ' ').replace(/\bms\b/g, '(ms)').replace(/\bseconds\b/g, '(s)'); }
  function promptFor(item, condition) {
    if (typeof condition.prompt === 'string') return condition.prompt;
    const linked = Array.isArray(item.prompts) && item.prompts.find(p => p.id === condition.prompt_id);
    return linked ? linked.text : typeof item.prompt === 'string' ? item.prompt : '';
  }
  function fixturesFrom(data) {
    const experiments = {};
    for (const [key, exp] of Object.entries(data.experiments)) {
      experiments[key] = {
        method: exp.method,
        cases: exp.cases.map(item => ({ id: item.id, title: item.title, prompt: item.prompt, prompts: item.prompts, question: item.question, ground_truth: item.ground_truth, choices: item.choices, intervention: item.intervention, conditions: item.conditions.map(c => ({ id: c.id, model: c.model, quant: c.quant, prompt: promptFor(item, c), settings: c.settings, intervention: c.intervention })) }))
      };
    }
    return { schema_version: 1, generated_at: data.generated_at, provenance: data.provenance, methodology: data.methodology, experiments };
  }
  function routingFrame(condition, step) {
    const frames = condition.routing && condition.routing.tokens;
    return Array.isArray(frames) && step < frames.length ? frames[step] : null;
  }
  const api = { validateResults, stablePairOrder, isLowBit, formatMetric, promptFor, fixturesFrom, routingFrame };
  if (typeof module !== 'undefined' && module.exports) module.exports = api;
  if (typeof document === 'undefined') return;
  const $ = id => document.getElementById(id);
  let data, active = 'reroute', currentCase, tokenStep = 0, toastTimer;
  const reveals = new Set();
  const stateKey = 'alphabell-experiments-v1';
  let guesses = { quant: {}, memory: {} };
  try { const saved = JSON.parse(localStorage.getItem(stateKey) || 'null'); if (saved && saved.quant && saved.memory) guesses = saved; } catch (_) { /* Private browser storage may be unavailable. */ }
  function node(tag, className, content) {
    const element = document.createElement(tag);
    if (className) element.className = className;
    if (content !== undefined) element.textContent = String(content);
    return element;
  }
  function append(parent, ...children) { for (const child of children) if (child) parent.append(child); return parent; }
  function button(text, action, className) { const el = node('button', className, text); el.type = 'button'; el.addEventListener('click', action); return el; }
  function safeURL(url) {
    if (typeof url !== 'string') return null;
    try { const parsed = new URL(url, location.href); return ['http:', 'https:'].includes(parsed.protocol) ? parsed.href : null; } catch (_) { return null; }
  }
  function link(text, url, className) {
    const href = safeURL(url);
    if (!href) return null;
    const el = node('a', className, text); el.href = href;
    if (new URL(href).origin !== location.origin) { el.target = '_blank'; el.rel = 'noreferrer'; }
    return el;
  }
  function toast(message) { $('toast').textContent = message; $('toast').hidden = false; clearTimeout(toastTimer); toastTimer = setTimeout(() => { $('toast').hidden = true; }, 3000); }
  function save() { try { localStorage.setItem(stateKey, JSON.stringify(guesses)); } catch (_) { toast('This browser cannot save guesses. Your current answer is still recorded for this visit.'); } }
  function download(object, name) {
    const url = URL.createObjectURL(new Blob([JSON.stringify(object, null, 2) + '\n'], { type: 'application/json' }));
    const anchor = document.createElement('a'); anchor.href = url; anchor.download = name; anchor.click(); setTimeout(() => URL.revokeObjectURL(url), 1000);
  }
  function displayValue(parent, value) {
    if (value === undefined || value === null || value === '') return;
    if (typeof value === 'string') { parent.append(node('p', '', value)); return; }
    if (Array.isArray(value)) { const list = node('ul'); value.forEach(item => append(list, node('li', '', typeof item === 'object' ? JSON.stringify(item) : item))); parent.append(list); return; }
    parent.append(node('pre', '', JSON.stringify(value, null, 2)));
  }
  function metrics(object) {
    const list = node('dl', 'metrics');
    for (const [key, value] of Object.entries(object || {})) {
      if (value === undefined || value === null) continue;
      list.append(append(node('div'), node('dt', '', readableKey(key)), node('dd', '', formatMetric(value))));
    }
    return list.children.length ? list : null;
  }
  function setupNav() {
    $('experimentNav').replaceChildren();
    for (const [key, def] of Object.entries(DEFINITIONS)) {
      const anchor = node('a', 'experiment-card'); anchor.href = key === 'sound' ? 'sound.html' : 'index.html?experiment=' + key;
      anchor.dataset.experiment = key;
      append(anchor, node('span', 'card-number', def.number), node('strong', '', def.title), node('small', '', def.short), node('span', 'card-arrow', '↗'));
      if (key !== 'sound') anchor.addEventListener('click', event => { event.preventDefault(); selectExperiment(key, null, true); });
      $('experimentNav').append(anchor);
    }
  }
  function selectExperiment(key, id, push) {
    active = ['reroute', 'quant', 'surgery', 'memory'].includes(key) ? key : 'reroute';
    if (!data) {
      const url = new URL(location.href); url.searchParams.set('experiment', active); url.searchParams.delete('case'); history.pushState(null, '', url);
      return;
    }
    const cases = data.experiments[active].cases;
    currentCase = cases.find(item => item.id === id) || cases[0];
    tokenStep = 0;
    if (push) {
      const url = new URL(location.href); url.searchParams.set('experiment', active); url.searchParams.set('case', currentCase.id); history.pushState(null, '', url);
    }
    document.querySelectorAll('[data-experiment]').forEach(a => { if (a.dataset.experiment === active) a.setAttribute('aria-current', 'page'); else a.removeAttribute('aria-current'); });
    render();
    if (push) $('experiment').scrollIntoView({block:'start',behavior:'smooth'});
  }
  function conditionCard(item, condition, options = {}) {
    const card = node('section', 'condition');
    const heading = node('div', 'condition-heading');
    append(heading, node('h4', '', options.label || condition.label || condition.id));
    if (!options.blind) heading.append(node('span', 'tag', [condition.model, condition.quant].filter(Boolean).join(' · ')));
    const content = node('div', 'condition-content');
    const prompt = promptFor(item, condition);
    if (options.showPrompt && prompt) content.append(node('div', 'prompt', prompt));
    content.append(node('div', 'output', condition.output || '(The model produced no answer.)'));
    if (!options.blind) {
      append(content, metrics(condition.metrics));
      if (condition.checks) { const detail = node('details'); append(detail, node('summary', '', 'Answer checks')); displayValue(detail, condition.checks); content.append(detail); }
      append(content, link('Open measured trace ↗', condition.trace_url, 'condition-link'));
    }
    append(card, heading, content); return card;
  }
  function shareButton() {
    return button('Share this case ↗', async () => {
      const url = new URL(location.href); url.searchParams.set('experiment', active); url.searchParams.set('case', currentCase.id);
      try { await navigator.clipboard.writeText(url.href); toast('Case link copied.'); } catch (_) { toast('Copy the address in your browser to share this case.'); }
    }, 'quiet share');
  }
  function render() {
    const exp = data.experiments[active], def = DEFINITIONS[active], item = currentCase;
    document.title = def.title + ' | alphaBell Experiments';
    const fragment = document.createDocumentFragment(), head = node('div', 'experiment-head');
    const title = node('h2', '', exp.title || def.title); title.id = 'experimentTitle';
    append(head, append(node('div'), node('p', 'eyebrow', 'EXPERIMENT ' + def.number), title, node('p', '', exp.summary || def.summary)));
    const picker = node('label', 'case-picker', 'Choose a recorded case'); const select = node('select'); select.id = 'caseSelect';
    exp.cases.forEach((c, index) => { const option = node('option', '', String(index + 1).padStart(2, '0') + ' · ' + (c.title || c.id)); option.value = c.id; option.selected = c.id === item.id; select.append(option); });
    select.addEventListener('change', () => selectExperiment(active, select.value, true)); append(picker, select); head.append(picker); fragment.append(head);
    const layout = node('article', 'case-layout');
    const top = node('div', 'case-top'), context = append(node('div'), node('h3', '', item.title || item.id));
    if (item.description) context.append(node('p', '', item.description));
    if (item.prompt && active !== 'reroute') context.append(node('div', 'prompt', item.prompt));
    append(top, context, node('span', 'tag', 'Recorded experiment'));
    const body = node('div', 'case-body');
    if (active === 'reroute') renderReroute(body, item);
    if (active === 'quant') renderQuant(body, item);
    if (active === 'surgery') renderSurgery(body, item);
    if (active === 'memory') renderMemory(body, item);
    const controls = node('div', 'controls');
    append(controls, button('Download this case ↓', () => download({ schema_version: data.schema_version, experiment: active, provenance: data.provenance, method: exp.method, case: item }, 'alphabell-' + active + '-' + item.id.replace(/[^a-z0-9_-]/gi, '_') + '.json'), 'quiet'), shareButton());
    body.append(controls);
    const method = node('details'); append(method, node('summary', '', 'How to read this experiment')); displayValue(method, exp.method); displayValue(method, exp.limitations); displayValue(method, exp.reproduce); if (item.provenance) displayValue(method, item.provenance); body.append(method);
    append(layout, top, body); fragment.append(layout); $('experiment').replaceChildren(fragment);
  }
  function renderReroute(body, item) {
    if (item.conditions.every(c => !c.routing)) body.append(node('p', 'annotation', 'Qwen Open uses recurrent heads and dense feed-forward blocks. Compare measured head activity in the full traces; expert grids apply to Flint.'));
    body.append(node('div', 'notice', 'A routing change is a measured change in expert selection. It does not identify what an expert means, or prove that an expert caused a particular answer.'));
    const frames = item.conditions.map(c => c.routing && c.routing.tokens || []), maxFrames = Math.max(...frames.map(f => f.length));
    const columns = node('div', item.conditions.length > 2 ? 'columns three' : 'columns');
    const panels = [];
    if (maxFrames > 0) {
      const controls = node('div', 'token-controls'), label = node('label', '', 'Generated token step'); label.htmlFor = 'routingStep';
      const slider = node('input'); slider.type = 'range'; slider.id = 'routingStep'; slider.min = '0'; slider.max = String(maxFrames - 1); slider.value = String(tokenStep);
      const readout = node('span', 'token-readout'); readout.setAttribute('aria-live', 'polite');
      const update = step => { tokenStep = Math.max(0, Math.min(maxFrames - 1, step)); slider.value = String(tokenStep); readout.textContent = (tokenStep + 1) + ' / ' + maxFrames; panels.forEach(panel => paintRouting(panel, tokenStep)); };
      const previous = button('‹', () => update(tokenStep - 1)); previous.setAttribute('aria-label', 'Previous token');
      const next = button('›', () => update(tokenStep + 1)); next.setAttribute('aria-label', 'Next token');
      slider.addEventListener('input', () => update(Number(slider.value))); append(controls, label, previous, slider, next, readout); body.append(controls);
      body.append(node('p', 'annotation', 'The slider pairs tokens by generated position. Different answers may use different token boundaries and stop at different lengths.'));
      readout.textContent = (tokenStep + 1) + ' / ' + maxFrames;
    }
    for (const condition of item.conditions) {
      const card = conditionCard(item, condition, { showPrompt: true });
      if (condition.routing && Array.isArray(condition.routing.tokens) && condition.routing.tokens.length) {
        const panel = node('div', 'routing-panel'); panel.condition = condition;
        const label = 'Selected experts' + (Number.isInteger(condition.routing.layer) ? ', block ' + condition.routing.layer : '');
        append(panel, node('p', 'routing-label', label), node('code', 'routing-token'), node('div', 'expert-grid'), node('p', 'expert-list'), node('p', 'routing-key', 'Each square is an expert ID. Dark squares were selected in this captured token.'));
        const grid = panel.querySelector('.expert-grid');
        for (let id = 0; id < (condition.routing.expert_count || 256); id++) { const dot = node('span', 'expert-dot'); dot.title = 'Expert ' + id; dot.setAttribute('aria-hidden', 'true'); grid.append(dot); }
        card.querySelector('.condition-content').prepend(panel); panels.push(panel); paintRouting(panel, tokenStep);
      }
      columns.append(card);
    }
    body.append(columns);
    if (item.comparison) { const details = node('details'); append(details, node('summary', '', 'Measured comparison')); displayValue(details, item.comparison); body.append(details); }
  }
  function paintRouting(panel, step) {
    const frame = routingFrame(panel.condition, step), token = panel.querySelector('.routing-token'), list = panel.querySelector('.expert-list');
    token.textContent = frame ? frame.token : 'This answer has ended.';
    const experts = new Set(frame ? frame.experts : []);
    Array.from(panel.querySelector('.expert-grid').children).forEach((dot, id) => dot.classList.toggle('active', experts.has(id)));
    list.textContent = frame ? frame.experts.map((id, index) => '#' + id + (frame.weights ? ' (' + formatMetric(frame.weights[index]) + ')' : '')).join(' · ') : 'No captured frame at this position.';
  }
  function renderQuant(body, item) {
    const ordered = stablePairOrder(item.id, item.conditions), key = 'quant:' + item.id, revealed = reveals.has(key);
    body.append(node('p', 'annotation', 'Both answers use the same case prompt. Model names, precision and measurements stay hidden until you reveal the evidence. Answer order is deterministic for each case.'));
    const columns = node('div', 'columns'); ordered.forEach((condition, index) => columns.append(conditionCard(item, condition, { blind: !revealed, label: revealed ? 'Answer ' + String.fromCharCode(65 + index) + ' · ' + (condition.label || condition.id) : 'Answer ' + String.fromCharCode(65 + index) }))); body.append(columns);
    const panel = node('section', 'guess-panel'); append(panel, node('h4', 'question', 'Which answer came from the 4-bit model?'));
    const choices = node('div', 'choice-row');
    ordered.forEach((condition, index) => {
      const pick = button('Answer ' + String.fromCharCode(65 + index) + ' is 4-bit', () => { guesses.quant[item.id] = condition.id; save(); reveals.add(key); render(); });
      pick.disabled = revealed; pick.setAttribute('aria-pressed', String(guesses.quant[item.id] === condition.id)); choices.append(pick);
    }); panel.append(choices);
    if (!revealed) { const controls = node('div', 'controls'); controls.append(button('Reveal without guessing', () => { reveals.add(key); render(); }, 'quiet')); panel.append(controls); }
    else {
      const low = ordered.find(isLowBit), guess = guesses.quant[item.id], chosen = ordered.find(c => c.id === guess);
      const message = !low ? 'Precision labels are shown above.' : guess ? (isLowBit(chosen) ? 'You spotted it.' : 'The other answer was 4-bit.') : 'The precision is revealed.';
      const result = node('div', 'result-note', message);
      if (low) result.append(node('p', '', 'Answer ' + String.fromCharCode(65 + ordered.indexOf(low)) + ' used ' + low.quant + '. A single answer is not a quality benchmark. Check the full outputs and measured results.'));
      append(panel, result); if (item.checks) displayValue(panel, item.checks);
    }
    let total = 0, correct = 0;
    for (const test of data.experiments.quant.cases) { const guess = guesses.quant[test.id]; if (guess) { total++; const cond = test.conditions.find(c => c.id === guess); if (cond && isLowBit(cond)) correct++; } }
    panel.append(node('p', 'local-tally', 'LOCAL ONLY · ' + correct + ' / ' + total + ' correct guesses saved in this browser. No votes are uploaded.'));
    body.append(panel);
  }
  function renderSurgery(body, item) {
    const intervention = item.intervention || {};
    const notice = node('div', 'notice'); append(notice, node('strong', '', intervention.kind ? readableKey(intervention.kind) : 'Recorded intervention'));
    if (intervention.description) notice.append(node('div', '', intervention.description));
    if (/prompt/i.test(intervention.kind || '')) notice.append(node('div', '', 'This intervention changes the prompt. It does not modify model weights.'));
    if (/activation/i.test(intervention.kind || '')) notice.append(node('div', '', 'This intervention changes activations during execution. It does not rewrite the model weights.'));
    body.append(notice);
    const columns = node('div', item.conditions.length > 2 ? 'columns three' : 'columns'); item.conditions.forEach(c => columns.append(conditionCard(item, c, { showPrompt: true }))); body.append(columns);
    if (item.checks) { const results = node('div', 'result-note'); results.append(node('strong', '', 'Behavior and regression checks')); displayValue(results, item.checks); body.append(results); }
    if (item.comparison) { const results = node('details'); results.append(node('summary', '', 'Intervention comparison')); displayValue(results, item.comparison); body.append(results); }
    body.append(node('p', 'annotation', 'A visible change is a result for this case and intervention. Check the other cases before generalizing about the model. Negative and unchanged results are useful evidence too.'));
  }
  function truthText(item) {
    return typeof item.ground_truth === 'string' ? item.ground_truth : item.ground_truth && (item.ground_truth.answer || item.ground_truth.value) || JSON.stringify(item.ground_truth);
  }
  function renderMemory(body, item) {
    const key = 'memory:' + item.id, revealed = reveals.has(key), selected = guesses.memory[item.id];
    const panel = node('section', 'guess-panel'); append(panel, node('h4', 'question', item.question || 'What should the model answer after the correction?'));
    const choices = node('div', 'choice-row');
    item.choices.forEach(choice => {
      const pick = button(choice, () => { guesses.memory[item.id] = choice; save(); render(); }); pick.disabled = revealed; pick.setAttribute('aria-pressed', String(selected === choice)); choices.append(pick);
    }); panel.append(choices);
    if (!revealed) {
      const reveal = button('Reveal model answers', () => { reveals.add(key); render(); }, 'primary'); reveal.disabled = !selected;
      const controls = node('div', 'controls'); controls.append(reveal); panel.append(controls);
      panel.append(node('p', 'local-tally', 'Choose the answer the model should give. Your choice stays in this browser.'));
      body.append(panel, node('div', 'reveal-blank', 'The captured answers and scoring stay hidden until you make a prediction.'));
    } else {
      const truth = truthText(item), correct = String(selected).trim().toLowerCase() === String(truth).trim().toLowerCase();
      const result = node('div', 'result-note', (correct ? 'Your answer matches the ground truth. ' : 'Compare your prediction with the ground truth. ') + 'Expected answer: ' + truth);
      result.append(node('p', '', 'Your prediction: ' + selected + '. The model answers below are separate measured results.'));
      panel.append(result); body.append(panel);
      const columns = node('div', item.conditions.length > 2 ? 'columns three' : 'columns'); item.conditions.forEach(c => columns.append(conditionCard(item, c, { showPrompt: true }))); body.append(columns);
      if (item.checks) { const check = node('div', 'result-note'); check.append(node('strong', '', 'Correction and recall checks')); displayValue(check, item.checks); body.append(check); }
    }
    body.append(node('p', 'annotation', 'Using an in-context correction and remembering a fact are different tests. The prompts show which information each condition received. These results do not measure a permanent change to model memory.'));
  }
  function renderMethods() {
    const target = $('methodBody'); target.replaceChildren();
    if (data.generated_at) target.append(node('p', '', 'Results prepared: ' + new Intl.DateTimeFormat('en-US', {dateStyle:'medium',timeStyle:'short',timeZone:'America/Los_Angeles'}).format(new Date(data.generated_at)) + ' Pacific time.'));
    displayValue(target, data.methodology);
    const provenance = data.provenance || {};
    if (Array.isArray(provenance.model_sources)) {
      target.append(node('h3', '', 'Source models'));
      const list = node('ul'); provenance.model_sources.forEach(source => { const li = node('li'); append(li, link(source.name || source.url, source.url)); if (source.revision) li.append(document.createTextNode(' · revision ' + source.revision)); list.append(li); }); target.append(list);
    }
    const detail = { ...provenance }; delete detail.model_sources; displayValue(target, detail);
  }
  async function load() {
    $('experiment').replaceChildren(node('div', 'loading', 'Opening measured experiments…'));
    try {
      const response = await fetch('data/results.json', { cache: 'no-cache' });
      if (!response.ok) throw new Error('The results could not be downloaded (HTTP ' + response.status + ').');
      data = validateResults(await response.json());
      renderMethods(); $('evidence').hidden = false;
      const params = new URLSearchParams(location.search); selectExperiment(params.get('experiment'), params.get('case'), false); if (params.has('experiment')) requestAnimationFrame(() => $('experiment').scrollIntoView({block:'start'}));
    } catch (error) {
      $('evidence').hidden = true;
      const panel = node('div', 'error'); panel.setAttribute('role', 'alert');
      append(panel, node('h2', '', 'The measured results could not be opened.'), node('p', '', error.message || 'Please try loading the experiment again.'), button('Try again', load));
      $('experiment').replaceChildren(panel);
    }
  }
  setupNav();
  $('downloadResults').addEventListener('click', () => { if (data) download(data, 'alphabell-experiment-results.json'); });
  $('downloadFixtures').addEventListener('click', () => { if (data) download(fixturesFrom(data), 'alphabell-experiment-prompts.json'); });
  $('resetGuesses').addEventListener('click', () => { guesses = { quant: {}, memory: {} }; reveals.clear(); save(); if (data) render(); toast('Local guesses cleared.'); });
  window.addEventListener('popstate', () => { if (data) { const params = new URLSearchParams(location.search); selectExperiment(params.get('experiment'), params.get('case'), false); } });
  load();
})(typeof window !== 'undefined' ? window : globalThis);