yzzhao commited on
Commit
f150553
·
verified ·
1 Parent(s): 109d6e5

Rebuild portal for matched RoboTwin native policy experiments

Browse files
Files changed (7) hide show
  1. README.md +6 -63
  2. app.js +22 -142
  3. data.json +98 -0
  4. index.html +1 -25
  5. manifest.json +28 -0
  6. sources.json +56 -0
  7. style.css +1 -5
README.md CHANGED
@@ -1,70 +1,13 @@
1
  ---
2
  title: Programmable VLA
3
- emoji: 🦾
4
- colorFrom: yellow
5
- colorTo: green
6
  sdk: static
7
- app_file: index.html
8
  pinned: false
9
- short_description: Agent and VLA behavior, videos and execution traces
10
  ---
11
 
12
- # Programmable VLA · experiment results
13
 
14
- Native robotics experiments with RLE-Bench, Kinex, rosbridge and StarVLA.
15
-
16
- The initial release contains **three valid LIBERO integration episodes** on
17
- `libero/libero-spatial-01`, seed 0, initial-state index 0:
18
-
19
- | Method | Native outcome | Control steps |
20
- | --- | --- | --- |
21
- | B0 · standalone frozen VLA | Success | 77 / 600 |
22
- | B1 · pure Kinex agent | Step limit | 600 / 600 |
23
- | B2 · Kinex + frozen VLA | Success | 79 / 600 |
24
-
25
- The native total-step limit is 605, including five initialization frames.
26
- There is no planner-decision cap. B1/B2 use GPT-6 sol with high reasoning effort.
27
- The policy is the original frozen StarVLA Qwen3-VL-4B OFT checkpoint at step 50000.
28
- No encoder, scientific training or progress monitor is involved.
29
-
30
- One task and one initial state per method support integration and behavior
31
- inspection, not a benchmark-level comparison. B2 delegates the full task in one
32
- accepted VLA call; this episode does not establish a planning benefit. Three
33
- engineering failures before task actions are excluded and recorded in the report.
34
- Earlier pilot experiments used a different protocol and are not pooled here.
35
-
36
- ## Evidence
37
-
38
- The page provides both camera videos, observable agent sessions, VLA invocation
39
- arguments and returns, policy input images, predicted action chunks, and executed
40
- action CSVs. B1's joint commands are in its session; it has no OSC action rows.
41
- Physics runs at 20 Hz; spectator cameras are captured at 10 Hz. The paired
42
- 1440 x 720 publication video encodes every source sample once at 4x simulation
43
- speed. The viewer defaults to 1x for short policy invocations and supports speed
44
- selection and the final native frame. Planning/inference pauses are omitted.
45
- Native success is a separate evaluator result; the VLA does not report semantic
46
- completion. Internal reasoning blocks, credentials, host-local paths and private
47
- simulator state are excluded. Session tool IDs are remapped to local identifiers.
48
-
49
- `data/report.json` is the machine-readable index. `data/manifest.json` records
50
- SHA-256 checksums of all other exported files. Each episode includes the hash of
51
- its retained native evidence archive.
52
-
53
- Saved agent code and memos, full task instructions, paired tool calls and returns,
54
- and audited per-response token usage are available in dedicated tabs. Displayed
55
- USD costs are Standard API-equivalent estimates from official GPT-6 Sol rates
56
- verified on 2026-10-28, not account charges. GPU inference cost is not measured.
57
-
58
- Future training runs are assigned to the W&B team `programmable_vla`, project
59
- `program_VLA`: https://forge.coreweave.com/wandb/programmable_vla
60
- This release contains evaluation evidence only.
61
-
62
- ## Transfer diagnostic
63
-
64
- The [Bridge-to-LIBERO probe](https://rle-bench-programmable-vla.static.hf.space/probes/bridge-20261028/index.html)
65
- uses the released Qwen3-VL-4B OFT Bridge/RT-1 5k checkpoint on spatial-01, seed 0.
66
- The valid episode reached 600 control steps without success. Its video, raw
67
- predictions, conversion traces and native controls are retained separately from
68
- the three integration episodes above. The checkpoint has an unresolved upstream
69
- upload mismatch, and its camera, embodiment and action contract differ from the
70
- LIBERO policy; this single probe is not a benchmark comparison.
 
1
  ---
2
  title: Programmable VLA
3
+ emoji: 🤖
4
+ colorFrom: purple
5
+ colorTo: blue
6
  sdk: static
 
7
  pinned: false
8
+ license: apache-2.0
9
  ---
10
 
11
+ # Programmable VLA
12
 
13
+ Matched RoboTwin behavior experiments: standalone VLA, Kinex, and Kinex with a native policy tool. Sessions, tool calls, policy inputs and outputs, usage, videos, and artifacts.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.js CHANGED
@@ -1,142 +1,22 @@
1
- "use strict";
2
- const $ = selector => document.querySelector(selector);
3
- const esc = value => String(value ?? "").replace(/[&<>"']/g, c => ({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]));
4
- const number = value => Number(value).toLocaleString("en-US");
5
- const money = value => value == null ? "Not reported" : `$${value.toFixed(4)}`;
6
- const code = value => `<pre class="code">${esc(typeof value === "string" ? value : JSON.stringify(value, null, 2))}</pre>`;
7
- const colors = {B0:"#5994bc", B1:"#c7995b", B2:"#756ee4"};
8
- const names = {B0:"Standalone VLA", B1:"Kinex agent", B2:"Agent + VLA"};
9
- const cache = new Map();
10
- let report, current, epoch = 0;
11
- async function load(path) {
12
- if (!cache.has(path)) {
13
- const response = await fetch(path);
14
- if (!response.ok) throw Error(`Cannot load ${path} (${response.status})`);
15
- cache.set(path, await response.json());
16
- }
17
- return cache.get(path);
18
- }
19
- function external(href, label, className = "") { return `<a class="${className}" href="${esc(href)}" target="_blank" rel="noopener">${esc(label)} ↗</a>`; }
20
- function links(path, files) { return `<div class="links">${files.map(([name,file]) => external(`${path}/${file}`, name)).join("")}</div>`; }
21
- function metric(value, label) { return `<div class="metric"><strong>${esc(value)}</strong><span>${esc(label)}</span></div>`; }
22
- function badge(episode) { return `<span class="badge${episode.success ? "" : " fail"}">${episode.success ? "Success" : "Step limit"}</span>`; }
23
- function methods() {
24
- $("#methods").innerHTML = report.episodes.map(e => `<a class="method-card ${current.method === e.method ? "active" : ""}" data-method="${e.method}" style="--variant:${colors[e.method]}" href="#${e.method}/${current.tab}" ${current.method === e.method ? 'aria-current="true"' : ""}><div class="card-header"><span class="method-label"><span class="method-id">${e.method}</span>${names[e.method]}</span>${badge(e)}</div><div class="card-model">${e.method === "B0" ? "Qwen3-VL OFT · frozen" : `GPT-6 Sol / high${e.method === "B2" ? " + OFT" : ""}`}</div><div class="card-stats"><div><strong>${number(e.control_steps)} / 600</strong><span>control steps</span></div><div><strong>${number(e.total_tokens)}</strong><span>total tokens</span></div><div><strong>${money(e.estimated_api_equivalent_usd)}</strong><span>API equiv. · est.</span></div></div></a>`).join("");
25
- }
26
- function tabs(e) {
27
- const items = [["overview","Overview"],["instruction","Instruction"],["session","Session",e.session_messages],["tools","Tool calls",e.tool_calls],["vla","VLA I/O",e.vla_calls],["artifacts","Artifacts",e.artifacts],["usage","Tokens & cost"]];
28
- $("#tabs").innerHTML = items.map(([key,label,count]) => `<a href="#${e.method}/${key}" class="${current.tab === key ? "active" : ""}" ${current.tab === key ? 'aria-current="page"' : ""}>${label}${count != null ? `<small>${count}</small>` : ""}</a>`).join("");
29
- }
30
- function overview(e, detail) {
31
- return `<div class="episode-layout"><div class="video-panel"><div class="video-caption"><span>Third-person + wrist</span><span>1440 × 720</span></div><video id="episode-video" controls playsinline preload="metadata" poster="${e.path}/poster.jpg" src="${e.path}/video.mp4" aria-label="${e.method} paired camera recording"></video><div class="video-toolbar"><label>Speed <select id="playback-speed" aria-label="Video playback speed">${[.5,1,2,4,8].map(speed => `<option value="${speed/4}" ${speed === 1 ? "selected" : ""}>${speed}× simulation</option>`).join("")}</select></label><div><button id="final-frame" class="button">Final frame</button>${external(`${e.path}/video.mp4`,"Video","button")}${external(`${e.path}/terminal.jpg`,"Still","button")}</div></div></div><aside class="panel result-panel"><div class="result-head"><span class="eyebrow">NATIVE RESULT</span>${badge(e)}</div><div class="result-number">${e.control_steps}<span> / 600 control steps</span></div><div class="completion-track"><span style="width:${e.control_steps/6}%"></span></div><dl class="facts"><dt>Termination</dt><dd>${esc(e.native_reason)}</dd><dt>Simulation time</dt><dd>${e.simulation_time_s.toFixed(2)} s</dd><dt>Episode wall time</dt><dd>${e.wall_time_sec.toFixed(1)} s</dd><dt>Model responses</dt><dd>${e.model_responses}</dd><dt>Tool calls</dt><dd>${e.tool_calls}</dd><dt>Accepted VLA calls</dt><dd>${e.vla_calls}</dd><dt>API equiv. · est.</dt><dd>${money(e.estimated_api_equivalent_usd)}</dd></dl></aside></div><div class="metric-grid">${metric(number(detail.accounting.tokens.fresh_input),"Fresh input tokens")}${metric(number(detail.accounting.tokens.cache_read),"Cache read tokens")}${metric(number(detail.accounting.output_tokens),"Output tokens")}${metric(number(e.total_tokens),"Total input + output")}</div><div class="observation"><strong>Observed behavior</strong><span>${esc(e.observation)}</span></div>`;
32
- }
33
- function instruction(e, d) {
34
- return `<section class="panel"><div class="panel-heading"><h2>Task instruction</h2>${links(e.path,[["Full instruction","instruction.txt"]])}</div><p class="instruction">${esc(report.instruction)}</p><details class="instruction-full"><summary>Full recorded agent input</summary>${code(d.instruction)}</details></section>`;
35
- }
36
- function image(src, label) { return `<img src="${esc(src)}" alt="${esc(label)}" data-image="${esc(src)}" loading="lazy">`; }
37
- function blocks(content) {
38
- return content.map(block => block.type === "image" ? image(block.src,"Recorded agent observation") : code(block.type === "text" ? block.text : block)).join("");
39
- }
40
- async function session(e, token) {
41
- const messages = await load(`${e.path}/session.json`);
42
- if (token !== epoch) return;
43
- if (!messages.length) { $("#detail").innerHTML = `<div class="panel empty">B0 has no planner session. ${external(`#${e.method}/vla`,"View its policy invocation")}</div>`; return; }
44
- $("#detail").innerHTML = `<section class="panel"><div class="panel-heading"><h2>Session & observations</h2>${links(e.path,[["Session JSON","session.json"]])}</div><div class="session-toolbar"><select id="event-filter" aria-label="Session filter"><option value="all">All messages</option><option value="assistant">Assistant</option><option value="toolResult">Tool results</option><option value="user">Instruction</option><option value="images">Observations</option></select><input id="event-search" type="search" aria-label="Search session" placeholder="Search text, tool or operation"><span id="event-count"></span></div><div id="session-events"></div><div class="session-more"><button id="more-events" class="button" hidden>Load more</button></div></section>`;
45
- let limit = 30;
46
- function render() {
47
- const query = $("#event-search").value.toLowerCase(), filter = $("#event-filter").value;
48
- const selected = messages.map((message,index) => ({message,index})).filter(({message}) => (filter === "all" || message.role === filter || (filter === "images" && message.content.some(b => b.type === "image"))) && JSON.stringify(message.content).toLowerCase().includes(query));
49
- $("#event-count").textContent = `${selected.length} messages`;
50
- $("#session-events").innerHTML = selected.slice(0,limit).map(({message,index}) => {
51
- const tools = message.content.filter(b => b.type === "toolCall").map(b => b.name).join(", ");
52
- const preview = message.content.find(b => b.type === "text")?.text?.slice(0,130) || tools;
53
- const elapsed = message.timestamp == null ? "" : `+${((message.timestamp-messages[0].timestamp)/1000).toFixed(1)}s`;
54
- return `<details class="event"><summary><span class="event-id">${String(index+1).padStart(3,"0")} · ${elapsed}</span><span class="event-name">${esc(message.role)}${tools ? ` · ${esc(tools)}` : ""}</span><span class="event-preview">${esc(preview)}</span></summary><div class="event-body">${blocks(message.content)}</div></details>`;
55
- }).join("") || '<p class="empty">No matching messages.</p>';
56
- $("#more-events").hidden = selected.length <= limit;
57
- }
58
- $("#event-filter").onchange = $("#event-search").oninput = () => { limit = 30; render(); };
59
- $("#more-events").onclick = () => { limit += 30; render(); }; render();
60
- }
61
- async function tools(e, token) {
62
- const calls = await load(`${e.path}/tool-calls.json`);
63
- if (token !== epoch) return;
64
- $("#detail").innerHTML = `<section class="panel"><div class="panel-heading"><h2>Tool calls · ${calls.length}</h2>${links(e.path,[["Tool I/O JSON","tool-calls.json"]])}</div><div class="session-toolbar"><input id="tool-search" type="search" aria-label="Search tool calls" placeholder="Filter by tool, operation or argument"><span id="tool-count"></span></div><div id="tool-list"></div></section>`;
65
- function render() {
66
- const selected = calls.filter(c => JSON.stringify(c).toLowerCase().includes($("#tool-search").value.toLowerCase()));
67
- $("#tool-count").textContent = `${selected.length} calls`;
68
- $("#tool-list").innerHTML = selected.map(call => `<details class="event"><summary><span class="event-id">${esc(call.id)}</span><span class="event-name">${esc(call.name)}</span><span class="event-preview">${esc(call.arguments.description || call.arguments.action || call.arguments.path || "")}</span></summary><div class="event-body"><h3>Arguments</h3>${code(call.arguments)}<h3>Return</h3>${blocks(call.result)}</div></details>`).join("") || '<p class="empty">No planner tool calls.</p>';
69
- }
70
- $("#tool-search").oninput = render; render();
71
- }
72
- function vla(e, d) {
73
- if (!d.calls.length) return '<section class="panel empty">This method uses native arm and gripper commands. See Tool calls for the recorded actions.</section>';
74
- $("#detail").innerHTML = `<section class="panel"><div class="panel-heading"><h2>Agent → VLA · ${d.calls.length} accepted call</h2>${links(e.path,[["Invocations","vla-calls.json"],["Executed actions","executed-actions.csv"]])}</div>${d.calls.map(call => `<div class="io-grid"><div><h3>Invocation input</h3>${code(call.input)}</div><div><h3>Executor return</h3>${code(call.output)}</div></div>`).join("")}<p class="caption">The native verdict is evaluated separately. The executor returns no semantic completion signal.</p></section><section class="panel section-gap"><div class="panel-heading"><h2>Policy observations → action chunk</h2><select id="policy-step" aria-label="Policy step">${d.policy_inputs.map((input,index) => `<option value="${index}">Chunk ${index+1} · control step ${input.control_step}</option>`).join("")}</select></div><div id="policy-content"></div>${links(e.path,[["Policy inputs","policy-inputs.json"],["Predicted chunks","policy-chunks.json"]])}</section>`;
75
- function render() {
76
- const index = Number($("#policy-step").value), input = d.policy_inputs[index];
77
- $("#policy-content").innerHTML = `<p>${esc(input.instruction)}</p><div class="image-pair">${input.images.map((src,n) => `<figure>${image(src,n ? "Wrist policy input" : "Agent-view policy input")}<figcaption>${n ? "Wrist" : "Agent view"}</figcaption></figure>`).join("")}</div>${code(d.policy_chunks[index])}<p class="caption">8 × 7 unnormalized actions. First six values: OSC controller inputs; last value: gripper prediction.</p>`;
78
- }
79
- $("#policy-step").onchange = render; render();
80
- }
81
- async function artifacts(e, d, token) {
82
- $("#detail").innerHTML = `<section class="panel"><div class="panel-heading"><h2>Saved agent artifacts · ${d.artifacts.length}</h2></div>${d.artifacts.length ? '<div class="artifact-list"><div id="artifact-nav" class="artifact-nav"></div><div id="artifact-content"></div></div>' : '<p class="empty">No agent-created files for this method.</p>'}</section><section class="panel section-gap"><div class="panel-heading"><h2>Episode evidence</h2></div><div class="links">${[["Result metadata","detail.json"],["Instruction","instruction.txt"],["Session","session.json"],["Tool calls","tool-calls.json"],["Usage","usage.json"],["Policy actions","executed-actions.csv"],["Media checks","media.json"],["Frame mapping","frame-mapping.json"],["Third-person source","third_person.mp4"],["Wrist source","wrist.mp4"]].map(([label,file])=>external(`${e.path}/${file}`,label)).join("")}</div></section>`;
83
- let selected = -1;
84
- async function show(index) {
85
- selected = index;
86
- $("#artifact-nav").querySelectorAll("button").forEach((b,n) => b.classList.toggle("active", n === index));
87
- const resource = d.artifacts[index];
88
- $("#artifact-content").innerHTML = '<p class="loading">Loading file…</p>';
89
- const response = await fetch(resource.file);
90
- if (!response.ok) throw Error(`Artifact unavailable: ${resource.name}`);
91
- const text = await response.text();
92
- if (token !== epoch || selected !== index) return;
93
- $("#artifact-content").innerHTML = `<div class="panel-heading"><h3>${esc(resource.name.split("/").at(-1))}</h3>${external(resource.file,"Download","button")}</div>${code(text)}`;
94
- }
95
- if (d.artifacts.length) {
96
- $("#artifact-nav").innerHTML = d.artifacts.map((r,i) => `<button data-artifact="${i}">${esc(r.name)}</button>`).join("");
97
- $("#artifact-nav").onclick = event => { const button = event.target.closest("[data-artifact]"); if (button) show(Number(button.dataset.artifact)).catch(showError); };
98
- await show(0);
99
- }
100
- }
101
- function usage(e, d) {
102
- const u = d.accounting, labels = {fresh_input:"Fresh input",cache_read:"Cache read",cache_write:"Cache write",output:"Output"};
103
- return `<div class="metric-grid">${metric(number(u.input_tokens),"Input tokens · includes cache")}${metric(number(u.output_tokens),"Output tokens")}${metric(number(u.total_tokens),"Total tokens")}${metric(money(u.estimated_api_equivalent_usd),"API equivalent · estimated")}</div><section class="panel"><div class="panel-heading"><h2>Token & cost accounting</h2>${links(e.path,[["Usage JSON","usage.json"]])}</div><div class="table-wrap"><table class="data-table"><thead><tr><th>Category</th><th>Tokens</th><th>USD / 1M</th><th>Estimated USD</th></tr></thead><tbody>${Object.entries(labels).map(([key,label])=>`<tr><td>${label}</td><td>${number(u.tokens[key])}</td><td>$${u.pricing.per_million_tokens[key].toFixed(2)}</td><td>${money(u.cost_breakdown_usd[key])}</td></tr>`).join("")}<tr><td><strong>Total</strong></td><td>${number(u.total_tokens)}</td><td></td><td><strong>${money(u.estimated_api_equivalent_usd)}</strong></td></tr></tbody></table></div><p class="caption">Standard API-equivalent estimate from ${u.response_count} audited responses. Actual billed cost: ${money(u.actual_billed_usd)}. GPU cost: not measured. ${external(u.pricing.source,"Pricing source")} · ${esc(u.pricing.verified_date)}</p><details class="section-gap"><summary>Per-response usage</summary>${code(u.per_response_usage)}</details></section>`;
104
- }
105
- function viewer(src, label) { $("#viewer-image").src = src; $("#image-original").href = src; $("#image-label").textContent = label; $("#image-viewer").showModal(); }
106
- function setupVideo() {
107
- const video = $("#episode-video"); if (!video) return;
108
- video.defaultPlaybackRate = .25; video.playbackRate = .25;
109
- $("#playback-speed").onchange = event => { video.playbackRate = Number(event.target.value); };
110
- $("#final-frame").onclick = () => { video.pause(); viewer(`${current.episode.path}/terminal.jpg`,"Final native frame · third-person + wrist"); };
111
- }
112
- function showError(error) { $("#detail").innerHTML = `<p class="error">${esc(error.message)}</p>`; }
113
- async function route() {
114
- if (!report) return;
115
- if (location.hash === "#protocol") { $("#protocol").open = true; $("#protocol").scrollIntoView(); return; }
116
- const [requestedMethod,requestedTab] = location.hash.slice(1).split("/");
117
- const method = ["B0","B1","B2"].includes(requestedMethod) ? requestedMethod : "B2";
118
- const tab = ["overview","instruction","session","tools","vla","artifacts","usage"].includes(requestedTab) ? requestedTab : "overview";
119
- const e = report.episodes.find(row => row.method === method), token = ++epoch;
120
- current = {method,tab,episode:e}; methods(); tabs(e);
121
- $("#detail").innerHTML = '<p class="loading">Loading evidence…</p>';
122
- try {
123
- const detail = await load(`${e.path}/detail.json`); if (token !== epoch) return;
124
- if (tab === "overview") { $("#detail").innerHTML = overview(e,detail); setupVideo(); }
125
- if (tab === "instruction") $("#detail").innerHTML = instruction(e,detail);
126
- if (tab === "session") await session(e,token);
127
- if (tab === "tools") await tools(e,token);
128
- if (tab === "vla") { const empty = vla(e,detail); if (empty) $("#detail").innerHTML = empty; }
129
- if (tab === "artifacts") await artifacts(e,detail,token);
130
- if (tab === "usage") $("#detail").innerHTML = usage(e,detail);
131
- } catch(error) { if (token === epoch) showError(error); }
132
- }
133
- $("#close-viewer").onclick = () => $("#image-viewer").close();
134
- $("#image-viewer").addEventListener("click", event => { if (event.target === $("#image-viewer")) $("#image-viewer").close(); });
135
- document.addEventListener("click", event => { const img = event.target.closest("[data-image]"); if (img) viewer(img.dataset.image,img.alt); });
136
- window.addEventListener("hashchange", route);
137
- async function main() {
138
- report = await load("data/report.json");
139
- $("#protocol-content").innerHTML = `<p>One task, one initial state per method. Original frozen VLA; no encoder, training or progress monitor. Three pre-action engineering failures are excluded. Earlier pilots use a different protocol.</p><details class="section-gap"><summary>Effective configuration</summary>${code(report.protocol)}</details><p>Sessions contain observable messages, tool I/O and images. Internal reasoning and private simulator state remain excluded. Videos retain every synchronized camera sample and the terminal frame.</p><div class="links">${external("https://huggingface.co/StarVLA/Qwen3-VL-OFT-LIBERO-4in1","Policy checkpoint")}${external("data/report.json","Result index")}${external("data/manifest.json","Checksums")}</div>`;
140
- await route();
141
- }
142
- main().catch(showError);
 
1
+ 'use strict';
2
+ const $=s=>document.querySelector(s),esc=x=>String(x??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
3
+ const num=x=>x==null?'—':Number(x).toLocaleString('en-US'),money=x=>x==null?'—':'$'+Number(x).toFixed(4);
4
+ let data;const cache=new Map();
5
+ async function load(path){if(!cache.has(path)){const r=await fetch(path);if(!r.ok)throw new Error(`${path}: ${r.status}`);cache.set(path,await r.json());}return cache.get(path)}
6
+ const condition=id=>data.conditions.find(x=>x.id===id),episode=(t,c)=>data.episodes.find(e=>e.task===t&&e.condition===c);
7
+ const state=e=>!e?'pending':e.valid===false?'invalid':e.success===true?'success':e.success===false?'failure':e.status||'pending';
8
+ const badge=(e,url)=>`<a class="badge ${state(e)}" href="${url}">${{success:'Success',failure:'Failure',invalid:'Interrupted',pending:'Pending',running:'Running'}[state(e)]||esc(state(e))}</a>`;
9
+ const route=(t,c,tab='overview')=>`#episode/${t}/${c}/${tab}`;
10
+ function matrix(){return `<div class="table-wrap"><table><thead><tr><th>Task · seed 0</th>${data.conditions.map(c=>`<th>${esc(c.label)}</th>`).join('')}<th>Action limit</th></tr></thead><tbody>${data.tasks.map(t=>`<tr data-task="${esc(t.label.toLowerCase())}"><td><strong>${esc(t.label)}</strong></td>${data.conditions.map(c=>`<td>${badge(episode(t.id,c.id),route(t.id,c.id))}</td>`).join('')}<td>${num(t.max_steps)}</td></tr>`).join('')}</tbody></table></div>`}
11
+ function overview(tasksOnly=false){const cards=data.conditions.map(c=>{const all=data.episodes.filter(e=>e.condition===c.id),es=all.filter(e=>e.valid&&typeof e.success==='boolean'),wins=es.filter(e=>e.success).length;return `<article class="card"><h3>${esc(c.label)}</h3><div class="label">${esc(c.model)}</div><div class="value">${es.length?(100*wins/es.length).toFixed(0)+'%':'—'}</div><div class="label">${wins} / ${es.length} completed · ${data.tasks.length} planned</div><div class="meta"><span>${es.length?num(es.reduce((n,e)=>n+(e.input_tokens||0)+(e.output_tokens||0),0)):'—'} tokens</span><span>${(!es.length||es.some(e=>e.api_cost_usd==null))?'—':money(es.reduce((n,e)=>n+(e.api_cost_usd||0),0))} API equivalent</span></div></article>`}).join('');$('#main').innerHTML=`<div class="eyebrow">RoboTwin 2.0 · task03 · single seed</div><h1>${tasksOnly?'Explore tasks':'Agent decisions. Policy actions.'}</h1><p class="muted">Standalone VLA, Kinex, and Kinex with a native policy tool.</p>${tasksOnly?'':`<div class="cards">${cards}</div>`}<section class="panel"><div class="row"><h2>Matched task comparison</h2></div>${matrix()}<p class="note">Same instructions, seeds and native control budgets. Three-task behavior pilot; incomplete episodes are excluded from success-rate denominators.</p></section>`}
12
+ async function detail(tid,cid,tab){const t=data.tasks.find(x=>x.id===tid),c=condition(cid);if(!t||!c){overview();return}const e=episode(tid,cid);const tabs=['overview','instruction','session','tools','policy','artifacts'];$('#main').innerHTML=`<a href="#overview">← All experiments</a><div class="toolbar"><select id="task-select" aria-label="Task">${data.tasks.map(x=>`<option value="${x.id}" ${x.id===tid?'selected':''}>${esc(x.label)}</option>`).join('')}</select><select id="condition-select" aria-label="Baseline">${data.conditions.map(x=>`<option value="${x.id}" ${x.id===cid?'selected':''}>${esc(x.label)}</option>`).join('')}</select>${badge(e,route(tid,cid))}</div><h1>${esc(t.label)}</h1><p class="muted">${esc(c.model)} · ${num(t.max_steps)} action steps · seed 0</p><div class="tabs">${tabs.map(x=>`<a class="${tab===x?'active':''}" href="${route(tid,cid,x)}">${x==='tools'?'Tool calls':x==='policy'?'VLA input / output':x[0].toUpperCase()+x.slice(1)}</a>`).join('')}</div><section id="detail-content"></section>`;$('#task-select').onchange=x=>location.hash=route(x.target.value,cid,tab);$('#condition-select').onchange=x=>location.hash=route(tid,x.target.value,tab);const target=$('#detail-content');if(!e||!e.path){target.innerHTML='<div class="empty">Episode pending</div>';return}const d=await load(e.path+'/detail.json');if(!document.contains(target))return;
13
+ const metrics=`<div class="metrics">${[['Native steps',num(e.control_steps)],['Tool calls',num(e.tool_calls)],['VLA inferences',num(e.vla_calls)],['Input / output',`${num(e.input_tokens)} / ${num(e.output_tokens)}`],['API equivalent',money(e.api_cost_usd)]].map(([k,v])=>`<div class="metric"><b>${v}</b><span>${k}</span></div>`).join('')}</div>`;
14
+ if(tab==='overview')target.innerHTML=`<div class="panel">${e.video?`<video id="episode-video" class="video" controls playsinline preload="metadata" poster="${esc(e.poster||'')}"><source src="${esc(e.video)}" type="video/mp4"></video>`:'<div class="empty">Recording is not available yet</div>'}${metrics}<p class="note">${esc(d.cost_note||'API-equivalent cost is an estimate, not a subscription charge.')} ${esc(d.media_note||'')}</p></div><div class="panel"><h2>Observed behavior</h2><p>${esc(d.observation||'Analysis pending.')}</p><a href="${e.path}/detail.json">Episode record ↗</a></div>`;
15
+ else if(tab==='instruction')target.innerHTML=`<div class="panel"><h2>Task instruction</h2><div class="text">${esc(d.instruction)}</div></div>`;
16
+ else if(tab==='artifacts')target.innerHTML=`<div class="panel files"><h2>Recorded artifacts</h2>${(d.artifacts||[]).map(a=>`<a href="${esc(a.path)}" target="_blank" rel="noopener">${esc(a.name)} <span class="muted">${num(a.bytes)} bytes ↗</span></a>`).join('')}</div>`;
17
+ else if(tab==='session'){const v=await load(e.path+'/session.json');target.innerHTML=`<p class="muted">${num(v.length)} messages · <a href="${e.path}/session.json">Download full session</a></p>${v.map(m=>`<article class="message"><b>${esc(m.role)} ${esc(m.name||'')}</b><div class="text">${esc(m.text)}</div>${m.data?`<details><summary>Recorded payload</summary><pre>${esc(JSON.stringify(m.data,null,2))}</pre></details>`:''}</article>`).join('')||'<div class="empty">Standalone evaluation has no planning-agent session.</div>'}`;}
18
+ else if(tab==='tools'){const v=await load(e.path+'/tool-calls.json');target.innerHTML=`<p class="muted">${num(v.length)} calls · <a href="${e.path}/tool-calls.json">Download tool calls</a></p>${v.map((m,i)=>`<details><summary>${i+1}. ${esc(m.name)} <span class="muted">${esc(m.summary||'')}</span></summary><h3>Input</h3><pre>${esc(JSON.stringify(m.input,null,2))}</pre><h3>Output</h3><pre>${esc(typeof m.output==='string'?m.output:JSON.stringify(m.output,null,2))}</pre></details>`).join('')||'<div class="empty">No agent tool calls</div>'}`;}
19
+ else if(tab==='policy'){const v=await load(e.path+'/policy-calls.json');target.innerHTML=`<p class="muted">${num(v.length)} inferences · <a href="${e.path}/policy-calls.json">Download all input / output records</a></p>${v.map((m,i)=>`<details><summary>${i+1}. ${esc(m.task)} <span class="muted">${m.inference_s?.toFixed(2)||'—'} s</span></summary><div class="images">${(m.images||[]).map(x=>`<a href="${esc(x)}" target="_blank"><img loading="lazy" src="${esc(x)}" alt="Policy input observation"></a>`).join('')}</div><h3>Model output and submitted interface</h3><pre>${esc(JSON.stringify(m.output,null,2))}</pre></details>`).join('')||'<div class="empty">No VLA inference recorded</div>'}`;}}
20
+ function protocol(){$('#main').innerHTML=`<div class="eyebrow">Experiment protocol</div><h1>One task. Three control choices.</h1><div class="panel"><h2>Execution</h2><table><tbody>${[['Suite','RLE-Bench task03 · RoboTwin 2.0 · ALOHA AgileX'],['VLA','StarVLA/Qwen3-VL-OFT-Robotwin2 · steps 40,000'],['Agent','Kinex · GPT-6 Astra · high'],['Policy interface','Native run_policy · one inference per call · agent owns the outer loop'],['Control','25 Hz native joint / gripper frames; original task-specific limits'],['Selection','First three task03 tasks, seed 0; frozen before outcome inspection'],['Scope','Frozen-policy behavior only; no encoder or progress monitor']].map(([k,v])=>`<tr><th>${k}</th><td>${v}</td></tr>`).join('')}</tbody></table></div><p class="muted">This fixed-rate device protocol differs from upstream VLA trajectory execution. Published StarVLA benchmark scores are not a direct comparison. RoboTwin 2.0-Plus is a separate suite and is not included here.</p><a href="sources.json">Source and model identities ↗</a>`}
21
+ async function render(){try{const parts=location.hash.slice(1).split('/');if(parts[0]==='episode')await detail(parts[1],parts[2],parts[3]||'overview');else if(parts[0]==='protocol')protocol();else overview(parts[0]==='tasks')}catch(e){$('#main').innerHTML=`<div class="panel"><h1>Could not load this record</h1><p>${esc(e.message)}</p><a href="#overview">Return to overview</a></div>`}}
22
+ fetch('data.json').then(r=>r.json()).then(x=>{data=x;window.addEventListener('hashchange',render);render()}).catch(e=>{$('#main').textContent=e.message});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data.json ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "tasks": [
3
+ {
4
+ "id": "1",
5
+ "native_id": "robotwin/handover-block-aloha-agilex",
6
+ "label": "Handover block",
7
+ "max_steps": 800,
8
+ "seed": 0
9
+ },
10
+ {
11
+ "id": "2",
12
+ "native_id": "robotwin/place-dual-shoes-aloha-agilex",
13
+ "label": "Place dual shoes",
14
+ "max_steps": 600,
15
+ "seed": 0
16
+ },
17
+ {
18
+ "id": "3",
19
+ "native_id": "robotwin/put-bottles-dustbin-aloha-agilex",
20
+ "label": "Put bottles in dustbin",
21
+ "max_steps": 1700,
22
+ "seed": 0
23
+ }
24
+ ],
25
+ "conditions": [
26
+ {
27
+ "id": "standalone",
28
+ "label": "Standalone VLA",
29
+ "model": "Qwen3-VL-OFT \u00b7 40k"
30
+ },
31
+ {
32
+ "id": "kinex",
33
+ "label": "Kinex",
34
+ "model": "GPT-6 Astra / high"
35
+ },
36
+ {
37
+ "id": "kinex_policy",
38
+ "label": "Kinex + VLA",
39
+ "model": "GPT-6 Astra / high \u00b7 native run_policy"
40
+ }
41
+ ],
42
+ "episodes": [
43
+ {
44
+ "id": "standalone-1",
45
+ "task": "1",
46
+ "condition": "standalone",
47
+ "status": "running"
48
+ },
49
+ {
50
+ "id": "kinex-1",
51
+ "task": "1",
52
+ "condition": "kinex",
53
+ "status": "running"
54
+ },
55
+ {
56
+ "id": "kinex_policy-1",
57
+ "task": "1",
58
+ "condition": "kinex_policy",
59
+ "status": "running"
60
+ },
61
+ {
62
+ "id": "standalone-2",
63
+ "task": "2",
64
+ "condition": "standalone",
65
+ "status": "pending"
66
+ },
67
+ {
68
+ "id": "kinex-2",
69
+ "task": "2",
70
+ "condition": "kinex",
71
+ "status": "pending"
72
+ },
73
+ {
74
+ "id": "kinex_policy-2",
75
+ "task": "2",
76
+ "condition": "kinex_policy",
77
+ "status": "pending"
78
+ },
79
+ {
80
+ "id": "standalone-3",
81
+ "task": "3",
82
+ "condition": "standalone",
83
+ "status": "pending"
84
+ },
85
+ {
86
+ "id": "kinex-3",
87
+ "task": "3",
88
+ "condition": "kinex",
89
+ "status": "pending"
90
+ },
91
+ {
92
+ "id": "kinex_policy-3",
93
+ "task": "3",
94
+ "condition": "kinex_policy",
95
+ "status": "pending"
96
+ }
97
+ ]
98
+ }
index.html CHANGED
@@ -1,25 +1 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <meta name="description" content="Programmable VLA experiments: robot recordings, agent sessions, tool calls and measured usage.">
7
- <meta name="theme-color" content="#756ee4">
8
- <title>Programmable VLA — RLE-Bench</title>
9
- <link rel="stylesheet" href="style.css">
10
- <script src="app.js" defer></script>
11
- </head>
12
- <body>
13
- <a class="skip" href="#main">Skip to results</a>
14
- <header class="site-header"><div class="header-inner"><a class="brand" href="#B2/overview"><span class="brand-icon">P</span><strong>programVLA</strong><span class="brand-divider">/</span><span>experiments</span></a><nav><a href="#B2/overview">Results</a><a href="#protocol">Protocol</a><a href="https://forge.coreweave.com/wandb/programmable_vla" target="_blank" rel="noopener">W&B ↗</a></nav><a class="header-link" href="https://huggingface.co/spaces/RLE-Bench/programmable-vla/tree/main" target="_blank" rel="noopener">Hugging Face ↗</a></div></header>
15
- <main id="main" tabindex="-1">
16
- <section class="hero"><div><p class="eyebrow">AGENT × VISION-LANGUAGE-ACTION</p><h1>Programmable <span>VLA</span></h1><p>Compare execution, delegation and agent behavior.</p></div><div class="hero-facts"><div><strong>3</strong><span>valid episodes</span></div><div><strong>1</strong><span>LIBERO task</span></div><div><strong>0</strong><span>seed / init state</span></div></div></section>
17
- <div id="methods" class="method-cards" aria-label="Select method"></div>
18
- <section id="experiment"><div class="task-heading"><div><p class="eyebrow">LIBERO / SPATIAL-01</p><h2>Bowl between plate and ramekin → center of plate</h2></div><span class="pill">600 control steps</span></div><nav id="tabs" class="detail-nav" aria-label="Episode sections"></nav><div id="detail" aria-live="polite"><p class="loading">Loading recorded results…</p></div></section>
19
- <details id="protocol" class="protocol panel"><summary>Protocol & sources</summary><div id="protocol-content"></div></details>
20
- </main>
21
- <footer><span>RLE-Bench · Programmable VLA</span><div><a href="https://rle-bench-programmable-vla.static.hf.space/probes/bridge-20261028/index.html">Bridge transfer probe ↗</a><a href="data/report.json">Results JSON</a><a href="data/manifest.json">File manifest</a><a href="https://huggingface.co/spaces/RLE-Bench/kinex-benchmark-native-budget-with-robobridge">Kinex benchmark ↗</a></div></footer>
22
- <dialog id="image-viewer"><div class="viewer-toolbar"><span id="image-label">Recorded observation</span><a id="image-original" target="_blank" rel="noopener">Original ↗</a><button id="close-viewer" aria-label="Close image">×</button></div><img id="viewer-image" alt="Recorded robot observation"></dialog>
23
- <noscript><p><a href="data/report.json">Download the result index.</a></p></noscript>
24
- </body>
25
- </html>
 
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="theme-color" content="#756ee4"><title>Programmable VLA — RLE-Bench</title><link rel="stylesheet" href="style.css"><script src="app.js" defer></script></head><body><a class="skip" href="#main">Skip to content</a><header><div class="header-inner"><a class="brand" href="#overview">programmable<span>/</span><b>vla</b></a><nav><a href="#overview">Overview</a><a href="#tasks">Explore tasks</a><a href="#protocol">Protocol</a></nav><a class="external" href="https://huggingface.co/spaces/RLE-Bench/programmable-vla">Hugging Face ↗</a></div></header><main id="main"><p>Loading experiments…</p></main><footer><b>Programmable VLA</b><span>by RLE-Bench</span><a href="data.json">Results JSON</a><a href="manifest.json">Artifact manifest</a></footer></body></html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
manifest.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "files": {
3
+ "app.js": {
4
+ "bytes": 8878,
5
+ "sha256": "8429ad1235aa238a37d76a15ade2e1fbaa4eca1d573c99d6dc371403835480c9"
6
+ },
7
+ "README.md": {
8
+ "bytes": 321,
9
+ "sha256": "f57cb3993c5f2ed4aaa9a51f977138889b77cd9969617b85f49e27ff93dc6df0"
10
+ },
11
+ "sources.json": {
12
+ "bytes": 2232,
13
+ "sha256": "752c59a9e6e36a1aefd2401912d425c155402b0dc90622eff40278f4e4315a83"
14
+ },
15
+ "index.html": {
16
+ "bytes": 901,
17
+ "sha256": "c787bda4dcd8b664d4844e72a39ac6d2855bbbdbbe4b58cb317488b30aee86e3"
18
+ },
19
+ "data.json": {
20
+ "bytes": 1957,
21
+ "sha256": "3faf31ab4a47409fc9502556743b386690e24fbe177feab3eb81c64371490ec8"
22
+ },
23
+ "style.css": {
24
+ "bytes": 5015,
25
+ "sha256": "c9185472e4768289aa8bb101f9031f87988c3d03a7007a8467af90fd750a6117"
26
+ }
27
+ }
28
+ }
sources.json ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "protocol": {
3
+ "tasks": [
4
+ "robotwin/handover-block-aloha-agilex",
5
+ "robotwin/place-dual-shoes-aloha-agilex",
6
+ "robotwin/put-bottles-dustbin-aloha-agilex"
7
+ ],
8
+ "selection_reason": "First three tasks in the existing task03 catalog order; frozen before new outcomes",
9
+ "baselines": [
10
+ "standalone",
11
+ "kinex",
12
+ "kinex_policy"
13
+ ],
14
+ "model": "openai/gpt-6-astra",
15
+ "reasoning_effort": "high",
16
+ "endpoint": "openai/codex",
17
+ "checkpoint": "StarVLA/Qwen3-VL-OFT-Robotwin2",
18
+ "revision": "e9cdfc38a0db968bf3a865bcb542d3d455952226",
19
+ "checkpoint_file": "checkpoints/steps_40000_pytorch_model.pt",
20
+ "seeds": "inherit task03",
21
+ "control_hz": 25,
22
+ "standalone_max_actions": 16,
23
+ "episodes_per_task": 1,
24
+ "retries_of_valid_outcomes": 0,
25
+ "status": "implementation"
26
+ },
27
+ "images": {
28
+ "simulator": "sha256:76f216ea6cb3aa2bbfbc705fe0e6bd18e9952f1af23b1fec3b424308162416a3",
29
+ "task-owner": "sha256:6328c4b92b6d6d18c936b30c317ea0e45e00a9284eff2de7a0b324ce29a49b5e",
30
+ "agent-base": "sha256:a00a70c778ef42dce0b33ff22b1dec8da11309ddac675880b9da20b47aaff4ab",
31
+ "policy-images": "sha256:b68c21e960150c9dabaefa8736b89638e0884d5ca008f36feadf90433e09426f",
32
+ "policy-contract": "sha256:50fef70af05ca772d59f6ddf3d47cfd39b38798a55dc7c2e6b7dfd872a9b238f"
33
+ },
34
+ "sources": {
35
+ "kinex": {
36
+ "revision": "f4ab59e9e93aa2f569d88386799a2bed0670d59c",
37
+ "content_sha256": "d94724a0ddffe36dd713844e78ddfd03069d1f03a9f210e59c39ad7df1b083e3"
38
+ },
39
+ "rosbridge": {
40
+ "revision": "fb5347cdf04404fcecea5ecdff505bb038551082",
41
+ "content_sha256": "b8e1522c9538d42493a22ec1b794a825d65006f8a97454b20d22fd7a0e688c40",
42
+ "note": "Frozen standard task03 provider; Plus development is a separate suite."
43
+ },
44
+ "programvla": {
45
+ "revision": "d119fb57696dace85abe3aedc9edd1db12068516",
46
+ "wheel_sha256": "4e68938c169ab24ca73d8ed2d7122b3fcc8c0f0a94103052f52bb6bc28046d73"
47
+ },
48
+ "rlebench": {
49
+ "revision": "143a3522000d840a493202d247c0105b88a8b717",
50
+ "wheel_sha256": "ba45e87637a50b2eb58440dd7435ec7b25d11a71d68ca18c87fba6161c4bef58"
51
+ },
52
+ "starvla": {
53
+ "revision": "985c2238627481f25476fbee478083812dd82a21"
54
+ }
55
+ }
56
+ }
style.css CHANGED
@@ -1,5 +1 @@
1
- :root{font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#272b34;background:#f7f8fb;font-size:14px;--ink:#272b34;--muted:#687083;--line:#e5e7ef;--purple:#756ee4;--purple-dark:#5d56ca;--lavender:#f0effd;--green:#1e755c;--red:#a14b47;--radius:14px;color-scheme:light;font-synthesis:none}
2
- *{box-sizing:border-box}body{margin:0}a{color:inherit;text-decoration:none}a:hover{color:var(--purple-dark)}button,input,select{font:inherit}button{cursor:pointer;color:inherit;border:0;background:none}button,a,input,select,summary{touch-action:manipulation}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,summary:focus-visible{outline:3px solid #b7b2ff;outline-offset:3px}h1,h2,h3,p{margin:0}h1,h2,h3{letter-spacing:-.035em}h1{font-size:43px;font-weight:680;line-height:1.13}h2{font-size:21px;font-weight:650}h3{font-size:15px;font-weight:650}p{line-height:1.65}code,pre{font-family:ui-monospace,SFMono-Regular,Consolas,monospace}pre{margin:0;white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.65;font-size:12px;font-variant-ligatures:none;max-height:540px;overflow:auto}p,code{overflow-wrap:anywhere}[hidden]{display:none!important}.skip{position:absolute;top:-80px;background:#fff;padding:12px;z-index:100}.skip:focus{top:0}.site-header{background:#fffffff2;border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20;backdrop-filter:blur(12px)}.header-inner{max-width:1440px;margin:auto;padding:0 46px;min-height:76px;display:flex;align-items:center;gap:30px}.brand{display:flex;align-items:center;gap:11px;margin-right:auto;white-space:nowrap;font-size:15px}.brand strong{font-size:22px;letter-spacing:-.8px}.brand-icon{display:grid;place-items:center;background:var(--purple);color:white;width:33px;height:33px;border-radius:9px;font-size:23px;font-weight:750}.brand-divider{color:#d3d4df;font-size:22px}.header-inner nav{display:flex;gap:25px;font-size:12px;color:var(--muted)}.header-link{font-size:11px;border-left:1px solid var(--line);padding-left:25px;color:var(--muted)}main{max-width:1440px;padding:34px 46px 45px;margin:auto;min-height:75vh;outline:none}.eyebrow{font-size:10px;letter-spacing:1.45px;font-weight:650;color:var(--purple);line-height:1.5;text-transform:uppercase}.hero{display:flex;align-items:center;justify-content:space-between;gap:30px;margin:4px 0 30px}.hero .eyebrow{margin-bottom:12px}.hero h1 span{color:var(--purple)}.hero p:not(.eyebrow){color:var(--muted);margin-top:12px}.hero-facts{display:flex;text-align:center;gap:0}.hero-facts>div{padding:0 25px;border-left:1px solid var(--line)}.hero-facts>div:first-child{border:0}.hero-facts strong{display:block;font-size:29px;font-weight:600}.hero-facts span{font-size:11px;color:var(--muted)}.method-cards{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.method-card{position:relative;display:block;border:1px solid var(--line);border-radius:var(--radius);background:#fff;padding:19px 21px;overflow:hidden;box-shadow:0 3px 5px #29274903;text-align:left;transition:border-color .15s,box-shadow .15s}.method-card:before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:var(--variant)}.method-card.active{border-color:var(--variant);box-shadow:0 0 0 1px var(--variant)}.method-card:hover{color:inherit;box-shadow:0 5px 17px #3029660c}.card-header{display:flex;align-items:center;justify-content:space-between;gap:10px}.method-label{font-weight:650;font-size:15px}.method-id{font-size:11px;color:var(--variant);margin-right:8px}.badge{font-size:10px;border-radius:20px;background:#eaf5ef;color:var(--green);padding:4px 9px;white-space:nowrap}.badge.fail{background:#fcf0ed;color:var(--red)}.card-model{font-size:11px;color:var(--muted);margin-top:8px}.card-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:17px}.card-stats strong{display:block;font-size:18px;letter-spacing:-.5px;font-weight:600;font-variant-numeric:tabular-nums}.card-stats span{font-size:10px;color:var(--muted)}.task-heading{display:flex;align-items:center;justify-content:space-between;gap:20px;margin:34px 0 20px}.task-heading .eyebrow{margin-bottom:7px}.task-heading h2{font-size:22px}.pill{font-size:11px;color:var(--muted);border:1px solid var(--line);padding:6px 10px;border-radius:6px;white-space:nowrap}.detail-nav{display:flex;gap:24px;align-items:center;border-bottom:1px solid var(--line);margin-bottom:22px;overflow:auto;white-space:nowrap}.detail-nav a{padding:0 0 13px;font-size:12px;color:var(--muted);border-bottom:2px solid transparent}.detail-nav a.active{color:var(--purple-dark);border-color:var(--purple);font-weight:650}.detail-nav small{font-size:10px;margin-left:5px;background:#eceef5;padding:2px 5px;border-radius:4px}.panel{background:#fff;border:1px solid var(--line);border-radius:var(--radius);padding:22px}.episode-layout{display:grid;grid-template-columns:minmax(0,1fr) 295px;gap:20px;align-items:stretch}.video-panel{border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;background:#fff;display:flex;flex-direction:column}.video-caption,.video-toolbar{display:flex;align-items:center;justify-content:space-between;padding:11px 16px;font-size:11px;color:var(--muted);gap:12px}.video-caption{border-bottom:1px solid var(--line)}video{width:100%;display:block;aspect-ratio:2/1;background:#17191d}.video-toolbar{margin-top:auto}.video-toolbar>div{display:flex;gap:8px}.video-toolbar label{display:flex;align-items:center;gap:8px}.video-toolbar select{font-size:11px}.button{display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--line);background:#fff;border-radius:6px;padding:7px 11px;font-size:11px;gap:6px}.button:hover{background:var(--lavender);color:var(--purple-dark)}select,input{border:1px solid var(--line);border-radius:6px;background:#fff;padding:8px 10px;color:var(--ink);font-size:12px}select{max-width:100%}.result-head{display:flex;justify-content:space-between;align-items:center}.result-number{font-size:35px;letter-spacing:-1px;font-weight:650;margin-top:18px;line-height:1.3}.result-number span{font-size:12px;color:var(--muted);letter-spacing:0;font-weight:400}.completion-track{background:#eceef6;border-radius:6px;height:5px;overflow:hidden;margin:12px 0 20px}.completion-track span{display:block;height:100%;background:var(--purple)}.facts{display:grid;grid-template-columns:1fr auto;gap:10px;font-size:12px;margin:0}.facts dt{color:var(--muted)}.facts dd{margin:0;font-variant-numeric:tabular-nums;text-align:right}.metric-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin:18px 0}.metric{padding:18px 20px;background:#fff;border:1px solid var(--line);border-radius:10px}.metric strong{display:block;font-size:23px;font-weight:600;letter-spacing:-.7px;font-variant-numeric:tabular-nums}.metric span{display:block;font-size:11px;color:var(--muted);margin-top:5px}.observation{display:flex;align-items:baseline;gap:18px;font-size:13px;margin:20px 0 0}.observation span{color:var(--muted)}.panel-heading{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:18px}.panel-heading h2{font-size:18px}.links{display:flex;gap:14px;flex-wrap:wrap;font-size:12px;color:var(--purple-dark)}.instruction{font-size:17px;line-height:1.7;max-width:900px}.instruction-full{margin-top:20px}details summary{cursor:pointer;font-size:13px}details[open]>summary{margin-bottom:14px}.code{background:#f6f7fa;border:1px solid #eef0f5;border-radius:7px;padding:16px}.session-toolbar{display:flex;gap:10px;align-items:center;margin-bottom:18px;flex-wrap:wrap}.session-toolbar input{flex:1;min-width:160px}.session-toolbar span{font-size:11px;color:var(--muted)}.event{border:1px solid var(--line);border-radius:8px;margin-top:10px;overflow:hidden}.event summary{display:flex;align-items:center;gap:12px;list-style:none;padding:13px 15px;font-size:12px;background:#fafbfe}.event summary::before{content:'›';color:var(--purple);font-size:18px}.event[open]>summary{margin-bottom:0}.event[open]>summary::before{transform:rotate(90deg)}.event .event-id{color:var(--muted);font-size:10px;font-variant-numeric:tabular-nums}.event .event-name{font-weight:600}.event .event-preview{color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.event-body{padding:16px;display:grid;gap:14px}.event-body h3{font-size:12px}.event-body img{display:block;max-width:100%;max-height:320px;object-fit:contain;cursor:zoom-in}.session-more{margin-top:18px;text-align:center}.io-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px}.io-grid h3{margin-bottom:10px}.image-pair{display:grid;grid-template-columns:1fr 1fr;gap:15px;margin:16px 0}.image-pair figure{margin:0}.image-pair img{width:100%;max-width:360px;cursor:zoom-in;border-radius:6px}.image-pair figcaption{font-size:11px;color:var(--muted);margin-top:4px}.section-gap{margin-top:20px}.artifact-list{display:grid;grid-template-columns:280px minmax(0,1fr);gap:20px}.artifact-nav{display:flex;flex-direction:column;gap:6px}.artifact-nav button{padding:11px;text-align:left;border:1px solid var(--line);border-radius:6px;font-size:11px;overflow-wrap:anywhere}.artifact-nav button.active{background:var(--lavender);border-color:#c9c5f4;color:var(--purple-dark)}.data-table{width:100%;border-collapse:collapse;font-size:12px}.data-table th{text-align:left;color:var(--muted);font-weight:500;background:#f8f9fc}.data-table td,.data-table th{padding:12px 14px;border-bottom:1px solid var(--line)}.data-table td:not(:first-child),.data-table th:not(:first-child){text-align:right;font-variant-numeric:tabular-nums}.table-wrap{overflow:auto}.caption{font-size:11px;color:var(--muted);margin-top:14px;line-height:1.6}.protocol{margin-top:28px;scroll-margin-top:100px}.protocol summary{font-weight:600}.protocol p{font-size:13px;margin-top:12px;max-width:950px}.protocol .links{margin-top:16px}.loading{padding:45px;text-align:center;color:var(--muted)}.empty{padding:28px;color:var(--muted);font-size:13px;text-align:center}.error{padding:16px;background:#fcf0ed;color:var(--red);border-radius:6px}.gallery{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}.gallery img{width:100%;cursor:zoom-in}.gallery figure{margin:0}.gallery figcaption{font-size:11px;color:var(--muted)}footer{max-width:1440px;margin:auto;padding:24px 46px 35px;border-top:1px solid var(--line);display:flex;justify-content:space-between;gap:20px;font-size:11px;color:var(--muted)}footer>div{display:flex;gap:18px;flex-wrap:wrap}dialog{border:1px solid var(--line);border-radius:12px;padding:18px;max-width:94vw;max-height:94vh}dialog::backdrop{background:#131625bb;backdrop-filter:blur(4px)}.viewer-toolbar{display:flex;gap:25px;align-items:center;margin-bottom:12px;font-size:12px}.viewer-toolbar span{margin-right:auto}.viewer-toolbar button{font-size:25px}dialog>img{max-width:86vw;max-height:77vh;object-fit:contain;display:block}#experiment{scroll-margin-top:96px}
3
- @media(max-width:1000px){.header-inner,main,footer{padding-left:26px;padding-right:26px}.header-inner{gap:20px}.header-link{display:none}.hero-facts>div{padding:0 16px}.episode-layout{grid-template-columns:minmax(0,1fr) 240px}.result-panel{padding:18px}.method-card{padding:16px}.method-label{font-size:14px}.card-stats strong{font-size:16px}.detail-nav{gap:20px}.artifact-list{grid-template-columns:220px minmax(0,1fr)}}
4
- @media(max-width:720px){.header-inner,main,footer{padding-left:18px;padding-right:18px}.header-inner{min-height:65px}.brand>span:last-child,.brand-divider{display:none}.brand strong{font-size:19px}.header-inner nav{gap:14px;font-size:11px}.hero{align-items:flex-start;flex-direction:column;gap:20px;margin-bottom:24px}h1{font-size:35px}.hero-facts{width:100%;justify-content:space-between}.hero-facts>div{flex:1;padding:0 10px}.hero-facts strong{font-size:25px}.method-cards{grid-template-columns:1fr;gap:10px}.method-card{padding:13px 16px}.card-stats{margin-top:10px}.card-model{margin-top:4px}.card-stats strong{font-size:17px}.task-heading{margin-top:25px;gap:10px}.task-heading h2{font-size:19px}.task-heading>.pill{display:none}.detail-nav{gap:19px}.episode-layout{grid-template-columns:1fr}.result-panel .facts{grid-template-columns:1fr 1fr}.result-number{margin-top:12px}.metric-grid{grid-template-columns:1fr 1fr;gap:10px}.metric{padding:14px}.metric strong{font-size:21px}.observation{display:block}.observation strong{display:block;margin-bottom:7px}.panel{padding:17px}.panel-heading{align-items:flex-start}.panel-heading h2{font-size:16px}.io-grid{grid-template-columns:1fr}.artifact-list{grid-template-columns:1fr}.artifact-nav{flex-direction:row;overflow:auto}.artifact-nav button{min-width:170px}.image-pair{gap:10px}.gallery{grid-template-columns:1fr 1fr}.video-toolbar{padding:10px}.video-toolbar>div{gap:5px}.video-toolbar .button{padding:6px}.event summary{padding:10px;gap:8px}.event-preview{display:none}.event-body{padding:12px}.session-toolbar input{min-width:130px}.data-table td,.data-table th{padding:10px 8px}footer{flex-direction:column}main{padding-top:24px}}
5
- @media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}
 
1
+ :root{--bg:#f7f8fb;--ink:#272b34;--muted:#687083;--line:#e5e7ef;--accent:#756ee4;--soft:#efedfc}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.6 Inter,ui-sans-serif,system-ui,sans-serif}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}button,input,select{font:inherit}button,select{cursor:pointer}button,a,select,input{outline-offset:4px}header{background:#fff;border-bottom:1px solid var(--line)}.header-inner{max-width:1280px;padding:21px 32px;margin:auto;display:flex;gap:42px;align-items:center}.brand{font-size:20px;font-weight:700;color:var(--ink);white-space:nowrap}.brand span{color:#b7b4c4;padding:0 11px}.brand b{font-weight:500;color:var(--muted)}nav{display:flex;gap:24px}nav a{color:var(--muted)}.external{margin-left:auto;font-size:13px}main{max-width:1216px;min-height:78vh;margin:auto;padding:40px 0 56px}h1{font-size:34px;line-height:1.2;letter-spacing:-1px;margin:6px 0 12px}h2{font-size:20px;line-height:1.35;margin:0 0 16px}h3{font-size:16px;margin:0 0 8px}p{margin:8px 0 18px}.muted,.subtle{color:var(--muted)}.eyebrow{text-transform:uppercase;letter-spacing:1.6px;font-size:11px;font-weight:700;color:var(--accent)}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;margin:28px 0}.card,.panel{background:#fff;border:1px solid var(--line);border-radius:14px;padding:24px}.card .value{font-size:36px;line-height:1.3;font-weight:650;margin-top:15px}.card .label{font-size:13px;color:var(--muted)}.card .meta{display:flex;gap:20px;margin-top:16px;font-size:13px;color:var(--muted)}.panel{margin:24px 0}.toolbar,.row{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.toolbar{margin:24px 0}.toolbar select,.toolbar input{background:#fff;border:1px solid var(--line);border-radius:8px;padding:10px 14px;color:var(--ink)}.toolbar input{margin-left:auto;max-width:100%}.table-wrap{overflow-x:auto}table{border-collapse:collapse;width:100%;font-size:14px}th{text-align:left;color:var(--muted);font-size:12px;font-weight:600;padding:13px;border-bottom:1px solid var(--line)}td{padding:18px 13px;border-bottom:1px solid var(--line)}tr:last-child td{border-bottom:0}.badge{display:inline-flex;gap:7px;align-items:center;border-radius:20px;padding:5px 11px;font-size:12px;background:#eff1f6;color:#687083;white-space:nowrap}.badge:before{content:'';height:6px;width:6px;border-radius:50%;background:currentColor}.badge.success{color:#27847a;background:#eaf6f2}.badge.failure{color:#b16b40;background:#fff2e8}.badge.invalid{color:#b94c69;background:#fcecf1}.badge.running{color:var(--accent);background:var(--soft)}.tabs{display:flex;gap:24px;border-bottom:1px solid var(--line);margin:26px 0 22px;overflow-x:auto;white-space:nowrap}.tabs a{padding:10px 0;color:var(--muted);border-bottom:2px solid transparent}.tabs a.active{color:var(--accent);border-color:var(--accent)}.video{width:100%;aspect-ratio:2;background:#161922;border-radius:10px;display:block}.empty{padding:80px 20px;text-align:center;color:var(--muted);background:#f2f3f8;border-radius:10px}.metrics{display:grid;grid-template-columns:repeat(5,1fr);gap:15px;margin-top:22px}.metric b{display:block;font-size:21px;font-weight:600}.metric span{font-size:12px;color:var(--muted)}pre{white-space:pre-wrap;overflow-wrap:anywhere;max-height:600px;overflow:auto;background:#f7f8fb;border:1px solid var(--line);border-radius:8px;padding:16px;font:12px/1.65 ui-monospace,monospace}details{background:#fff;border:1px solid var(--line);border-radius:10px;margin:12px 0;padding:16px}summary{cursor:pointer;overflow-wrap:anywhere}.text{white-space:pre-wrap;overflow-wrap:anywhere}.images{display:flex;gap:10px;flex-wrap:wrap;margin:14px 0}.images img{max-width:30%;border-radius:8px}.message{border-left:3px solid #ddd9f8;margin:16px 0;padding:12px 18px;background:#fff;border-radius:0 9px 9px 0}.message b{font-size:12px;color:var(--accent);text-transform:uppercase}.message .text{margin-top:8px;font-size:14px}.files a{display:block;padding:9px 0;border-bottom:1px solid var(--line);overflow-wrap:anywhere}.note{font-size:12px;color:var(--muted);margin-top:12px}footer{max-width:1216px;margin:auto;padding:24px 0;border-top:1px solid var(--line);display:flex;gap:20px;color:var(--muted);font-size:12px}footer b{color:var(--ink)}footer a:first-of-type{margin-left:auto}.skip{position:absolute;left:-1000px}.skip:focus{left:10px;top:10px;z-index:2;background:white;padding:8px}@media(max-width:1270px){main,footer{margin:0 28px}}@media(max-width:760px){.header-inner{padding:16px 20px;gap:14px;flex-wrap:wrap}.brand{font-size:18px}.external{display:none}nav{gap:17px;font-size:13px}main{padding-top:28px;margin:0 18px}h1{font-size:28px}.cards{grid-template-columns:1fr;gap:12px}.card{padding:18px}.card .value{font-size:30px}.panel{padding:18px}.metrics{grid-template-columns:repeat(2,1fr)}.tabs{gap:18px}.toolbar select{max-width:100%}footer{flex-wrap:wrap;margin:0 18px}footer a:first-of-type{margin-left:0}.images img{max-width:100%}.table-wrap{margin:0 -8px}}