+
+
Compare rollouts
Select runs, inspect an actor’s contribution, then open the recorded calls and results. Each column retains its own trace and annotations.
+
Query saved eval jobs · Workshop backend
r.id)} stateKey="runebench-research" onInspect={(selection:any)=>{
+ const index=options.findIndex((r:any)=>r.traceDigest===selection.selector?.trace_digest);
+ if(index<0)throw new Error('This query result has no matching retained RuneBench replay. Its exact source remains readable.');
+ const run=options[index];const side=ids[1]===run.id?1:0;
+ setIds((v:string[])=>v.map((id,i)=>i===side?run.id:id));
+ setFocus((v:any[])=>v.map((x,i)=>i===side?{selector:selection.selector,revision:(x?.revision||0)+1}:x));
+ setPositions((v:any[])=>v.map((x,i)=>i===side?{...x,actor:'all'}:x));
+ }}/>{ids.map((id,side)=>)}
+
{mode==='decision'?'Selecting a decision aligns the same actor and decision number in the other run, when present. These are ordinal matches, not equivalent game states.':mode==='elapsed'?'Moving either cursor seeks both runs to the same elapsed time.':'Each replay cursor moves independently.'} One episode per arm; engine RNG is unseeded.
+
Actor contribution differences
| Actor | {runs.map((r:any)=>{armLabel(r)} XP | )}Change (right − left) | Evidence |
{runs[0].actors.map((a:any)=>| {a.id} · {a.team} · {a.role} | {runs.map((r:any)=>{r.scores[a.id]??'—'} | )}{runs[1].scores[a.id]-runs[0].scores[a.id]} | |
)}
+
setTerm(e.target.value)}/>{hits.length} matching events across these two runs
+
{hits.map(({side,event}:any,i:number)=>
)}{!hits.length&&
No matching recorded events.
}
+
{runs.map((r:any,side:number)=>seek(side,ms,actor,decision)}/>)}
+
;
+}
+function ComparisonPane({side,run:r,data,fallback,position,focus,onSeek}:any){
+ const evidence=useTraceEvidence(data?.annotationService,r.id,fallback);
+ const [edit,setEdit]=useState