KoeYe commited on
Commit
5c0856a
·
1 Parent(s): 2cc8c14

Rebuild reference Pareto frontier and default all case displays to live 3D

Browse files
README.md CHANGED
@@ -25,3 +25,7 @@ Layout, navigation, wireframe mark, typography and card geometry follow the user
25
  The reference toolkit's Inter, IBM Plex Mono and Source Serif 4 font files are bundled with their original SIL Open Font License notices in `assets/fonts/`. Provider marks are from the toolkit's Lobehub icons, used to identify the corresponding model providers.
26
 
27
  Deployment remains this Hugging Face static Space: `index.html` and `cases.html`, with the existing media/model paths and headers. The pre-restyle version is recoverable at commit `9ff787d0f9d72905d92f780da56bd2229782b981`.
 
 
 
 
 
25
  The reference toolkit's Inter, IBM Plex Mono and Source Serif 4 font files are bundled with their original SIL Open Font License notices in `assets/fonts/`. Provider marks are from the toolkit's Lobehub icons, used to identify the corresponding model providers.
26
 
27
  Deployment remains this Hugging Face static Space: `index.html` and `cases.html`, with the existing media/model paths and headers. The pre-restyle version is recoverable at commit `9ff787d0f9d72905d92f780da56bd2229782b981`.
28
+
29
+ The Pareto renderer (`assets/pareto.js`) ports the reference toolkit's reversed logarithmic cost axis, connected frontier, provider palette and greedy collision-aware label placement. `data/pareto.json` combines unrounded scores from the existing `data/site.js` with exact mean costs from the original published tables; all three task views recompute non-dominance from those values. The table displays three decimals but sorts at source precision.
30
+
31
+ Homepage scenes (`assets/scene-cards.js`) adapt BuildingBench's shared WebGL renderer and per-view canvas engine. Existing public packed models are decoded with the same adapter as `cases.html`; geometry and the full case explorer remain unchanged. Visible scenes load automatically; repeated scenes share decoded assets. Drag or arrow keys orbit, wheel or +/- zoom, and the pause control stops rotation. Both Image-to-Scene cards switch between the saved repair, corrupted input and ground truth using the reference camera.
assets/buildingbench.css CHANGED
@@ -24,3 +24,10 @@ section,section.alt,section.dark{padding:34px 0;background:transparent;color:var
24
  @media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*{animation:none!important;transition:none!important}}
25
  .section-header.centered .section-desc{margin-left:0;margin-right:0;text-align:left}
26
  .bar-mobile-hint{display:none}@media(max-width:650px){.bar-mobile-hint{display:block;font-size:10px;color:var(--muted);padding:10px 8px 0}.kit-bars{min-width:960px}}
 
 
 
 
 
 
 
 
24
  @media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*{animation:none!important;transition:none!important}}
25
  .section-header.centered .section-desc{margin-left:0;margin-right:0;text-align:left}
26
  .bar-mobile-hint{display:none}@media(max-width:650px){.bar-mobile-hint{display:block;font-size:10px;color:var(--muted);padding:10px 8px 0}.kit-bars{min-width:960px}}
27
+ /* Live scenes use BuildingBench's single-renderer / per-view canvas architecture. */
28
+ .live-scene{position:relative;display:block;overflow:hidden;isolation:isolate;background:#e6edf0;border-radius:14px;height:273px;touch-action:pan-y}.live-scene canvas{display:block;width:100%;height:100%;cursor:grab;touch-action:none;outline-offset:-3px}.live-scene canvas:active{cursor:grabbing}.scene-loading{position:absolute;inset:0;display:grid;place-items:center;color:#53616a;font-size:12px;pointer-events:none}.scene-hint{position:absolute;left:12px;bottom:10px;font-size:10px;letter-spacing:.02em;color:#fff;background:#17201dcc;border-radius:5px;padding:5px 8px;pointer-events:none}.scene-motion{position:absolute;right:10px;bottom:10px;border:1px solid #fff9;border-radius:50%;background:#fffdf8e8;color:#344341;width:28px;height:28px;padding:0;cursor:pointer}.output-panel .live-scene{height:255px;border-radius:12px}.case-card .live-scene{height:250px;border-radius:16px 16px 0 0}.case-card{display:block}.case-card .meta{padding:20px}.case-card .meta .eyebrow{font-size:9px}.case-card .meta h4{margin:8px 0}.case-card .meta .text-button{display:inline-block;margin-top:13px;font-size:12px}.scene-versions{display:flex;gap:4px;padding:9px;background:#f2f2ee;border-top:1px solid #e0e3df}.scene-versions button{flex:1;border:0;border-radius:6px;background:transparent;padding:7px 4px;color:#707b75;font:500 11px Inter,sans-serif;cursor:pointer}.scene-versions button.on{background:white;color:#17201d;box-shadow:0 1px 3px #0001}.pareto-card{background:#fffefb!important;padding:26px 28px 20px!important}.figure-title{font:400 31px/1.2 'Source Serif 4',serif!important;letter-spacing:-.7px!important;margin:0 0 8px!important}.figure-title span{color:#8d8d8d}.figure-subtitle{font:400 15.5px/22px Inter,sans-serif;color:#8d8d8d;margin:0 0 8px}.figure-caption{font:400 12px/1.7 Inter,sans-serif;color:#7d857f;margin:8px 20px 0!important}.pareto-scroll{overflow-x:auto}.pareto-svg{width:100%;min-width:880px;height:auto;display:block;font-family:Inter,sans-serif}.pareto-svg .grid{stroke:#dfdcd2;stroke-width:.8}.pareto-svg .vertical{stroke-dasharray:2 5;opacity:.65}.pareto-svg .axis{stroke:#c8c4b7;stroke-width:1}.pareto-svg .tick{fill:#9aa19b;font-size:11px}.pareto-svg .axis-title{fill:#7d857f;font-size:16px}.pareto-svg .frontier-line{fill:none;stroke:#2b3531;stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round}.pareto-svg .frontier-halo{fill:none;stroke:#2b3531;stroke-opacity:.13;stroke-width:11.7;stroke-linejoin:round;stroke-linecap:round}.pareto-svg .model-label{paint-order:stroke;stroke:#fffefb;stroke-width:3.6px;stroke-linejoin:round}.pareto-node{cursor:help;transition:opacity .15s}.pareto-node.dim{opacity:.24}.pareto-node:focus{outline:none}.pareto-node:focus .chip{stroke:#17201d;stroke-width:2}.pareto-tooltip{position:fixed;z-index:1000;padding:12px 15px;border:1px solid #dfdcd2;border-radius:10px;background:#fffefb;color:#4a544f;box-shadow:0 5px 25px #17201d20;pointer-events:none;opacity:0;font:12px/1.7 Inter,sans-serif}.pareto-tooltip.on{opacity:1}.pareto-tooltip strong{display:block;color:#17201d;margin-bottom:5px;font-size:13px}
29
+ @media(max-width:650px){.live-scene{height:260px}.output-panel .live-scene{height:245px}.case-card .live-scene{height:270px}.pareto-card{padding:20px 12px!important}.figure-title{font-size:26px!important}.figure-title span{display:block}.figure-subtitle{font-size:13px}.figure-caption{margin:10px 4px 0!important}.pareto-scroll{margin:0 -12px;padding:0 12px}.scene-hint{font-size:9px}}
30
+ .featured .featured-scene.live-scene{position:absolute;height:calc(100% - 57px);border-radius:0}.featured-footer,.featured-note{z-index:2}.tk figure>.live-scene{height:340px;border-radius:12px}.scene-loading{background:#e6edf0}
31
+ @media(max-width:650px){.case-grid{grid-template-columns:1fr}.tk figure>.live-scene{height:260px}}
32
+ .pareto-card .figure-title{color:#1a1a1a!important}.pareto-card .figure-title span{color:#8d8d8d}
33
+ .pareto-swipe{display:none}@media(max-width:650px){.pareto-swipe{display:block;font:10px Inter,sans-serif;color:#7d857f;margin:6px 0}}
assets/pareto.js ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Adapted from BuildingBench's figure-kit/pareto-frontier/render.py and live SVG.
2
+ // Domain, costs and scores are exclusively Code4Scene's historical paper evaluation.
3
+ const NS='http://www.w3.org/2000/svg';
4
+ const colors={openai:'#10A37F',claude:'#D97757',gemini:'#7b1fa2',meta:'#42a5f5',deepseek:'#3f51e0',zai:'#96650b',grok:'#111111',qwen:'#fe7016',thinkingmachines:'#686868'};
5
+ const data=await fetch('data/pareto.json?v=20261001b').then(r=>r.json());
6
+ await Promise.all([document.fonts.load('400 13px Inter'),document.fonts.load('700 16px Inter')]);
7
+ const measure=document.createElement('canvas').getContext('2d');
8
+ function node(tag,attrs,text){const n=document.createElementNS(NS,tag);for(const[k,v]of Object.entries(attrs))n.setAttribute(k,v);if(text!==undefined)n.textContent=text;return n;}
9
+ const lum=c=>{const a=c.match(/\w\w/g).map(x=>parseInt(x,16)/255).map(x=>x<=.03928?x/12.92:((x+.055)/1.055)**2.4);return .2126*a[0]+.7152*a[1]+.0722*a[2];};
10
+ function labelColor(hex,front){let a=hex.slice(1).match(/../g).map(x=>parseInt(x,16)/255),max=Math.max(...a),min=Math.min(...a),l=(max+min)/2,h=0,s=0,d=max-min;if(d){s=d/(1-Math.abs(2*l-1));h=max===a[0]?((a[1]-a[2])/d)%6:max===a[1]?(a[2]-a[0])/d+2:(a[0]-a[1])/d+4;h=(h/6+1)%1;}s=Math.min(s,front?.92:.55);const rgb=l=>{const c=(1-Math.abs(2*l-1))*s,x=c*(1-Math.abs(h*6%2-1)),m=l-c/2;const q=h<1/6?[c,x,0]:h<2/6?[x,c,0]:h<3/6?[0,c,x]:h<4/6?[0,x,c]:h<5/6?[x,0,c]:[c,0,x];return '#'+q.map(v=>Math.round((v+m)*255).toString(16).padStart(2,'0')).join('');};const target=front?4.6:2.9;if(front&&(lum('fffefb')+.05)/(lum(hex.slice(1))+.05)>=target)return hex;let lo=0,hi=1;for(let i=0;i<26;i++){let mid=(lo+hi)/2;if((lum('fffefb')+.05)/(lum(rgb(mid).slice(1))+.05)>target)lo=mid;else hi=mid;}return rgb((lo+hi)/2);}
11
+ for(const svg of document.querySelectorAll('.pareto-svg')){
12
+ const rows=data[svg.dataset.mode],front=rows.filter(a=>!rows.some(b=>b.cost<=a.cost&&b.score>=a.score&&(b.cost<a.cost||b.score>a.score))).sort((a,b)=>b.cost-a.cost),on=new Set(front.map(d=>d.key));
13
+ const lo=Math.min(...rows.map(d=>-Math.log10(d.cost)))-.30,hi=Math.max(...rows.map(d=>-Math.log10(d.cost)))+.30;
14
+ const x=c=>96+(-Math.log10(c)-lo)/(hi-lo)*1074,y=s=>473.2-s/.96*448;
15
+ const add=(tag,attrs,text)=>{const n=node(tag,attrs,text);svg.append(n);return n;};
16
+ add('rect',{x:0,y:0,width:1200,height:560,fill:'#fffefb'});
17
+ for(const t of [0,.24,.48,.72,.96]){add('line',{x1:96,x2:1170,y1:y(t),y2:y(t),class:'grid'});add('text',{x:84,y:y(t)+4,class:'tick','text-anchor':'end'},t.toFixed(2));}
18
+ for(const c of [100,50,20,10,5,2,1,.5,.2,.1,.05,.02,.01,.005,.002,.001])if(-Math.log10(c)>lo&&-Math.log10(c)<hi){add('line',{x1:x(c),x2:x(c),y1:25.2,y2:473.2,class:'grid vertical'});add('text',{x:x(c),y:499,class:'tick','text-anchor':'middle'},'$'+c);}
19
+ add('line',{x1:96,x2:1170,y1:473.2,y2:473.2,class:'axis'});
20
+ add('text',{x:633,y:539,class:'axis-title','text-anchor':'middle'},'Mean Cost per Case (USD) · cheaper →');
21
+ add('text',{x:27,y:249,transform:'rotate(-90 27 249)',class:'axis-title','text-anchor':'middle'},'Simulation Quality');
22
+ const path=front.map((d,i)=>(i?'L':'M')+x(d.cost)+','+y(d.score)).join(' ');
23
+ add('path',{d:path,class:'frontier-halo'});add('path',{d:path,class:'frontier-line'});
24
+ const points=rows.map(d=>({x:x(d.cost),y:y(d.score)}));const boxes=points.map(p=>[p.x-9,p.y-9,p.x+9,p.y+9]);
25
+ const linePoints=[];for(let i=1;i<front.length;i++){const a=front[i-1],b=front[i],n=Math.max(2,Math.ceil(Math.hypot(x(b.cost)-x(a.cost),y(b.score)-y(a.score))/5));for(let j=0;j<=n;j++)linePoints.push([x(a.cost)+(x(b.cost)-x(a.cost))*j/n,y(a.score)+(y(b.score)-y(a.score))*j/n]);}
26
+ for(const d of [...rows].sort((a,b)=>(on.has(b.key)-on.has(a.key))||b.score-a.score)){
27
+ const isFront=on.has(d.key),cx=x(d.cost),cy=y(d.score),font=isFront?16:12.5,name=d.name.replace(/^Claude /,'').replace(/\((.*?)\)/,(_,s)=>'('+s[0].toUpperCase()+s.slice(1)+')'),size=font;
28
+ measure.font=`${isFront?700:400} ${size}px Inter`;const tw=measure.measureText(name).width,th=size*.78,r=9,gap=5,pad=2.5,up=r+gap,dn=r+gap+th;
29
+ const candidates=[];for(let k=0;k<5;k++){const lift=k*th*1.55;candidates.push([0,-up-lift,'middle'],[0,dn+lift,'middle'],[r+gap,th*.4-lift,'start'],[-r-gap,th*.4-lift,'end'],[r+gap,th*.4+lift,'start'],[-r-gap,th*.4+lift,'end'],[r*.6,-up*.82-lift,'start'],[-r*.6,-up*.82-lift,'end'],[r*.6,dn*.9+lift,'start'],[-r*.6,dn*.9+lift,'end']);}
30
+ const hard=[...boxes,...linePoints.filter(p=>Math.hypot(p[0]-cx,p[1]-cy)>r*2.4).map(p=>[p[0]-4,p[1]-4,p[0]+4,p[1]+4])];let best,bestCost=Infinity;
31
+ for(let i=0;i<candidates.length;i++){const[dx,dy,anchor]=candidates[i],tx=cx+dx,ty=cy+dy,x0=anchor==='middle'?tx-tw/2:anchor==='end'?tx-tw:tx,b=[x0-pad,ty-th-pad,x0+tw+pad,ty+pad];let cost=i*.6;if(b[0]<58)cost+=400+58-b[0];if(b[2]>1180)cost+=400+b[2]-1180;if(b[1]<9)cost+=400+9-b[1];if(b[3]>475)cost+=400+b[3]-475;let overlap=0;for(const q of hard){const w=Math.min(b[2],q[2])-Math.max(b[0],q[0]),h=Math.min(b[3],q[3])-Math.max(b[1],q[1]);if(w>0&&h>0)overlap+=w*h;}cost+=overlap*4;if(cost<bestCost){bestCost=cost;best={dx,dy,anchor,b};}if(!overlap&&cost<400)break;}
32
+ boxes.push(best.b);const color=colors[d.logo]||'#686868',label=labelColor(color,isFront);
33
+ const g=add('g',{class:'pareto-node'+(isFront?' is-frontier':''),tabindex:0,role:'img','aria-label':`${d.name}, score ${d.score.toFixed(6)}, mean cost $${d.cost.toFixed(6)}${isFront?', Pareto frontier':''}`,'data-name':d.name,'data-score':d.score,'data-cost':d.cost,'data-frontier':isFront});
34
+ if(isFront)g.append(node('circle',{cx,cy,r:9,fill:'none',stroke:color,'stroke-width':1.3}));
35
+ g.append(node('rect',{x:cx-5,y:cy-5,width:10,height:10,rx:1,fill:color,stroke:'#fffefb','stroke-width':1.7,class:'chip'}));
36
+ g.append(node('text',{x:cx+best.dx,y:cy+best.dy,'text-anchor':best.anchor,fill:label,'font-size':font,'font-weight':isFront?700:400,class:'model-label'},name));
37
+ g.append(node('circle',{cx,cy,r:14,fill:'transparent'}));
38
+ const tip=document.querySelector('#pareto-tooltip')||Object.assign(document.body.appendChild(document.createElement('div')),{id:'pareto-tooltip',className:'pareto-tooltip'});
39
+ const show=()=>{tip.replaceChildren();const title=document.createElement('strong');title.textContent=d.name;tip.append(title);for(const text of [`Score: ${d.score.toFixed(6)}`,`Mean cost / case: $${d.cost.toFixed(6)}`,isFront?'Pareto frontier':'Dominated configuration']){const p=document.createElement('div');p.textContent=text;tip.append(p);}const rect=g.querySelector('.chip').getBoundingClientRect();tip.classList.add('on');tip.style.left=Math.max(8,Math.min(innerWidth-tip.offsetWidth-8,rect.x+rect.width/2-tip.offsetWidth/2))+'px';tip.style.top=(rect.y<tip.offsetHeight+16?rect.bottom+12:rect.y-tip.offsetHeight-12)+'px';svg.querySelectorAll('.pareto-node').forEach(n=>n.classList.toggle('dim',n!==g));};
40
+ const hide=()=>{tip.classList.remove('on');svg.querySelectorAll('.pareto-node').forEach(n=>n.classList.remove('dim'));};
41
+ g.addEventListener('mouseenter',show);g.addEventListener('focus',show);g.addEventListener('click',show);g.addEventListener('mouseleave',hide);g.addEventListener('blur',hide);window.addEventListener('scroll',hide,{passive:true});
42
+ }
43
+ svg.dataset.ready='true';
44
+ }
assets/scene-cards.js ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as THREE from "three";
2
+ import {GLTFLoader} from "three/addons/loaders/GLTFLoader.js";
3
+ import {MeshoptDecoder} from "three/addons/libs/meshopt_decoder.module.js";
4
+ import {Sky} from "three/addons/objects/Sky.js";
5
+
6
+ "use strict";
7
+ // The 3D engine: one shared WebGL renderer, one scene per loaded submission,
8
+ // and a Viewer per canvas that keeps the same interface the prototype's
9
+ // procedural viewer had (rotationX / rotationY / zoom / paused / group /
10
+ // draw(dt) / destroy()), so the page code above it did not have to change.
11
+ //
12
+ // Every mesh drawn here is the building.glb an agent handed in to the
13
+ // benchmark, re-encoded for the page (textures at most 512 px, JPEG for
14
+ // opaque maps) and gzipped. Geometry is untouched.
15
+ (function () {
16
+ const ADD = {GLTFLoader};
17
+ const activeViewers = new Set();
18
+ window.activeViewers = activeViewers;
19
+ let lastFrame = performance.now();
20
+
21
+ /* ------------------------------------------------------------ renderer */
22
+ let gl = null, ENV = null;
23
+ function renderer() {
24
+ if (gl) return gl;
25
+ gl = new THREE.WebGLRenderer({ antialias: true, alpha: true, premultipliedAlpha: true, powerPreference: "high-performance" });
26
+ gl.setPixelRatio(1);
27
+ gl.outputColorSpace = THREE.SRGBColorSpace;
28
+ gl.toneMapping = THREE.ACESFilmicToneMapping; gl.toneMappingExposure = 1.0;
29
+ gl.shadowMap.enabled = true;
30
+ gl.shadowMap.type = THREE.PCFSoftShadowMap;
31
+ gl.setClearColor(0x000000, 0);
32
+ const sky=new Sky();sky.scale.setScalar(1000);const u=sky.material.uniforms;u.turbidity.value=4;u.rayleigh.value=1.4;u.mieCoefficient.value=.004;u.mieDirectionalG.value=.8;u.sunPosition.value.setFromSphericalCoords(1,THREE.MathUtils.degToRad(52),THREE.MathUtils.degToRad(215));const skyScene=new THREE.Scene();skyScene.add(sky);ENV=new THREE.PMREMGenerator(gl).fromScene(skyScene,0,.1,2000).texture;
33
+ return gl;
34
+ }
35
+
36
+ // The benchmark's own light rig (city_bench.building_light/1), the numbers
37
+ // the board's renders use: sun in the south-east at 50 degrees, a pale sky,
38
+ // and a muted environment. One rig for the page, moved into whichever scene
39
+ // is being drawn.
40
+ const RIG = (window.BB_DATA && window.BB_DATA.rig) || {};
41
+ const RIG_GROUP = new THREE.Group();
42
+ const SUN = new THREE.DirectionalLight(0xffffff, RIG.sun_intensity ?? 3.2);
43
+ SUN.color.setRGB(...(RIG.sun_colour || [1, 0.97, 0.92]));
44
+ SUN.castShadow = true;
45
+ SUN.shadow.mapSize.set(1024, 1024);
46
+ SUN.shadow.bias = -0.0006;
47
+ SUN.shadow.normalBias = 0.02;
48
+ const SKY = new THREE.HemisphereLight(0xbcd4f0, 0x6b6157, RIG.sky_intensity ?? 0.45);
49
+ SKY.color.setRGB(...(RIG.sky_colour || [0.7, 0.8, 0.94]));
50
+ SKY.groundColor.setRGB(...(RIG.ground_colour || [0.36, 0.34, 0.31]));
51
+ // A ground that shows only the shadow that lands on it, so the page's own
52
+ // background reads through everywhere else.
53
+ const GROUND = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), new THREE.ShadowMaterial({ opacity: 0.22, transparent: true }));
54
+ GROUND.rotation.x = -Math.PI / 2;
55
+ GROUND.receiveShadow = true;
56
+ RIG_GROUP.add(SUN, SUN.target, SKY);
57
+
58
+ function rigFor(asset) {
59
+ const reach = Math.max(RIG.reach_m || 400, asset.radius * 1.6);
60
+ const az = THREE.MathUtils.degToRad(RIG.sun_azimuth_deg ?? 135), el = THREE.MathUtils.degToRad(RIG.sun_elevation_deg ?? 50);
61
+ SUN.position.set(Math.cos(el) * Math.sin(az), Math.sin(el), -Math.cos(el) * Math.cos(az)).multiplyScalar(reach).add(asset.centre);
62
+ SUN.target.position.copy(asset.centre);
63
+ const s = SUN.shadow.camera, r = asset.radius * 1.15;
64
+ s.left = -r; s.right = r; s.top = r; s.bottom = -r; s.near = 1; s.far = reach * 3;
65
+ s.updateProjectionMatrix();
66
+ GROUND.position.set(asset.centre.x, asset.floor, asset.centre.z);
67
+ GROUND.scale.set(asset.radius * 8, asset.radius * 8, 1);
68
+ asset.scene.add(RIG_GROUP);
69
+ }
70
+
71
+ /* -------------------------------------------------------------- assets */
72
+ // BB_ASSETS maps a key to either a gzipped-base64 glb (the self-contained
73
+ // page) or an assets/… path (the split copy for hosting). Same bytes, one
74
+ // fetch earlier.
75
+ const ASSETS = window.BB_ASSETS || {};
76
+ const cache = new Map(); // key -> {promise, asset, refs, last}
77
+ const loader = new ADD.GLTFLoader().setMeshoptDecoder(MeshoptDecoder);
78
+ const gunzip = buf => new Response(new Blob([buf]).stream().pipeThrough(new DecompressionStream("gzip"))).arrayBuffer();
79
+ const PACK_KEY = Uint8Array.from("5c2f9e71d3a84b06e1f7c29a38d05b64a7193ce8f2046db95e81c73a2df6904b".match(/../g), h => parseInt(h,16));
80
+ async function bytesFor(key) {
81
+ const r = await fetch(key); if (!r.ok) throw new Error("HTTP " + r.status);
82
+ const a = new Uint8Array(await r.arrayBuffer());
83
+ for (let i=0;i<a.length;i++) a[i] ^= PACK_KEY[i&31] ^ (((i>>>5)*131)&255);
84
+ return a.buffer;
85
+ }
86
+ function parse(buf) { return new Promise((resolve, reject) => loader.parse(buf, "", resolve, reject)); }
87
+ function acquire(key) {
88
+ let entry = cache.get(key);
89
+ if (!entry) {
90
+ entry = { refs: 0, last: performance.now(), asset: null };
91
+ // A dropped connection is not a bad file: one retry, and a failure leaves
92
+ // the cache so the next mount can try again.
93
+ const attempt = () => bytesFor(key).then(parse);
94
+ entry.promise = attempt().catch(() => new Promise(r => setTimeout(r, 1500)).then(attempt)).then(gltf => {
95
+ const root = gltf.scene;
96
+ root.traverse(node => { if (node.isMesh) { node.castShadow = true; node.receiveShadow = true; } });
97
+ const box = new THREE.Box3(), pts=[];
98
+ root.traverse(o=>{if(o.isMesh){const b=new THREE.Box3().setFromObject(o);if(!b.isEmpty())pts.push(b);for(const m of [].concat(o.material||[]))if(m.specularIntensity!==undefined)m.specularIntensity=Math.min(m.specularIntensity,.5);}});
99
+ const q=(arr,t)=>arr.slice().sort((a,b)=>a-b)[Math.floor(t*(arr.length-1))];
100
+ if(pts.length>8){const cx=pts.map(b=>(b.min.x+b.max.x)/2),cy=pts.map(b=>(b.min.y+b.max.y)/2),cz=pts.map(b=>(b.min.z+b.max.z)/2);box.set(new THREE.Vector3(q(cx,.03),q(cy,.03),q(cz,.03)),new THREE.Vector3(q(cx,.97),q(cy,.97),q(cz,.97)));}else box.setFromObject(root);
101
+ const centre = box.getCenter(new THREE.Vector3());
102
+ const size = box.getSize(new THREE.Vector3());
103
+ const corners = [];
104
+ for (const x of [box.min.x, box.max.x]) for (const y of [box.min.y, box.max.y]) for (const z of [box.min.z, box.max.z]) corners.push(new THREE.Vector3(x, y, z));
105
+ const scene = new THREE.Scene();
106
+ scene.environment = ENV || (renderer(), ENV);
107
+ scene.environmentIntensity = RIG.environment_intensity ?? 0.8;
108
+ scene.background = new THREE.Color(0xe6edf0); scene.add(root);
109
+ const task=window.SITE.cases.find(c=>c.track==='i2s'&&(c.input_glb===key||c.gt_glb===key||c.agents.some(a=>a.glb===key)));
110
+ if(task){root.updateMatrixWorld(true);root.traverse(o=>{const duplicate=(task.duplicates||[]).includes(o.name);if(!duplicate&&!(task.targets||[]).includes(o.name))return;const b=new THREE.Box3().setFromObject(o);if(b.isEmpty())return;const h=new THREE.Box3Helper(b.expandByScalar(.08),duplicate?0xff6b5e:0x5ee08a);h.material.depthTest=false;h.material.transparent=true;h.renderOrder=999;scene.add(h);});}
111
+ entry.asset = { key, root, scene, box, centre, corners, radius: Math.max(size.length() / 2, 1), floor: box.min.y };
112
+ return entry.asset;
113
+ });
114
+ entry.promise.catch(() => { if (cache.get(key) === entry) cache.delete(key); });
115
+ cache.set(key, entry);
116
+ }
117
+ entry.refs++; entry.last = performance.now();
118
+ return entry;
119
+ }
120
+ function release(entry) { if (!entry) return; entry.refs = Math.max(0, entry.refs - 1); entry.last = performance.now(); evict(); }
121
+ const KEEP = 40;
122
+ function evict() {
123
+ const idle = [...cache.values()].filter(e => e.refs === 0 && e.asset);
124
+ if (cache.size <= KEEP) return;
125
+ idle.sort((a, b) => a.last - b.last);
126
+ for (const e of idle) {
127
+ if (cache.size <= KEEP) break;
128
+ e.asset.root.traverse(n => {
129
+ if (!n.isMesh) return;
130
+ n.geometry?.dispose();
131
+ for (const m of [].concat(n.material || [])) { for (const s of ["map", "normalMap", "roughnessMap", "metalnessMap", "emissiveMap", "aoMap", "alphaMap"]) m[s]?.dispose(); m.dispose(); }
132
+ });
133
+ cache.delete(e.asset.key);
134
+ }
135
+ }
136
+
137
+ /* -------------------------------------------------------------- viewer */
138
+ const clamp = (v, a, b) => Math.min(b, Math.max(a, v));
139
+ const reduced = () => window.matchMedia("(prefers-reduced-motion: reduce)").matches;
140
+ class Viewer {
141
+ constructor(canvas, options = {}) {
142
+ this.canvas = canvas;
143
+ this.ctx = canvas.getContext("2d", { alpha: true });
144
+ this.rotationY = options.rotationY ?? -0.65;
145
+ this.rotationX = options.rotationX ?? 0.35;
146
+ this.zoom = options.zoom ?? 1;
147
+ this.centerX = options.centerX ?? 0.5;
148
+ this.speed = options.speed ?? 0.22;
149
+ this.group = options.group || null;
150
+ this.refCam = options.refCam; this.paused = !!options.refCam; this.dragging = false; this.lastPointer = null;
151
+ this.destroyed = false; this.hasDrawn = false; this.failed = null;
152
+ this.initialRotation = [this.rotationX,this.rotationY];
153
+ this.camera = new THREE.PerspectiveCamera(36, 1, 0.1, 5000);
154
+ this.entry = null; this.asset = null;
155
+ this.bind();
156
+ activeViewers.add(this);
157
+ if (options.assetKey) this.load(options.assetKey);
158
+ }
159
+ load(key) {
160
+ release(this.entry);
161
+ this.entry = acquire(key); this.asset = null; this.failed = null;
162
+ const entry = this.entry;
163
+ entry.promise.then(asset => { if (this.entry === entry && !this.destroyed) { this.asset = asset; this.hasDrawn = false; } },
164
+ err => { if (this.entry === entry) { this.failed = err; console.error("Code4Scene: submission failed to load", key, err); } });
165
+ return entry.promise;
166
+ }
167
+ bind() {
168
+ this.onDown = e => { this.paused = true; this.dragging = true; this.lastPointer = [e.clientX, e.clientY]; this.canvas.setPointerCapture?.(e.pointerId); };
169
+ this.onMove = e => { if (!this.dragging) return; const dx = e.clientX - this.lastPointer[0], dy = e.clientY - this.lastPointer[1]; this.rotationY += dx * .012; this.rotationX = clamp(this.rotationX + dy * .008, -.05, 1.3); this.lastPointer = [e.clientX, e.clientY]; if (this.group) { this.group.rotationX = this.rotationX; this.group.rotationY = this.rotationY; } };
170
+ this.onUp = () => { this.dragging = false; this.lastPointer = null; };
171
+ this.onWheel = e => { e.preventDefault(); this.zoom = clamp(this.zoom - e.deltaY * .0007, .6, 2.2); if (this.group) this.group.zoom = this.zoom; };
172
+ this.onKey = e => { const keys = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "+", "=", "-"]; if (!keys.includes(e.key)) return; e.preventDefault(); this.paused = true; if (e.key === "ArrowLeft") this.rotationY -= .12; if (e.key === "ArrowRight") this.rotationY += .12; if (e.key === "ArrowUp") this.rotationX = clamp(this.rotationX + .1, -.05, 1.3); if (e.key === "ArrowDown") this.rotationX = clamp(this.rotationX - .1, -.05, 1.3); if (e.key === "+" || e.key === "=") this.zoom = clamp(this.zoom + .08, .6, 2.2); if (e.key === "-") this.zoom = clamp(this.zoom - .08, .6, 2.2); if (this.group) Object.assign(this.group, { rotationX: this.rotationX, rotationY: this.rotationY, zoom: this.zoom, paused: true }); };
173
+ this.canvas.addEventListener("wheel", this.onWheel, { passive: false }); this.canvas.addEventListener("keydown", this.onKey);
174
+ this.canvas.addEventListener("pointerdown", this.onDown); this.canvas.addEventListener("pointermove", this.onMove); this.canvas.addEventListener("pointerup", this.onUp); this.canvas.addEventListener("pointercancel", this.onUp);
175
+ }
176
+ resize() {
177
+ const rect = this.canvas.getBoundingClientRect();
178
+ const dpr = Math.min(window.devicePixelRatio || 1, 2);
179
+ const w = Math.max(1, Math.round(rect.width * dpr)), h = Math.max(1, Math.round(rect.height * dpr));
180
+ if (this.canvas.width !== w || this.canvas.height !== h) { this.canvas.width = w; this.canvas.height = h; }
181
+ this.w = rect.width; this.h = rect.height; this.dpr = dpr;
182
+ }
183
+ // Orbit the model's centre. The distance is fitted to the bounding box
184
+ // seen from this direction, not to the sphere, so a wide flat building
185
+ // fills its card the way a tower does.
186
+ aim() {
187
+ const a = this.asset, cam = this.camera;
188
+ if(this.refCam){
189
+ const rc=this.refCam,target=new THREE.Vector3(...rc.target),offset=new THREE.Vector3(...rc.pos).sub(target);
190
+ const spherical=new THREE.Spherical().setFromVector3(offset);
191
+ spherical.theta += this.rotationY-this.initialRotation[1]; spherical.phi -= this.rotationX-this.initialRotation[0]; spherical.makeSafe(); spherical.radius /= this.zoom;
192
+ cam.aspect=this.w/this.h;cam.fov=2*Math.atan(Math.tan(rc.hfov*Math.PI/360)/cam.aspect)*180/Math.PI;
193
+ cam.near=.05;cam.far=Math.max(2000,a.radius*24);cam.position.copy(target).add(new THREE.Vector3().setFromSpherical(spherical));cam.lookAt(target);cam.updateProjectionMatrix();return;
194
+ }
195
+ const az = this.rotationY, el = this.rotationX;
196
+ const dir = new THREE.Vector3(Math.cos(el) * Math.sin(az), Math.sin(el), Math.cos(el) * Math.cos(az)).normalize();
197
+ const aspect = this.w / this.h;
198
+ cam.aspect = aspect; cam.fov = 34;
199
+ const vfov = THREE.MathUtils.degToRad(cam.fov), tanV = Math.tan(vfov / 2), tanH = tanV * aspect;
200
+ const probe = new THREE.Object3D();
201
+ probe.position.copy(a.centre).add(dir); probe.up.set(0, 1, 0); probe.lookAt(a.centre); probe.updateMatrixWorld();
202
+ const right = new THREE.Vector3().setFromMatrixColumn(probe.matrixWorld, 0);
203
+ const up = new THREE.Vector3().setFromMatrixColumn(probe.matrixWorld, 1);
204
+ let dist = 0;
205
+ for (const c of a.corners) { const q = c.clone().sub(a.centre); const z = q.dot(dir); dist = Math.max(dist, z + Math.abs(q.dot(right)) / tanH, z + Math.abs(q.dot(up)) / tanV); }
206
+ dist = Math.max(dist * 1.08, a.radius * 0.2) / this.zoom;
207
+ const target = a.centre.clone().addScaledVector(right, -(this.centerX - 0.5) * 2 * dist * tanH);
208
+ cam.near = Math.max(0.1, dist / 200); cam.far = Math.max(2000, dist * 20 + a.radius * 4);
209
+ cam.position.copy(target).addScaledVector(dir, dist);
210
+ cam.up.set(0, 1, 0); cam.lookAt(target); cam.updateProjectionMatrix();
211
+ }
212
+ draw(dt) {
213
+ if (this.destroyed || !this.canvas.isConnected) return;
214
+ const rect = this.canvas.getBoundingClientRect();
215
+ if (rect.width < 2 || rect.height < 2 || document.hidden) return;
216
+ if (rect.bottom < 0 || rect.top > window.innerHeight) return;
217
+ this.resize();
218
+ if (this.group) { this.rotationX = this.group.rotationX; this.rotationY = this.group.rotationY; this.zoom = this.group.zoom; }
219
+ if (!this.group && !this.paused && !this.dragging && !reduced()) this.rotationY += this.speed * dt;
220
+ const ctx = this.ctx, W = this.canvas.width, H = this.canvas.height;
221
+ if (!this.asset) {
222
+ if (this.hasDrawn) return;
223
+ ctx.clearRect(0, 0, W, H);
224
+ if (this.failed) { ctx.fillStyle = "#93673f"; ctx.font = `${Math.max(9, Math.round(10 * this.dpr))}px Arial`; ctx.textAlign = "center"; ctx.fillText("mesh unavailable", W / 2, H / 2); this.hasDrawn = true; }
225
+ return;
226
+ }
227
+ const g = renderer();
228
+ g.setSize(W, H, false);
229
+ this.aim();
230
+ rigFor(this.asset);
231
+ g.render(this.asset.scene, this.camera);
232
+ ctx.clearRect(0, 0, W, H);
233
+ ctx.drawImage(g.domElement, 0, 0, W, H, 0, 0, W, H);
234
+ this.hasDrawn = true; this.canvas.dataset.rendered="true"; this.canvas.parentElement.querySelector(".scene-loading")?.remove();
235
+ }
236
+ destroy() {
237
+ this.destroyed = true; activeViewers.delete(this);
238
+ release(this.entry); this.entry = null; this.asset = null;
239
+ this.canvas.removeEventListener("pointerdown", this.onDown); this.canvas.removeEventListener("pointermove", this.onMove); this.canvas.removeEventListener("pointerup", this.onUp); this.canvas.removeEventListener("pointercancel", this.onUp); this.canvas.removeEventListener("wheel", this.onWheel); this.canvas.removeEventListener("keydown", this.onKey);
240
+ }
241
+ }
242
+ window.GLBViewer = Viewer;
243
+ window.destroyViewersWithin = root => { activeViewers.forEach(v => { if (root.contains(v.canvas)) v.destroy(); }); };
244
+ window.BB_ENGINE = { cache, renderer, preload: key => acquire(key).promise.then(a => (release(cache.get(key)), a)) };
245
+
246
+ /* ------------------------------------------------------------- the clock */
247
+ function loop(now) {
248
+ requestAnimationFrame(loop);
249
+ if (now - lastFrame < 32) return;
250
+ const dt = Math.min(.05, (now - lastFrame) / 1000); lastFrame = now;
251
+ const groups = new Set([...activeViewers].filter(v => v.group && !v.destroyed).map(v => v.group));
252
+ if (!document.hidden && !reduced()) groups.forEach(g => { if (!g.paused && ![...activeViewers].some(v => v.group === g && v.dragging)) g.rotationY += dt * .14; });
253
+ activeViewers.forEach(v => { try { v.draw(dt); } catch (err) { v.failed = err; v.asset = null; console.error('Code4Scene: viewer draw failed', err); } });
254
+ }
255
+ requestAnimationFrame(loop);
256
+ })();
257
+
258
+
259
+ // Mount only near the viewport; repeated appearances share one decoded asset and WebGL renderer.
260
+ const observer=new IntersectionObserver(entries=>entries.forEach(entry=>{
261
+ if(!entry.isIntersecting)return;observer.unobserve(entry.target);
262
+ const el=entry.target,c=window.SITE.cases.find(c=>c.id===el.dataset.case),a=c.agents.find(a=>a.key===el.dataset.agent);
263
+ const viewer=new window.GLBViewer(el.querySelector('canvas'),{assetKey:a.glb,refCam:c.ref_cam,rotationX:.55,rotationY:-.62,speed:.1});
264
+ const loading=el.querySelector('.scene-loading');viewer.entry.promise.catch(()=>{if(loading)loading.textContent='Scene unavailable — open the case to retry.';});
265
+ const pause=el.querySelector('.scene-motion');if(c.ref_cam){pause.textContent='▶';pause.setAttribute('aria-label','Resume scene rotation');}
266
+ pause.addEventListener('click',()=>{viewer.paused=!viewer.paused;pause.textContent=viewer.paused?'▶':'Ⅱ';pause.setAttribute('aria-label',viewer.paused?'Resume scene rotation':'Pause scene rotation');});
267
+ el.querySelector('canvas').addEventListener('pointerdown',()=>{pause.textContent='▶';pause.setAttribute('aria-label','Resume scene rotation');});
268
+ el.parentElement.querySelectorAll('.scene-versions button').forEach(b=>b.addEventListener('click',()=>{
269
+ const url=b.dataset.state==='input'?c.input_glb:b.dataset.state==='gt'?c.gt_glb:a.glb;
270
+ el.parentElement.querySelectorAll('.scene-versions button').forEach(x=>x.classList.toggle('on',x===b));
271
+ const status=document.createElement('span');status.className='scene-loading';status.setAttribute('role','status');status.textContent='Loading '+b.textContent.toLowerCase()+'…';el.querySelector('.scene-loading')?.remove();el.append(status);
272
+ el.querySelector('canvas').dataset.rendered='false';el.querySelector('.scene-hint').textContent=b.textContent+' · Drag to orbit';
273
+ viewer.load(url).catch(()=>status.textContent='Scene unavailable — open the case to retry.');el.dataset.state=b.dataset.state;
274
+ }));
275
+ }),{rootMargin:'180px'});
276
+ document.querySelectorAll('.live-scene').forEach(el=>observer.observe(el));
cases.html CHANGED
@@ -83,7 +83,7 @@ table.cs td,table.cs th{white-space:nowrap}.panel code{font-size:.82em;backgroun
83
  .in-open:hover{background:var(--primary);color:#fff}
84
  @media(max-width:900px){.in-i2s{grid-template-columns:1fr}}
85
  </style><script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"}}</script><link href="assets/buildingbench.css?v=20261001" rel="stylesheet"/></head><body class="case-page"><a class="skip-link" href="#casebar">Skip to benchmark explorer</a>
86
- <header class="site-header"><div class="brand"><a aria-label="Code4Scene home" class="brand-home" href="index.html#top"><svg aria-hidden="true" fill="none" viewbox="0 0 30 32"><path d="M15 2 27 9v14l-12 7L3 23V9L15 2Z M3 9l12 7 12-7 M15 16v14 M9 5.5l12 7v14 M21 5.5l-12 7v14" stroke="currentColor" stroke-linejoin="round" stroke-width="1.6"></path></svg><span>Code4Scene</span></a><a class="brand-by" href="https://simworld.org/">by SimWorld</a></div><nav aria-label="Main navigation"><a href="index.html#top">Benchmark</a><a href="index.html#leaderboard">Leaderboard</a><a class="nav-active" href="cases.html">Visualization</a><a href="https://arxiv.org/abs/2609.36777" rel="noopener" target="_blank">Paper ↗</a><a href="https://github.com/SimWorld-AI/Code4Scene" rel="noopener" target="_blank">Github ↗</a></nav></header>
87
  <section class="alt" style="padding-top:34px;padding-bottom:40px"><div class="container">
88
  <div class="section-header" style="margin-bottom:1.6rem"><span class="section-label">Cases</span><h2 class="section-title">Every scene in 3D, with its scores</h2>
89
  <p class="section-desc">Each agent's result is the engine-native scene it saved, exported from Unreal Engine so you can turn it in the browser. Beside it, how the
 
83
  .in-open:hover{background:var(--primary);color:#fff}
84
  @media(max-width:900px){.in-i2s{grid-template-columns:1fr}}
85
  </style><script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"}}</script><link href="assets/buildingbench.css?v=20261001" rel="stylesheet"/></head><body class="case-page"><a class="skip-link" href="#casebar">Skip to benchmark explorer</a>
86
+ <header class="site-header"><div class="brand"><a aria-label="Code4Scene home" class="brand-home" href="index.html#top"><svg aria-hidden="true" fill="none" viewbox="0 0 30 32"><path d="M15 2 27 9v14l-12 7L3 23V9L15 2Z M3 9l12 7 12-7 M15 16v14 M9 5.5l12 7v14 M21 5.5l-12 7v14" stroke="currentColor" stroke-linejoin="round" stroke-width="1.6"></path></svg><span>Code4Scene</span></a><a class="brand-by" href="https://simworld.org/">by SimWorld</a></div><nav aria-label="Main navigation"><a href="index.html#top">Benchmark</a><a href="index.html#leaderboard">Leaderboard</a><a class="nav-active" href="index.html#cases">Visualization</a><a href="https://arxiv.org/abs/2609.36777" rel="noopener" target="_blank">Paper ↗</a><a href="https://github.com/SimWorld-AI/Code4Scene" rel="noopener" target="_blank">Github ↗</a></nav></header>
87
  <section class="alt" style="padding-top:34px;padding-bottom:40px"><div class="container">
88
  <div class="section-header" style="margin-bottom:1.6rem"><span class="section-label">Cases</span><h2 class="section-title">Every scene in 3D, with its scores</h2>
89
  <p class="section-desc">Each agent's result is the engine-native scene it saved, exported from Unreal Engine so you can turn it in the browser. Beside it, how the
data/pareto.json ADDED
@@ -0,0 +1,554 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "overall": [
3
+ {
4
+ "key": "astra",
5
+ "name": "GPT-6 Astra (max)",
6
+ "logo": "openai",
7
+ "open": false,
8
+ "overall": 0.6193352866666667,
9
+ "t2s": 0.72382,
10
+ "i2s": 0.5148505733333334,
11
+ "indoor": 0.78546656,
12
+ "outdoor": 0.37954258,
13
+ "score": 0.6193352866666667,
14
+ "cost": 12.896991822966507
15
+ },
16
+ {
17
+ "key": "gemini",
18
+ "name": "Gemini 3.8 Flash (high)",
19
+ "logo": "gemini",
20
+ "open": false,
21
+ "overall": 0.6189750266666667,
22
+ "t2s": 0.65689,
23
+ "i2s": 0.5810600533333334,
24
+ "indoor": 0.71653332,
25
+ "outdoor": 0.5133234200000001,
26
+ "score": 0.6189750266666667,
27
+ "cost": 1.9235185771258503
28
+ },
29
+ {
30
+ "key": "fable",
31
+ "name": "Claude Fable 5.1 (max)",
32
+ "logo": "claude",
33
+ "open": false,
34
+ "overall": 0.60604898,
35
+ "t2s": 0.78775,
36
+ "i2s": 0.4243479600000001,
37
+ "indoor": 0.53523332,
38
+ "outdoor": 0.36890528000000006,
39
+ "score": 0.60604898,
40
+ "cost": 10.623258766403161
41
+ },
42
+ {
43
+ "key": "opus",
44
+ "name": "Claude Opus 5 (max)",
45
+ "logo": "claude",
46
+ "open": false,
47
+ "overall": 0.5931299933333334,
48
+ "t2s": 0.7181599999999999,
49
+ "i2s": 0.46809998666666675,
50
+ "indoor": 0.61843384,
51
+ "outdoor": 0.39293306,
52
+ "score": 0.5931299933333334,
53
+ "cost": 19.47789116793412
54
+ },
55
+ {
56
+ "key": "sol",
57
+ "name": "GPT-5.6 Sol (high)",
58
+ "logo": "openai",
59
+ "open": false,
60
+ "overall": 0.5499994666666667,
61
+ "t2s": 0.70704,
62
+ "i2s": 0.3929589333333333,
63
+ "indoor": 0.49742972,
64
+ "outdoor": 0.34072354,
65
+ "score": 0.5499994666666667,
66
+ "cost": 2.9749956842592593
67
+ },
68
+ {
69
+ "key": "muse",
70
+ "name": "Muse Spark 1.3 (medium)",
71
+ "logo": "meta",
72
+ "open": false,
73
+ "overall": 0.5022053666666666,
74
+ "t2s": 0.646195,
75
+ "i2s": 0.35821573333333334,
76
+ "indoor": 0.63253332,
77
+ "outdoor": 0.22105694,
78
+ "score": 0.5022053666666666,
79
+ "cost": 0.0861066423076923
80
+ },
81
+ {
82
+ "key": "glm",
83
+ "name": "GLM-5.3 Flash (max)",
84
+ "logo": "zai",
85
+ "open": true,
86
+ "overall": 0.4147088666666666,
87
+ "t2s": 0.5094299999999999,
88
+ "i2s": 0.3199877333333333,
89
+ "indoor": 0.46533411999999996,
90
+ "outdoor": 0.24731454,
91
+ "score": 0.4147088666666666,
92
+ "cost": 0.40130584861111107
93
+ },
94
+ {
95
+ "key": "qwen-off",
96
+ "name": "Qwen 3.8 27B (thinking off)",
97
+ "logo": "qwen",
98
+ "open": true,
99
+ "overall": 0.37818169333333335,
100
+ "t2s": 0.55666,
101
+ "i2s": 0.19970338666666668,
102
+ "indoor": 0.26629816,
103
+ "outdoor": 0.166406,
104
+ "score": 0.37818169333333335,
105
+ "cost": 0.38413493183103564
106
+ },
107
+ {
108
+ "key": "grok",
109
+ "name": "Grok 4.6 (high)",
110
+ "logo": "grok",
111
+ "open": false,
112
+ "overall": 0.37567506,
113
+ "t2s": 0.56721,
114
+ "i2s": 0.18414012,
115
+ "indoor": 0.1968008,
116
+ "outdoor": 0.17780978,
117
+ "score": 0.37567506,
118
+ "cost": 2.2948689296296294
119
+ },
120
+ {
121
+ "key": "qwen-on",
122
+ "name": "Qwen 3.8 27B (thinking on)",
123
+ "logo": "qwen",
124
+ "open": true,
125
+ "overall": 0.37094399333333333,
126
+ "t2s": 0.51571,
127
+ "i2s": 0.2261779866666667,
128
+ "indoor": 0.29651112,
129
+ "outdoor": 0.19101142,
130
+ "score": 0.37094399333333333,
131
+ "cost": 0.29748765575757574
132
+ },
133
+ {
134
+ "key": "inkling",
135
+ "name": "Inkling (high)",
136
+ "logo": "thinkingmachines",
137
+ "open": false,
138
+ "overall": 0.31150237333333336,
139
+ "t2s": 0.42498500000000006,
140
+ "i2s": 0.19801974666666666,
141
+ "indoor": 0.1690288,
142
+ "outdoor": 0.21251522,
143
+ "score": 0.31150237333333336,
144
+ "cost": 0.4783280191269841
145
+ },
146
+ {
147
+ "key": "gemma-on",
148
+ "name": "Gemma 4 31B (thinking on)",
149
+ "logo": "gemini",
150
+ "open": true,
151
+ "overall": 0.29812257999999997,
152
+ "t2s": 0.470705,
153
+ "i2s": 0.12554015999999998,
154
+ "indoor": 0.12704028,
155
+ "outdoor": 0.1247901,
156
+ "score": 0.29812257999999997,
157
+ "cost": 0.026867125396825398
158
+ },
159
+ {
160
+ "key": "gemma-off",
161
+ "name": "Gemma 4 31B (thinking off)",
162
+ "logo": "gemini",
163
+ "open": true,
164
+ "overall": 0.28498897333333334,
165
+ "t2s": 0.45494,
166
+ "i2s": 0.11503794666666667,
167
+ "indoor": 0.11239600000000001,
168
+ "outdoor": 0.11635892,
169
+ "score": 0.28498897333333334,
170
+ "cost": 0.017148714965986397
171
+ },
172
+ {
173
+ "key": "deepseek",
174
+ "name": "DeepSeek V4.1 Flash (high)",
175
+ "logo": "deepseek",
176
+ "open": true,
177
+ "overall": 0.22658958,
178
+ "t2s": 0.24269,
179
+ "i2s": 0.21048916,
180
+ "indoor": 0.18933412,
181
+ "outdoor": 0.22106668,
182
+ "score": 0.22658958,
183
+ "cost": 0.1240297601010101
184
+ }
185
+ ],
186
+ "t2s": [
187
+ {
188
+ "key": "fable",
189
+ "name": "Claude Fable 5.1 (max)",
190
+ "logo": "claude",
191
+ "open": false,
192
+ "overall": 0.60604898,
193
+ "t2s": 0.78775,
194
+ "i2s": 0.4243479600000001,
195
+ "indoor": 0.53523332,
196
+ "outdoor": 0.36890528000000006,
197
+ "score": 0.78775,
198
+ "cost": 18.1576316
199
+ },
200
+ {
201
+ "key": "astra",
202
+ "name": "GPT-6 Astra (max)",
203
+ "logo": "openai",
204
+ "open": false,
205
+ "overall": 0.6193352866666667,
206
+ "t2s": 0.72382,
207
+ "i2s": 0.5148505733333334,
208
+ "indoor": 0.78546656,
209
+ "outdoor": 0.37954258,
210
+ "score": 0.72382,
211
+ "cost": 22.472260736842106
212
+ },
213
+ {
214
+ "key": "opus",
215
+ "name": "Claude Opus 5 (max)",
216
+ "logo": "claude",
217
+ "open": false,
218
+ "overall": 0.5931299933333334,
219
+ "t2s": 0.7181599999999999,
220
+ "i2s": 0.46809998666666675,
221
+ "indoor": 0.61843384,
222
+ "outdoor": 0.39293306,
223
+ "score": 0.7181599999999999,
224
+ "cost": 34.49611078947368
225
+ },
226
+ {
227
+ "key": "sol",
228
+ "name": "GPT-5.6 Sol (high)",
229
+ "logo": "openai",
230
+ "open": false,
231
+ "overall": 0.5499994666666667,
232
+ "t2s": 0.70704,
233
+ "i2s": 0.3929589333333333,
234
+ "indoor": 0.49742972,
235
+ "outdoor": 0.34072354,
236
+ "score": 0.70704,
237
+ "cost": 4.07260365
238
+ },
239
+ {
240
+ "key": "gemini",
241
+ "name": "Gemini 3.8 Flash (high)",
242
+ "logo": "gemini",
243
+ "open": false,
244
+ "overall": 0.6189750266666667,
245
+ "t2s": 0.65689,
246
+ "i2s": 0.5810600533333334,
247
+ "indoor": 0.71653332,
248
+ "outdoor": 0.5133234200000001,
249
+ "score": 0.65689,
250
+ "cost": 2.49471265
251
+ },
252
+ {
253
+ "key": "muse",
254
+ "name": "Muse Spark 1.3 (medium)",
255
+ "logo": "meta",
256
+ "open": false,
257
+ "overall": 0.5022053666666666,
258
+ "t2s": 0.646195,
259
+ "i2s": 0.35821573333333334,
260
+ "indoor": 0.63253332,
261
+ "outdoor": 0.22105694,
262
+ "score": 0.646195,
263
+ "cost": 0.10048539999999999
264
+ },
265
+ {
266
+ "key": "grok",
267
+ "name": "Grok 4.6 (high)",
268
+ "logo": "grok",
269
+ "open": false,
270
+ "overall": 0.37567506,
271
+ "t2s": 0.56721,
272
+ "i2s": 0.18414012,
273
+ "indoor": 0.1968008,
274
+ "outdoor": 0.17780978,
275
+ "score": 0.56721,
276
+ "cost": 4.326270533333333
277
+ },
278
+ {
279
+ "key": "qwen-off",
280
+ "name": "Qwen 3.8 27B (thinking off)",
281
+ "logo": "qwen",
282
+ "open": true,
283
+ "overall": 0.37818169333333335,
284
+ "t2s": 0.55666,
285
+ "i2s": 0.19970338666666668,
286
+ "indoor": 0.26629816,
287
+ "outdoor": 0.166406,
288
+ "score": 0.55666,
289
+ "cost": 0.49154169999999997
290
+ },
291
+ {
292
+ "key": "qwen-on",
293
+ "name": "Qwen 3.8 27B (thinking on)",
294
+ "logo": "qwen",
295
+ "open": true,
296
+ "overall": 0.37094399333333333,
297
+ "t2s": 0.51571,
298
+ "i2s": 0.2261779866666667,
299
+ "indoor": 0.29651112,
300
+ "outdoor": 0.19101142,
301
+ "score": 0.51571,
302
+ "cost": 0.3344125
303
+ },
304
+ {
305
+ "key": "glm",
306
+ "name": "GLM-5.3 Flash (max)",
307
+ "logo": "zai",
308
+ "open": true,
309
+ "overall": 0.4147088666666666,
310
+ "t2s": 0.5094299999999999,
311
+ "i2s": 0.3199877333333333,
312
+ "indoor": 0.46533411999999996,
313
+ "outdoor": 0.24731454,
314
+ "score": 0.5094299999999999,
315
+ "cost": 0.710521125
316
+ },
317
+ {
318
+ "key": "gemma-on",
319
+ "name": "Gemma 4 31B (thinking on)",
320
+ "logo": "gemini",
321
+ "open": true,
322
+ "overall": 0.29812257999999997,
323
+ "t2s": 0.470705,
324
+ "i2s": 0.12554015999999998,
325
+ "indoor": 0.12704028,
326
+ "outdoor": 0.1247901,
327
+ "score": 0.470705,
328
+ "cost": 0.0253001
329
+ },
330
+ {
331
+ "key": "gemma-off",
332
+ "name": "Gemma 4 31B (thinking off)",
333
+ "logo": "gemini",
334
+ "open": true,
335
+ "overall": 0.28498897333333334,
336
+ "t2s": 0.45494,
337
+ "i2s": 0.11503794666666667,
338
+ "indoor": 0.11239600000000001,
339
+ "outdoor": 0.11635892,
340
+ "score": 0.45494,
341
+ "cost": 0.0151612
342
+ },
343
+ {
344
+ "key": "inkling",
345
+ "name": "Inkling (high)",
346
+ "logo": "thinkingmachines",
347
+ "open": false,
348
+ "overall": 0.31150237333333336,
349
+ "t2s": 0.42498500000000006,
350
+ "i2s": 0.19801974666666666,
351
+ "indoor": 0.1690288,
352
+ "outdoor": 0.21251522,
353
+ "score": 0.42498500000000006,
354
+ "cost": 0.20252835
355
+ },
356
+ {
357
+ "key": "deepseek",
358
+ "name": "DeepSeek V4.1 Flash (high)",
359
+ "logo": "deepseek",
360
+ "open": true,
361
+ "overall": 0.22658958,
362
+ "t2s": 0.24269,
363
+ "i2s": 0.21048916,
364
+ "indoor": 0.18933412,
365
+ "outdoor": 0.22106668,
366
+ "score": 0.24269,
367
+ "cost": 0.193865
368
+ }
369
+ ],
370
+ "i2s": [
371
+ {
372
+ "key": "gemini",
373
+ "name": "Gemini 3.8 Flash (high)",
374
+ "logo": "gemini",
375
+ "open": false,
376
+ "overall": 0.6189750266666667,
377
+ "t2s": 0.65689,
378
+ "i2s": 0.5810600533333334,
379
+ "indoor": 0.71653332,
380
+ "outdoor": 0.5133234200000001,
381
+ "score": 0.5810600533333334,
382
+ "cost": 1.3523245042517007
383
+ },
384
+ {
385
+ "key": "astra",
386
+ "name": "GPT-6 Astra (max)",
387
+ "logo": "openai",
388
+ "open": false,
389
+ "overall": 0.6193352866666667,
390
+ "t2s": 0.72382,
391
+ "i2s": 0.5148505733333334,
392
+ "indoor": 0.78546656,
393
+ "outdoor": 0.37954258,
394
+ "score": 0.5148505733333334,
395
+ "cost": 3.3217229090909095
396
+ },
397
+ {
398
+ "key": "opus",
399
+ "name": "Claude Opus 5 (max)",
400
+ "logo": "claude",
401
+ "open": false,
402
+ "overall": 0.5931299933333334,
403
+ "t2s": 0.7181599999999999,
404
+ "i2s": 0.46809998666666675,
405
+ "indoor": 0.61843384,
406
+ "outdoor": 0.39293306,
407
+ "score": 0.46809998666666675,
408
+ "cost": 4.459671546394558
409
+ },
410
+ {
411
+ "key": "fable",
412
+ "name": "Claude Fable 5.1 (max)",
413
+ "logo": "claude",
414
+ "open": false,
415
+ "overall": 0.60604898,
416
+ "t2s": 0.78775,
417
+ "i2s": 0.4243479600000001,
418
+ "indoor": 0.53523332,
419
+ "outdoor": 0.36890528000000006,
420
+ "score": 0.4243479600000001,
421
+ "cost": 3.0888859328063245
422
+ },
423
+ {
424
+ "key": "sol",
425
+ "name": "GPT-5.6 Sol (high)",
426
+ "logo": "openai",
427
+ "open": false,
428
+ "overall": 0.5499994666666667,
429
+ "t2s": 0.70704,
430
+ "i2s": 0.3929589333333333,
431
+ "indoor": 0.49742972,
432
+ "outdoor": 0.34072354,
433
+ "score": 0.3929589333333333,
434
+ "cost": 1.8773877185185188
435
+ },
436
+ {
437
+ "key": "muse",
438
+ "name": "Muse Spark 1.3 (medium)",
439
+ "logo": "meta",
440
+ "open": false,
441
+ "overall": 0.5022053666666666,
442
+ "t2s": 0.646195,
443
+ "i2s": 0.35821573333333334,
444
+ "indoor": 0.63253332,
445
+ "outdoor": 0.22105694,
446
+ "score": 0.35821573333333334,
447
+ "cost": 0.07172788461538461
448
+ },
449
+ {
450
+ "key": "glm",
451
+ "name": "GLM-5.3 Flash (max)",
452
+ "logo": "zai",
453
+ "open": true,
454
+ "overall": 0.4147088666666666,
455
+ "t2s": 0.5094299999999999,
456
+ "i2s": 0.3199877333333333,
457
+ "indoor": 0.46533411999999996,
458
+ "outdoor": 0.24731454,
459
+ "score": 0.3199877333333333,
460
+ "cost": 0.09209057222222222
461
+ },
462
+ {
463
+ "key": "qwen-on",
464
+ "name": "Qwen 3.8 27B (thinking on)",
465
+ "logo": "qwen",
466
+ "open": true,
467
+ "overall": 0.37094399333333333,
468
+ "t2s": 0.51571,
469
+ "i2s": 0.2261779866666667,
470
+ "indoor": 0.29651112,
471
+ "outdoor": 0.19101142,
472
+ "score": 0.2261779866666667,
473
+ "cost": 0.2605628115151515
474
+ },
475
+ {
476
+ "key": "deepseek",
477
+ "name": "DeepSeek V4.1 Flash (high)",
478
+ "logo": "deepseek",
479
+ "open": true,
480
+ "overall": 0.22658958,
481
+ "t2s": 0.24269,
482
+ "i2s": 0.21048916,
483
+ "indoor": 0.18933412,
484
+ "outdoor": 0.22106668,
485
+ "score": 0.21048916,
486
+ "cost": 0.054194520202020195
487
+ },
488
+ {
489
+ "key": "qwen-off",
490
+ "name": "Qwen 3.8 27B (thinking off)",
491
+ "logo": "qwen",
492
+ "open": true,
493
+ "overall": 0.37818169333333335,
494
+ "t2s": 0.55666,
495
+ "i2s": 0.19970338666666668,
496
+ "indoor": 0.26629816,
497
+ "outdoor": 0.166406,
498
+ "score": 0.19970338666666668,
499
+ "cost": 0.27672816366207137
500
+ },
501
+ {
502
+ "key": "inkling",
503
+ "name": "Inkling (high)",
504
+ "logo": "thinkingmachines",
505
+ "open": false,
506
+ "overall": 0.31150237333333336,
507
+ "t2s": 0.42498500000000006,
508
+ "i2s": 0.19801974666666666,
509
+ "indoor": 0.1690288,
510
+ "outdoor": 0.21251522,
511
+ "score": 0.19801974666666666,
512
+ "cost": 0.7541276882539681
513
+ },
514
+ {
515
+ "key": "grok",
516
+ "name": "Grok 4.6 (high)",
517
+ "logo": "grok",
518
+ "open": false,
519
+ "overall": 0.37567506,
520
+ "t2s": 0.56721,
521
+ "i2s": 0.18414012,
522
+ "indoor": 0.1968008,
523
+ "outdoor": 0.17780978,
524
+ "score": 0.18414012,
525
+ "cost": 0.2634673259259259
526
+ },
527
+ {
528
+ "key": "gemma-on",
529
+ "name": "Gemma 4 31B (thinking on)",
530
+ "logo": "gemini",
531
+ "open": true,
532
+ "overall": 0.29812257999999997,
533
+ "t2s": 0.470705,
534
+ "i2s": 0.12554015999999998,
535
+ "indoor": 0.12704028,
536
+ "outdoor": 0.1247901,
537
+ "score": 0.12554015999999998,
538
+ "cost": 0.028434150793650796
539
+ },
540
+ {
541
+ "key": "gemma-off",
542
+ "name": "Gemma 4 31B (thinking off)",
543
+ "logo": "gemini",
544
+ "open": true,
545
+ "overall": 0.28498897333333334,
546
+ "t2s": 0.45494,
547
+ "i2s": 0.11503794666666667,
548
+ "indoor": 0.11239600000000001,
549
+ "outdoor": 0.11635892,
550
+ "score": 0.11503794666666667,
551
+ "cost": 0.01913622993197279
552
+ }
553
+ ]
554
+ }
index.html CHANGED
The diff for this file is too large to render. See raw diff