Download atomrobot.html from TerryPei/gap-graph-as-policy: direct link, hf CLI and curl.
- Browser
- Download file 30.5 kB
-
https://huggingface.co/TerryPei/gap-graph-as-policy/resolve/main/atomrobot.html
- Command line
-
hf download hf://TerryPei/gap-graph-as-policy/atomrobot.html
-
curl -L -o atomrobot.html https://huggingface.co/TerryPei/gap-graph-as-policy/resolve/main/atomrobot.html
30.5 kB
| <!-- ATOMROBOT — agentic skill graphs for robot execution. Standalone, no affiliations. --> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |
| <title>AtomRobot — every skill earns its next step</title> | |
| <style> | |
| :root{ | |
| --bg:#0f1117; --panel:#171a23; --panel2:#1d212c; --ink:#e8eaf0; --muted:#9aa3b2; | |
| --line:#2a2f3b; --accent:#76b900; --accent2:#3b82f6; --warn:#f59e0b; | |
| --bad:#ef4444; --good:#22c55e; | |
| --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,monospace; | |
| } | |
| *{box-sizing:border-box} | |
| body{margin:0; background:var(--bg); color:var(--ink); | |
| font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif; | |
| line-height:1.65; -webkit-font-smoothing:antialiased} | |
| .wrap{max-width:1080px; margin:0 auto; padding:44px 24px 100px} | |
| header.hero{text-align:center; padding:34px 0 30px; border-bottom:1px solid var(--line); margin-bottom:38px} | |
| .kicker{color:var(--accent); font:700 12.5px/1.5 var(--mono); letter-spacing:.18em; text-transform:uppercase} | |
| h1{font-size:44px; margin:16px 0 10px; letter-spacing:-1.2px; text-wrap:balance} | |
| h1 .hl{color:var(--accent)} | |
| header.hero p{color:var(--muted); max-width:760px; margin:0 auto; font-size:16px} | |
| h2{font-size:23px; margin:52px 0 14px; letter-spacing:-.3px; display:flex; align-items:center; gap:12px} | |
| h2 .num{font:700 12px/1 var(--mono); color:var(--accent); border:1px solid var(--accent); | |
| border-radius:5px; padding:5px 7px; letter-spacing:.06em} | |
| h3{font-size:17px; margin:30px 0 10px; color:var(--ink)} | |
| p{margin:12px 0} | |
| p.lead{color:#c3c9d6} | |
| .legend{color:var(--muted); font-size:13.5px} | |
| b{color:#fff; font-weight:650} | |
| code{font-family:var(--mono); font-size:12.6px; background:var(--panel2); | |
| border:1px solid var(--line); border-radius:5px; padding:1.5px 5px} | |
| table{width:100%; border-collapse:collapse; margin:18px 0; font-size:14px} | |
| th,td{border-bottom:1px solid var(--line); padding:9px 10px; text-align:left} | |
| th{color:var(--muted); font-weight:600; font-size:12.5px; text-transform:uppercase; letter-spacing:.05em} | |
| td.n{text-align:right; font-family:var(--mono)} | |
| .good{color:var(--good)} .bad{color:var(--bad)} | |
| .callout{background:var(--panel); border:1px solid var(--line); border-left:3px solid var(--accent); | |
| border-radius:10px; padding:16px 18px; margin:22px 0; font-size:14.4px} | |
| .callout .t{display:block; font-weight:700; margin-bottom:6px; color:#fff} | |
| .callout.warn{border-left-color:var(--warn)} | |
| /* ---------- animated skill graph ---------- */ | |
| .gwrap{background:linear-gradient(180deg,#141824,#10131b); border:1px solid var(--line); | |
| border-radius:14px; padding:18px 16px 14px; margin:24px 0 10px; overflow-x:auto} | |
| #skillGraph{width:100%; height:auto; min-width:760px; display:block} | |
| .edge{fill:none; stroke:var(--line); stroke-width:2.4; marker-end:url(#ah)} | |
| .edge.fail{stroke:#2b2028; stroke-dasharray:7 6} | |
| .lit{fill:none; stroke:var(--accent); stroke-width:3.4; marker-end:url(#ahg); | |
| filter:drop-shadow(0 0 6px rgba(118,185,0,.55))} | |
| .lit.badpath{stroke:var(--bad); marker-end:url(#ahb); filter:drop-shadow(0 0 6px rgba(239,68,68,.55))} | |
| .gnode rect{fill:var(--panel2); stroke:var(--line); stroke-width:2; transition:all .32s ease} | |
| .gnode .nlab{fill:var(--ink); font:600 14.5px var(--mono); text-anchor:middle; transition:fill .3s} | |
| .gnode .nsub{fill:var(--muted); font:400 11px -apple-system,sans-serif; text-anchor:middle} | |
| .gnode.ours rect{stroke:#3a4a22} | |
| .gnode.active rect{stroke:var(--accent); fill:#1a2413; filter:drop-shadow(0 0 14px rgba(118,185,0,.5))} | |
| .gnode.active .nlab{fill:#c7f06a} | |
| .gnode.settled rect{stroke:#3f5a2a; fill:#161d13} | |
| .gnode.term.active rect{stroke:var(--good); fill:#10241a; filter:drop-shadow(0 0 16px rgba(34,197,94,.55))} | |
| .gnode.abort.active rect{stroke:var(--bad); fill:#241213; filter:drop-shadow(0 0 16px rgba(239,68,68,.55))} | |
| .gate polygon{fill:var(--panel2); stroke:#3c4356; stroke-width:2; transition:all .3s ease} | |
| .gate .glab{fill:var(--muted); font:600 12.5px var(--mono); text-anchor:middle} | |
| .gate .gres{fill:transparent; font:700 15px var(--mono); text-anchor:middle; transition:fill .25s} | |
| .gate.checking polygon{stroke:var(--warn); fill:#241d10; filter:drop-shadow(0 0 12px rgba(245,158,11,.45))} | |
| .gate.pass polygon{stroke:var(--good); fill:#10241a} | |
| .gate.pass .glab{fill:#9fe8b6} .gate.pass .gres{fill:var(--good)} | |
| .gate.fail polygon{stroke:var(--bad); fill:#241213; filter:drop-shadow(0 0 12px rgba(239,68,68,.5))} | |
| .gate.fail .glab{fill:#f3a5a5} .gate.fail .gres{fill:var(--bad)} | |
| .railcap{fill:#6b3b42; font:600 11px var(--mono); text-anchor:middle} | |
| .gbar{display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-top:12px} | |
| .gbtn{background:var(--accent); color:#0b0d12; border:0; border-radius:8px; | |
| padding:9px 16px; font:700 13px var(--mono); cursor:pointer; transition:filter .2s} | |
| .gbtn:hover{filter:brightness(1.12)} .gbtn:disabled{opacity:.45; cursor:default} | |
| .gbtn.alt{background:#3a2226; color:#ffb4b4; border:1px solid #5e3037} | |
| .gstatus{font:600 12.8px var(--mono); color:var(--muted); margin-left:auto; text-align:right} | |
| .gstatus .ok{color:var(--good)} .gstatus .no{color:var(--bad)} .gstatus .go{color:var(--accent)} | |
| .score{background:linear-gradient(180deg,var(--panel),#141821); border:1px solid var(--line); border-radius:14px; padding:26px 24px; margin:20px 0} | |
| .score .row{display:flex; align-items:center; gap:14px; margin:16px 0} | |
| .score .tag{width:160px; flex:0 0 160px; font-size:13.5px} | |
| .score .tag b{display:block; font-size:14px} .score .tag span{color:var(--muted); font-size:12px} | |
| .track{flex:1; height:34px; background:var(--panel2); border-radius:7px; overflow:hidden; position:relative} | |
| .fill{height:100%; width:0; border-radius:7px; transition:width 1.4s cubic-bezier(.2,.8,.2,1)} | |
| .fill.base{background:linear-gradient(90deg,#7a2020,var(--bad))} | |
| .fill.mut{background:linear-gradient(90deg,#3a3f4d,#6b7280)} | |
| .fill.ours{background:linear-gradient(90deg,#3d5c00,var(--accent))} | |
| .pct{position:absolute; right:10px; top:50%; transform:translateY(-50%); font:700 15px/1 var(--mono); color:#fff} | |
| .delta{text-align:center; margin-top:10px; font:700 14px/1.5 var(--mono); color:var(--accent); opacity:0; transition:opacity .6s .9s} | |
| .delta.show{opacity:1} | |
| .cards{display:grid; grid-template-columns:repeat(auto-fit,minmax(170px,1fr)); gap:12px; margin:18px 0} | |
| .card{background:var(--panel); border:1px solid var(--line); border-radius:10px; padding:15px 16px} | |
| .card .lab{color:var(--muted); font-size:11.5px; text-transform:uppercase; letter-spacing:.06em} | |
| .card .val{font:700 27px/1.15 var(--mono); margin-top:6px} | |
| .vids{display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:14px; margin:20px 0} | |
| .vid{background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:10px} | |
| .vid video{width:100%; border-radius:8px; display:block; background:#000} | |
| .vid .cap{font:600 12.5px var(--mono); margin-top:8px} | |
| @media(max-width:640px){ h1{font-size:32px} .wrap{padding:28px 16px 70px} } | |
| </style> | |
| <div class="wrap"> | |
| <header class="hero"> | |
| <div class="kicker">Agentic skill graphs · robot execution</div> | |
| <h1>Atom<span class="hl">Robot</span></h1> | |
| <p>A language instruction is compiled into a <b>typed graph of atomic robot skills</b>. Each skill runs only when the | |
| gate in front of it <b>verifies</b>. Nothing is a black box: when the robot fails, the graph says which atom failed | |
| and why.</p> | |
| </header> | |
| <h2><span class="num">01</span> Watch it execute</h2> | |
| <p class="lead">Boxes are <b>atomic skills</b>. Diamonds are <b>verification gates</b>. The rule is the whole idea: | |
| <b>a skill fires only after the preceding gate passes.</b> Press play — then run the failing branch and watch execution | |
| leave the pipeline the moment a gate says no.</p> | |
| <div class="gwrap"> | |
| <svg id="skillGraph" viewBox="0 0 1120 620" role="img" aria-label="Animated atomic skill graph: observe, perceive target, perceive container, found gate, compute grasp, goto and close, held gate, transport, release, placed gate, done, plus an abort rail"> | |
| <defs> | |
| <marker id="ah" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#2a2f3b"/></marker> | |
| <marker id="ahg" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#76b900"/></marker> | |
| <marker id="ahb" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#ef4444"/></marker> | |
| </defs> | |
| <g class="edges"> | |
| <path id="e1" class="edge" d="M210,103 H248"/> | |
| <path id="e2" class="edge" d="M420,103 H458"/> | |
| <path id="e3" class="edge" d="M630,103 H701"/> | |
| <path id="e4" class="edge" d="M760,158 V238"/> | |
| <path id="e5" class="edge" d="M620,267 H554"/> | |
| <path id="e6" class="edge" d="M380,267 H309"/> | |
| <path id="e7" class="edge" d="M250,294 V402"/> | |
| <path id="e8" class="edge" d="M350,431 H416"/> | |
| <path id="e9" class="edge" d="M590,431 H645"/> | |
| <path id="e10" class="edge" d="M757,431 H786"/> | |
| <path id="f1" class="edge fail" d="M817,103 H1015 V496"/> | |
| <path id="f2" class="edge fail" d="M193,267 H150 V340 H1015 V496"/> | |
| <path id="f3" class="edge fail" d="M700,486 V527 H936"/> | |
| </g> | |
| <g class="gnode" id="n1"><rect x="40" y="76" width="170" height="54" rx="10"/><text class="nlab" x="125" y="100">observe</text><text class="nsub" x="125" y="118">RGB-D capture</text></g> | |
| <g class="gnode ours" id="n2"><rect x="250" y="76" width="170" height="54" rx="10"/><text class="nlab" x="335" y="100">perceive·target</text><text class="nsub" x="335" y="118">detect → pre-rank → VLM</text></g> | |
| <g class="gnode ours" id="n3"><rect x="460" y="76" width="170" height="54" rx="10"/><text class="nlab" x="545" y="100">perceive·box</text><text class="nsub" x="545" y="118">detect → pre-rank → VLM</text></g> | |
| <g class="gnode" id="n4"><rect x="620" y="240" width="170" height="54" rx="10"/><text class="nlab" x="705" y="264">compute_grasp</text><text class="nsub" x="705" y="282">6-DoF pose</text></g> | |
| <g class="gnode" id="n5"><rect x="380" y="240" width="170" height="54" rx="10"/><text class="nlab" x="465" y="264">goto + close</text><text class="nsub" x="465" y="282">planned motion</text></g> | |
| <g class="gnode" id="n6"><rect x="180" y="404" width="170" height="54" rx="10"/><text class="nlab" x="265" y="428">transport</text><text class="nsub" x="265" y="446">collision-free</text></g> | |
| <g class="gnode" id="n7"><rect x="420" y="404" width="170" height="54" rx="10"/><text class="nlab" x="505" y="428">release</text><text class="nsub" x="505" y="446">open gripper</text></g> | |
| <g class="gnode term" id="n8"><rect x="786" y="404" width="152" height="54" rx="10"/><text class="nlab" x="862" y="428">done</text><text class="nsub" x="862" y="446">reward 1.0</text></g> | |
| <g class="gnode abort" id="nab"><rect x="936" y="496" width="152" height="54" rx="10"/><text class="nlab" x="1012" y="520">abort</text><text class="nsub" x="1012" y="538">arm → home</text></g> | |
| <g class="gate" id="g1"><polygon points="760,48 817,103 760,158 703,103"/><text class="glab" x="760" y="100">found?</text><text class="gres" x="760" y="124"></text></g> | |
| <g class="gate" id="g2"><polygon points="251,212 309,267 251,322 193,267"/><text class="glab" x="251" y="264">held?</text><text class="gres" x="251" y="288"></text></g> | |
| <g class="gate" id="g3"><polygon points="701,376 757,431 701,486 645,431"/><text class="glab" x="701" y="428">placed?</text><text class="gres" x="701" y="452"></text></g> | |
| <text class="railcap" x="1015" y="212">abort rail</text> | |
| </svg> | |
| <div class="gbar"> | |
| <button class="gbtn" id="gRun">▶ run · gates pass</button> | |
| <button class="gbtn alt" id="gFail">⚠ run · perception fails</button> | |
| <div class="gstatus" id="gStatus">ready</div> | |
| </div> | |
| </div> | |
| <p class="legend">The two green nodes are the perception atoms — the most expensive part of the pipeline, and the one | |
| this project rebuilt (§03).</p> | |
| <h2><span class="num">02</span> Why gate every atom</h2> | |
| <p class="lead">An end-to-end policy that fails gives you one bit of information: it failed. A gated graph gives you the | |
| node, the inputs it saw, and the branch it took. That turns debugging a robot into reading a trace.</p> | |
| <p>The gates are what make the failure legible. A run that dies at <code>found?</code> stops after about <b>51 control | |
| steps</b> — the arm has not moved, because the graph refused to hand an unverified object pose to the motion planner. A | |
| run that clears every gate executes roughly <b>750 steps</b>. You can classify a trial by its step count alone.</p> | |
| <div class="vids"> | |
| <div class="vid"><video src="media/demo/gap_fail_abort.mp4" autoplay loop muted playsinline></video> | |
| <div class="cap"><span class="bad">gate failed</span> · perception could not verify → abort, arm home</div></div> | |
| <div class="vid"><video src="media/demo/gap_success_reward1.mp4" autoplay loop muted playsinline></video> | |
| <div class="cap"><span class="good">all gates passed</span> · grasp → transport → release, reward 1.0</div></div> | |
| </div> | |
| <h2><span class="num">03</span> Making the perception atom cheap</h2> | |
| <p class="lead">The perception atom dominated cost. It proposed ~8 candidate boxes and ran a single-elimination | |
| tournament of vision-language comparisons — one model call per elimination, several times per task.</p> | |
| <p>The fix is to rank candidates against the target <b>name</b> first, keep the top few, and skip the tournament | |
| entirely when the leader is unambiguous. Same interface, same gates, far less compute:</p> | |
| <table> | |
| <thead><tr><th>Task family</th><th class="n">calls / trial · before</th><th class="n">calls / trial · after</th><th class="n">reduction</th></tr></thead> | |
| <tbody> | |
| <tr><td>Tabletop objects</td><td class="n">14.4</td><td class="n"><b class="good">2.6</b></td><td class="n">5.5×</td></tr> | |
| <tr><td>Spatial referring</td><td class="n">16.3</td><td class="n"><b class="good">1.9</b></td><td class="n">8.6×</td></tr> | |
| <tr><td>Goal-directed</td><td class="n">14.0</td><td class="n"><b class="good">1.3</b></td><td class="n">10.8×</td></tr> | |
| <tr style="background:rgba(118,185,0,.08)"><td><b>Average</b></td><td class="n"><b>14.9</b></td><td class="n"><b class="good">1.9</b></td><td class="n"><b>≈8×</b></td></tr> | |
| </tbody> | |
| </table> | |
| <div class="callout"> | |
| <span class="t">The honest headline: same accuracy, a fraction of the compute</span> | |
| Across <b>90 matched trials</b> the two configurations score <b>identically — 36/90 each (40.0%)</b>. Pre-ranking is | |
| not an accuracy trick; it is an <b>efficiency</b> result: the same behaviour at roughly <b>8× fewer</b> vision-language | |
| calls and ~2.3× faster wall-clock. Where perception is the <i>binding constraint</i>, it becomes an accuracy result | |
| too — on a deliberately weak perception backend the same change lifted one task from <b>6.7% → 80.0%</b>. | |
| </div> | |
| <h2><span class="num">04</span> Where the pipeline works — and where it stops</h2> | |
| <table> | |
| <thead><tr><th>Task family (10 tasks × 3 trials each)</th><th class="n">baseline</th><th class="n">+ pre-ranking</th></tr></thead> | |
| <tbody> | |
| <tr><td>Tabletop pick-and-place</td><td class="n"><b>28/30 = 93.3%</b></td><td class="n"><b class="good">29/30 = 96.7%</b></td></tr> | |
| <tr><td>Spatial referring expressions</td><td class="n">3/30 = 10.0%</td><td class="n">1/30 = 3.3%</td></tr> | |
| <tr><td>Goal-directed manipulation</td><td class="n">5/30 = 16.7%</td><td class="n">6/30 = 20.0%</td></tr> | |
| <tr style="background:rgba(118,185,0,.08)"><td><b>All 90 trials</b></td><td class="n"><b>36/90 = 40.0%</b></td><td class="n"><b>36/90 = 40.0%</b></td></tr> | |
| </tbody> | |
| </table> | |
| <p class="legend">Read the collapse outside pick-and-place carefully — the traces show <b>three different causes</b>, and | |
| only one is a real ceiling.</p> | |
| <h3>① A real limit: the skill library</h3> | |
| <p>Opening a drawer, turning a knob, pushing a plate — these are not pick-and-place. Those tasks ran their full trials | |
| with <b>zero perception aborts</b> and the <i>longest</i> executions measured: <b>2680–3297 control steps</b> against | |
| ~720 for a success. The graph was built, perception verified, and the arm genuinely tried. The manipulation atoms | |
| simply do not exist. That is a library boundary, and it is visible precisely because the gates all passed.</p> | |
| <h3>② A fixable bug: the qualifier gets dropped</h3> | |
| <p>For "the bowl <i>between the plate and the ramekin</i>", graph generation emits the target as just | |
| <code>"black bowl"</code> — and the scene contains two identical bowls. No downstream atom can recover the missing | |
| words. Worse, confident pre-ranking then locks onto the <i>salient</i> bowl rather than the <i>referred</i> one, which | |
| is why that column moves the wrong way. The fix under test scores candidates by <b>relation geometry</b> against | |
| detected anchor objects, at zero extra model calls.</p> | |
| <h3>③ Sampling, not capability</h3> | |
| <p>One graph is sampled per task. A single malformed sample zeroes all three trials — one observed failure was a graph | |
| whose error branch routed to a label its own edge map never declared, so it died before the arm moved at all. That is | |
| graph-generation variance, not robot capability, and it is reported separately rather than folded into the score.</p> | |
| <div class="callout warn"> | |
| <span class="t">Scope, stated plainly</span> | |
| 3 trials per task, one graph sample per task, a free-tier model behind the perception atom, and a fourth task family | |
| (long-horizon, multi-step) still running. Small numbers are labelled as such. Every trial counted here actually ran: | |
| runs killed by quota, connectivity, or out-of-memory are discarded and re-run, never scored as failures. | |
| </div> | |
| <h2><span class="num">05</span> The reproduction — matching the published baseline, for free</h2> | |
| <p class="lead">The reference implementation reports LIBERO grocery success around <b>~0.95</b>. Earlier reproduction | |
| attempts fell short only because they ran an <b>open perception backend</b>. Point the <code>perceive</code> atom at | |
| the framework's <b>default</b> perception model — <b>Gemini-3.1-flash-lite</b>, on Google's own endpoint — and the | |
| <b>unmodified baseline</b> reproduces that level. We ran the entire thing on a <b>free</b> API key.</p> | |
| <div class="score" id="repScore"> | |
| <div class="row"> | |
| <div class="tag"><b style="color:var(--muted)">Our free reproduction</b><span>unmodified baseline · all 10 tasks</span></div> | |
| <div class="track"><div class="fill mut" data-w="93.3"></div><div class="pct" id="pRep">0.0%</div></div> | |
| </div> | |
| <div class="row"> | |
| <div class="tag"><b style="color:var(--accent)">+ our pre-ranking</b><span>same tasks, same backend</span></div> | |
| <div class="track"><div class="fill ours" data-w="96.7"></div><div class="pct" id="pOurs">0.0%</div></div> | |
| </div> | |
| <div class="delta" id="dRep">baseline 28/30 = 93.3% → ours 29/30 = 96.7% · ~5.5× fewer perception calls (14.4 → 2.6 per trial, measured) · free API key</div> | |
| </div> | |
| <table> | |
| <thead><tr><th>Grocery task · 3 trials each</th><th class="n">baseline</th><th class="n">+ pre-ranking</th><th class="n">ours calls/trial</th></tr></thead> | |
| <tbody> | |
| <tr><td>0 · alphabet soup can</td><td class="n">2/3</td><td class="n">2/3</td><td class="n">3.3</td></tr> | |
| <tr><td>1 · cream cheese box</td><td class="n">3/3</td><td class="n">3/3</td><td class="n">4.0</td></tr> | |
| <tr><td>2 · dark salad-dressing bottle</td><td class="n">3/3</td><td class="n">3/3</td><td class="n">2.7</td></tr> | |
| <tr><td>3 · red bbq sauce bottle</td><td class="n">3/3</td><td class="n">3/3</td><td class="n">2.0</td></tr> | |
| <tr><td>4 · ketchup bottle</td><td class="n">3/3</td><td class="n">3/3</td><td class="n">2.0</td></tr> | |
| <tr style="background:rgba(118,185,0,.06)"><td>5 · tomato sauce can</td><td class="n"><span class="bad">2/3</span></td><td class="n"><span class="good"><b>3/3</b></span></td><td class="n">1.3</td></tr> | |
| <tr><td>6 · butter box</td><td class="n">3/3</td><td class="n">3/3</td><td class="n">2.0</td></tr> | |
| <tr><td>7 · milk carton</td><td class="n">3/3</td><td class="n">3/3</td><td class="n">2.7</td></tr> | |
| <tr><td>8 · chocolate pudding box</td><td class="n">3/3</td><td class="n">3/3</td><td class="n">3.3</td></tr> | |
| <tr><td>9 · orange juice</td><td class="n">3/3</td><td class="n">3/3</td><td class="n">2.7</td></tr> | |
| <tr style="background:rgba(118,185,0,.10)"><td><b>Aggregate · all 10 tasks</b></td><td class="n"><b>28/30 = 93.3%</b></td><td class="n"><b class="good">29/30 = 96.7%</b></td><td class="n"><b>2.6</b> <span class="legend">vs 14.4 base</span></td></tr> | |
| </tbody> | |
| </table> | |
| <p class="legend">Same-scene A/B across the whole gate. On this strong backend the baseline already sits near the | |
| ceiling, so accuracy headroom is small — only task 5 moves. The win here is <b>parity-or-better accuracy at ~5.5× | |
| fewer perception calls</b>: it prunes ~8 detector candidates to 3 before the tournament, and skips the container | |
| comparison outright when the leader is unambiguous.</p> | |
| <div class="callout warn"> | |
| <span class="t">The honest scope</span> | |
| No budget for paid API, so everything runs on a <b>free key with a tight daily quota</b>, spread over several days as | |
| it refilled. That means <b>3 trials/task</b> against the reference's ~30–50 — a <b>sanity-scale reproduction, not a | |
| full-scale eval</b>. What it is not is fabricated: every trial is counted, and runs killed by quota, connectivity, or | |
| memory are discarded and re-run, never scored as failures. | |
| </div> | |
| <h2><span class="num">06</span> The severe case — when the abort <i>is</i> the failure</h2> | |
| <p class="lead">On a strong perception backend the gates rarely trip, so pre-ranking buys efficiency. Put the same | |
| graph on a <b>deliberately constrained</b> backend, where the perception atom genuinely cannot resolve the object, and | |
| the same change stops being an optimisation and starts being the difference between moving and not moving.</p> | |
| <div class="score" id="sevScore"> | |
| <div class="row"> | |
| <div class="tag"><b style="color:var(--bad)">Baseline</b><span>perception aborts</span></div> | |
| <div class="track"><div class="fill base" data-w="6.7"></div><div class="pct" id="pSevB">0.0%</div></div> | |
| </div> | |
| <div class="row"> | |
| <div class="tag"><b style="color:var(--accent)">+ our pre-ranking</b><span>perception resolves</span></div> | |
| <div class="track"><div class="fill ours" data-w="80"></div><div class="pct" id="pSevO">0.0%</div></div> | |
| </div> | |
| <div class="delta" id="dSev">▲ +73.3 points · 2/30 → 24/30 trials · 30-trial ablation, p ≪ 0.001</div> | |
| </div> | |
| <div class="cards"> | |
| <div class="card"><div class="lab">Success</div><div class="val"><span style="color:var(--bad)">6.7%</span> <span style="color:var(--muted);font-size:18px">→</span> <span style="color:var(--accent)">80.0%</span></div></div> | |
| <div class="card"><div class="lab">Pre-ranking fired</div><div class="val" style="color:var(--accent)">90×</div></div> | |
| <div class="card"><div class="lab">Baseline failures = perception</div><div class="val" style="color:var(--bad)">100%</div></div> | |
| <div class="card"><div class="lab">Avg control steps</div><div class="val">51 <span style="color:var(--muted);font-size:18px">→</span> 746</div></div> | |
| </div> | |
| <p class="legend"><b>Why the step count is the proof.</b> A baseline trial dies after ~<b>51</b> steps: the graph | |
| reached <code>perceive</code>, the <code>found?</code> gate refused it, and the arm never moved. With pre-ranking the | |
| same task runs ~<b>746</b> steps — the gate passes, so the arm actually grasps and places. Independent failure | |
| attribution agrees: <b>100% of baseline failures (28/28) are perception</b>, exactly the atom this targets.</p> | |
| <div class="callout warn"> | |
| <span class="t">Read §06 as an ablation, not a headline</span> | |
| These numbers come from a <b>controlled module-on / module-off ablation</b> on a deliberately weak perception | |
| backend, which is why the baseline here is 6.7% rather than §05's 93.3%. Everything else is identical, so the gap | |
| between the two rows isolates the module's effect. Read <b>the difference</b>, not the absolute values — and read | |
| §04's dead heat as the fair verdict on strong backends. | |
| </div> | |
| <p class="legend" style="margin-top:34px; border-top:1px solid var(--line); padding-top:16px"> | |
| Built on an open-source typed skill-graph framework; the perception pre-ranking, the measurements, and the failure | |
| attribution are our own. All numbers come from real benchmark executions on 2×RTX 4090 — none are estimated. | |
| </p> | |
| </div> | |
| <script> | |
| (function(){ | |
| var svg=document.getElementById('skillGraph'); if(!svg) return; | |
| var status=document.getElementById('gStatus'), | |
| bRun=document.getElementById('gRun'), bFail=document.getElementById('gFail'); | |
| var overlays={}, timers=[], running=false; | |
| // build an animated overlay for every static edge | |
| svg.querySelectorAll('.edges path').forEach(function(p){ | |
| var o=p.cloneNode(); o.id=''; o.setAttribute('class','lit'); | |
| var L=p.getTotalLength(); | |
| o.style.strokeDasharray=L; o.style.strokeDashoffset=L; o.style.opacity=0; | |
| p.parentNode.appendChild(o); overlays[p.id]={el:o,len:L}; | |
| }); | |
| function reset(){ | |
| timers.forEach(clearTimeout); timers=[]; | |
| Object.keys(overlays).forEach(function(k){ | |
| var o=overlays[k]; o.el.style.transition='none'; o.el.style.strokeDashoffset=o.len; | |
| o.el.style.opacity=0; o.el.classList.remove('badpath'); | |
| }); | |
| svg.querySelectorAll('.gnode').forEach(function(n){n.classList.remove('active','settled');}); | |
| svg.querySelectorAll('.gate').forEach(function(n){ | |
| n.classList.remove('checking','pass','fail'); n.querySelector('.gres').textContent=''; | |
| }); | |
| void svg.getBoundingClientRect(); | |
| } | |
| function at(ms,fn){ timers.push(setTimeout(fn,ms)); } | |
| function say(html){ status.innerHTML=html; } | |
| var NODE_MS=560, EDGE_MS=460, GATE_MS=780; | |
| function play(fail){ | |
| if(running) return; running=true; reset(); | |
| bRun.disabled=true; bFail.disabled=true; | |
| var t=260; | |
| // step list: n=node, e=edge, g=gate | |
| var steps=[{n:'n1',s:'observe · capturing scene'}, | |
| {e:'e1'},{n:'n2',s:'perceive · ranking candidates'}, | |
| {e:'e2'},{n:'n3',s:'perceive · locating container'}, | |
| {e:'e3'},{g:'g1',pass:!fail,s:'verify found?'}]; | |
| if(fail){ | |
| steps=steps.concat([{e:'f1',bad:1},{n:'nab',bad:1,s:'gate failed → abort, arm home'}]); | |
| }else{ | |
| steps=steps.concat([{e:'e4'},{n:'n4',s:'compute_grasp · 6-DoF pose'}, | |
| {e:'e5'},{n:'n5',s:'goto + close gripper'}, | |
| {e:'e6'},{g:'g2',pass:true,s:'verify held?'}, | |
| {e:'e7'},{n:'n6',s:'transport · collision-free'}, | |
| {e:'e8'},{n:'n7',s:'release'}, | |
| {e:'e9'},{g:'g3',pass:true,s:'verify placed?'}, | |
| {e:'e10'},{n:'n8',s:'done · reward 1.0'}]); | |
| } | |
| steps.forEach(function(st){ | |
| if(st.e){ | |
| at(t,function(){ | |
| var o=overlays[st.e]; if(!o) return; | |
| if(st.bad) o.el.classList.add('badpath'); | |
| o.el.style.opacity=1; | |
| o.el.style.transition='stroke-dashoffset '+EDGE_MS+'ms linear'; | |
| o.el.style.strokeDashoffset=0; | |
| }); | |
| t+=EDGE_MS+40; | |
| } else if(st.n){ | |
| at(t,function(){ | |
| var el=document.getElementById(st.n); | |
| svg.querySelectorAll('.gnode.active').forEach(function(p){ | |
| p.classList.remove('active'); p.classList.add('settled'); | |
| }); | |
| el.classList.add('active'); | |
| say('<span class="'+(st.bad?'no':'go')+'">'+st.s+'</span>'); | |
| }); | |
| t+=NODE_MS; | |
| } else if(st.g){ | |
| at(t,function(){ | |
| var el=document.getElementById(st.g); | |
| el.classList.add('checking'); say('<span style="color:var(--warn)">'+st.s+'</span>'); | |
| }); | |
| at(t+GATE_MS*0.55,function(){ | |
| var el=document.getElementById(st.g); | |
| el.classList.remove('checking'); | |
| el.classList.add(st.pass?'pass':'fail'); | |
| el.querySelector('.gres').textContent = st.pass?'✓ pass':'✗ fail'; | |
| say(st.pass ? '<span class="ok">'+st.s+' → ✓ pass</span> · next atom may execute' | |
| : '<span class="no">'+st.s+' → ✗ fail</span> · leaving pipeline'); | |
| }); | |
| t+=GATE_MS; | |
| } | |
| }); | |
| at(t+420,function(){ | |
| running=false; bRun.disabled=false; bFail.disabled=false; | |
| say(fail?'<span class="no">aborted — failure localised to the perceive atom</span>' | |
| :'<span class="ok">complete — every gate verified before its skill ran</span>'); | |
| }); | |
| } | |
| bRun.addEventListener('click',function(){play(false);}); | |
| bFail.addEventListener('click',function(){play(true);}); | |
| var seen=false; | |
| new IntersectionObserver(function(es){ | |
| es.forEach(function(e){ if(e.isIntersecting && !seen){ seen=true; setTimeout(function(){play(false);},350); } }); | |
| },{threshold:.3}).observe(svg); | |
| })(); | |
| /* animated score panels (§05, §06) */ | |
| (function(){ | |
| function countTo(el,target,dur){ | |
| if(!el) return; var start=null; | |
| function step(ts){ if(!start) start=ts; var p=Math.min((ts-start)/dur,1); | |
| el.textContent=(target*(p<1?(1-Math.pow(1-p,3)):1)).toFixed(1)+'%'; | |
| if(p<1) requestAnimationFrame(step); } | |
| requestAnimationFrame(step); | |
| } | |
| function panel(id,pairs,deltaId){ | |
| var el=document.getElementById(id); if(!el) return; var fired=false; | |
| new IntersectionObserver(function(es){ | |
| es.forEach(function(e){ | |
| if(!e.isIntersecting||fired) return; fired=true; | |
| el.querySelectorAll('.fill').forEach(function(f){ f.style.width=f.dataset.w+'%'; }); | |
| pairs.forEach(function(p){ countTo(document.getElementById(p[0]),p[1],1400); }); | |
| setTimeout(function(){ var d=document.getElementById(deltaId); if(d) d.classList.add('show'); },900); | |
| }); | |
| },{threshold:.4}).observe(el); | |
| } | |
| panel('repScore',[['pRep',93.3],['pOurs',96.7]],'dRep'); | |
| panel('sevScore',[['pSevB',6.7],['pSevO',80.0]],'dSev'); | |
| })(); | |
| </script> | |