Skip to main content

HTML_RUNTIME

Constant HTML_RUNTIME 

Source
const HTML_RUNTIME: &str = r##";
const svg=document.querySelector("#arena"),NS=svg.namespaceURI,slider=document.querySelector("#slider"),layerSelect=document.querySelector("#layer");let index=0,timer=null;
document.querySelector("#fixture").textContent=DATA.fixture;slider.max=String(DATA.snapshots.length-1);
const layers=DATA.scene.layers.map(x=>x.id);for(const layer of DATA.scene.layers){const o=document.createElement("option");o.value=layer.id;o.textContent=`Layer ${layer.id} · ${layer.slug}`;layerSelect.append(o)}
const el=(name,attrs={})=>{const n=document.createElementNS(NS,name);for(const[k,v]of Object.entries(attrs))n.setAttribute(k,String(v));return n};
function allPoints(layer){const p=[];for(const s of DATA.snapshots)for(const a of s.actors)if(a.layer===layer)p.push([a.x,a.y]);for(const o of DATA.scene.obstacles){if(o.layer!==layer)continue;if(o.kind==="segment")p.push([o.start.x,o.start.y],[o.end.x,o.end.y]);else{for(const q of o.exterior)p.push([q.x,q.y]);for(const h of o.holes)for(const q of h)p.push([q.x,q.y])}}return p}
function bounds(layer){const p=allPoints(layer);let xs=p.map(q=>q[0]),ys=p.map(q=>q[1]);if(!p.length)xs=ys=[0];let minX=Math.min(...xs),maxX=Math.max(...xs),minY=Math.min(...ys),maxY=Math.max(...ys);if(maxX-minX<10){const m=(minX+maxX)/2;minX=m-5;maxX=m+5}if(maxY-minY<10){const m=(minY+maxY)/2;minY=m-5;maxY=m+5}const pad=Math.max(maxX-minX,maxY-minY)*.12;return[minX-pad,maxX+pad,minY-pad,maxY+pad]}
function render(){const s=DATA.snapshots[index],layer=Number(layerSelect.value),[minX,maxX,minY,maxY]=bounds(layer),W=1000,H=620,pad=55,iw=W-pad*2,ih=H-pad*2,scale=Math.min(iw/(maxX-minX),ih/(maxY-minY)),mx=x=>pad+(x-minX)*scale,my=y=>H-pad-(y-minY)*scale;svg.setAttribute("viewBox",`0 0 ${W} ${H}`);svg.replaceChildren();
 const defs=el("defs"),marker=el("marker",{id:"arrow",viewBox:"0 0 10 10",refX:8,refY:5,markerWidth:5,markerHeight:5,orient:"auto-start-reverse"});marker.append(el("path",{d:"M 0 0 L 10 5 L 0 10 z",fill:"context-stroke"}));defs.append(marker);svg.append(defs);
 const span=Math.max(maxX-minX,maxY-minY),step=span<=12?1:span<=30?2:5;for(let x=Math.ceil(minX/step)*step;x<=maxX;x+=step){svg.append(el("line",{x1:mx(x),y1:pad,x2:mx(x),y2:H-pad,class:x===0?"axis":"grid"}));const t=el("text",{x:mx(x)+3,y:H-pad+16,class:"coord"});t.textContent=x;svg.append(t)}for(let y=Math.ceil(minY/step)*step;y<=maxY;y+=step){svg.append(el("line",{x1:pad,y1:my(y),x2:W-pad,y2:my(y),class:y===0?"axis":"grid"}));const t=el("text",{x:pad-8,y:my(y)+3,class:"coord","text-anchor":"end"});t.textContent=y;svg.append(t)}
 for(const o of DATA.scene.obstacles){if(o.layer!==layer)continue;if(o.kind==="segment")svg.append(el("line",{x1:mx(o.start.x),y1:my(o.start.y),x2:mx(o.end.x),y2:my(o.end.y),class:"obstacle"}));else{const d=o.exterior.map((p,i)=>`${i?"L":"M"} ${mx(p.x)} ${my(p.y)}`).join(" ")+" Z";svg.append(el("path",{d,class:"obstacle","fill-rule":"evenodd"}))}}
 const current=s.actors.filter(a=>a.layer===layer);for(const actor of current){const history=DATA.snapshots.map(x=>x.actors.find(a=>a.id===actor.id)).filter(a=>a&&a.layer===layer),past=history.slice(0,index+1),future=history.slice(index);const points=a=>a.map(p=>`${mx(p.x)},${my(p.y)}`).join(" ");if(future.length>1)svg.append(el("polyline",{points:points(future),stroke:actor.color,class:"path-future"}));if(past.length>1)svg.append(el("polyline",{points:points(past),stroke:actor.color,class:"path-past","marker-end":"url(#arrow)"}));}
 for(const actor of current){const x=mx(actor.x),y=my(actor.y),g=el("g",{class:!actor.alive?"dead":!actor.active?"inactive":""});if(actor.targeted)g.append(el("circle",{cx:x,cy:y,r:20,class:"target-ring"}));if(actor.kind==="player")g.append(el("path",{d:`M ${x} ${y-12} L ${x+11} ${y} L ${x} ${y+12} L ${x-11} ${y} Z`,fill:actor.color,stroke:"#fff","stroke-width":1.5}));else g.append(el("circle",{cx:x,cy:y,r:11,fill:actor.color,stroke:"#fff","stroke-width":1.5}));g.append(el("line",{x1:x,y1:y,x2:x+Math.cos(actor.heading)*24,y2:y-Math.sin(actor.heading)*24,stroke:actor.color,"stroke-width":3}));if(!actor.alive){g.append(el("line",{x1:x-12,y1:y-12,x2:x+12,y2:y+12,stroke:"#fff","stroke-width":3}));g.append(el("line",{x1:x+12,y1:y-12,x2:x-12,y2:y+12,stroke:"#fff","stroke-width":3}))}const label=el("text",{x:x+16,y:y-15,class:"label"});label.textContent=`${actor.label}${actor.active?"":" · inactive"}`;g.append(label);const coord=el("text",{x:x+16,y:y+2,class:"coord"});coord.textContent=`(${actor.x.toFixed(2)}, ${actor.y.toFixed(2)}) h=${actor.heading.toFixed(2)}`;g.append(coord);svg.append(g)}
 document.querySelector("#time").textContent=`${(s.timeMs/1000).toFixed(3)}s · seq ${s.sequence}`;document.querySelector("#event").textContent=s.event;document.querySelector("#source").textContent=s.source;document.querySelector("#target").textContent=s.target;document.querySelector("#pull").textContent=`${s.pull} / ${s.group??"—"}`;document.querySelector("#step").textContent=`${index+1} / ${DATA.snapshots.length}`;slider.value=String(index);document.querySelector("#prev").disabled=index===0;document.querySelector("#next").disabled=index===DATA.snapshots.length-1;document.querySelectorAll(".event").forEach((n,i)=>n.classList.toggle("current",i===index));document.querySelector(".event.current")?.scrollIntoView({block:"nearest"})}
function go(i){index=Math.max(0,Math.min(DATA.snapshots.length-1,i));render()}function stop(){if(timer){clearInterval(timer);timer=null;document.querySelector("#play").textContent="Play"}}
for(const[sIndex,s]of DATA.snapshots.entries()){const li=document.createElement("li");li.className="event";li.innerHTML=`<div class="event-time">${(s.timeMs/1000).toFixed(3)}s</div><div><div class="event-name"></div><div class="event-source"></div></div>`;li.querySelector(".event-name").textContent=s.event;li.querySelector(".event-source").textContent=`${s.source} · pull ${s.pull}`;li.onclick=()=>{stop();go(sIndex)};document.querySelector("#events").append(li)}
document.querySelector("#prev").onclick=()=>{stop();go(index-1)};document.querySelector("#next").onclick=()=>{stop();go(index+1)};document.querySelector("#play").onclick=()=>{if(timer){stop();return}document.querySelector("#play").textContent="Pause";if(index===DATA.snapshots.length-1)go(0);timer=setInterval(()=>{if(index===DATA.snapshots.length-1){stop();return}go(index+1)},700)};slider.oninput=e=>{stop();go(Number(e.target.value))};layerSelect.onchange=render;document.onkeydown=e=>{if(e.key==="ArrowLeft"){stop();go(index-1)}if(e.key==="ArrowRight"){stop();go(index+1)}};render();
</script></body></html>
"##;