react-action-debug / index.html
yxma's picture
add mocap time-shift check: images fixed, overlay drawn from pose[frame+shift]
112f82f verified
Raw History Blame Contribute Delete
6.63 kB
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>React action debug — episode_006_seg00</title>
<style>
:root{--bg:#0e1116;--panel:#161b22;--line:#30363d;--fg:#e6edf3;--dim:#8b949e;
--L:#58a6ff;--R:#ff9d4d;--hi:#f85149}
*{box-sizing:border-box} body{margin:0;background:var(--bg);color:var(--fg);
font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace}
header{padding:10px 14px;border-bottom:1px solid var(--line)}
h1{margin:0;font-size:15px;font-weight:600}
.sub{color:var(--dim);font-size:12px;margin-top:2px}
#stage{background:#000;text-align:center;border-bottom:1px solid var(--line)}
#stage img{max-width:100%;display:block;margin:0 auto;image-rendering:auto}
.bar{display:flex;gap:8px;align-items:center;padding:8px 14px;flex-wrap:wrap;
border-bottom:1px solid var(--line)}
button{background:var(--panel);color:var(--fg);border:1px solid var(--line);
border-radius:5px;padding:4px 10px;cursor:pointer;font:inherit}
button:hover{border-color:var(--dim)} button.on{border-color:var(--L);color:var(--L)}
input[type=range]{flex:1;min-width:220px;accent-color:var(--L)}
.tag{color:var(--dim)} .val{font-variant-numeric:tabular-nums}
.readout{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
gap:6px 16px;padding:8px 14px;border-bottom:1px solid var(--line)}
.readout div{white-space:nowrap}
.L{color:var(--L)} .R{color:var(--R)}
canvas{display:block;width:100%;height:120px;border-bottom:1px solid var(--line)}
.cap{padding:3px 14px;color:var(--dim);font-size:11px}
kbd{background:var(--panel);border:1px solid var(--line);border-radius:3px;padding:0 4px}
</style></head><body>
<header>
<h1>action debug · motherboard / 2026-09-11 / episode_006_seg00</h1>
<div class="sub" id="hdr">loading…</div>
<div class="sub"><a href="shift.html" style="color:#58a6ff">→ mocap 时间偏移检查(±8 帧滑块)</a></div>
</header>
<div id="stage"><img id="frame" alt=""></div>
<div class="bar">
<button id="play">▶ 播放</button>
<button id="prev">◀ 帧</button><button id="next">帧 ▶</button>
<input id="slider" type="range" min="0" value="0">
<span class="tag">row</span><span class="val" id="row">—</span>
<button id="stepmode" class="on">步长 2 (15Hz)</button>
<button id="jump">跳到样本窗口</button>
</div>
<div class="readout">
<div><span class="tag">force</span> <span class="L">L <b class="val" id="fl">—</b> N</span> · <span class="R">R <b class="val" id="fr">—</b> N</span></div>
<div><span class="tag">penetration F/k</span> <span class="L"><b class="val" id="pl">—</b></span> · <span class="R"><b class="val" id="pr">—</b></span> mm</div>
<div><span class="tag">Δ 法向 (1帧)</span> <span class="L"><b class="val" id="d1l">—</b></span> · <span class="R"><b class="val" id="d1r">—</b></span> mm</div>
<div><span class="tag">Δ 法向 (2帧)</span> <span class="L"><b class="val" id="d2l">—</b></span> · <span class="R"><b class="val" id="d2r">—</b></span> mm</div>
</div>
<canvas id="cf"></canvas><div class="cap">force (N) — 蓝 L / 橙 R,红带为样本窗口 rows 1592–1622</div>
<canvas id="cd"></canvas><div class="cap">Δ action 沿传感器法向 (mm per 2 frames) — 训练样本用的就是这个步长</div>
<div class="cap"><kbd>←</kbd><kbd>→</kbd> 单帧 · <kbd>空格</kbd> 播放/暂停 · <kbd>Home</kbd> 回到开头</div>
<script>
const $=id=>document.getElementById(id);
let D=null,i=0,playing=false,step=2,timer=null;
const pad=n=>String(n).padStart(4,'0');
fetch('data.json').then(r=>r.json()).then(d=>{
D=d; $('slider').max=d.n-1;
$('hdr').textContent=`rows ${d.start_row}–${d.start_row+d.n-1} · ${d.n} 帧 @ ${d.fps}fps · k=${d.stiffness_n_per_mm} N/mm · 样本窗口 rows 1592–1622 (步长2)`;
draw(); show(0);
});
function show(k){
i=Math.max(0,Math.min(D.n-1,k));
$('frame').src='frames/f'+pad(i+1)+'.jpg';
$('slider').value=i; $('row').textContent=D.rows[i];
const f=(a,n)=>a[i]==null?'—':a[i].toFixed(n);
$('fl').textContent=f(D.force_left,2); $('fr').textContent=f(D.force_right,2);
$('pl').textContent=f(D.pen_left,2); $('pr').textContent=f(D.pen_right,2);
$('d1l').textContent=f(D.d1_left,2); $('d1r').textContent=f(D.d1_right,2);
$('d2l').textContent=f(D.d2_left,2); $('d2r').textContent=f(D.d2_right,2);
draw();
}
function series(c,arrs,cols,zero){
const ctx=c.getContext('2d'),W=c.width=c.clientWidth*devicePixelRatio,
H=c.height=120*devicePixelRatio;
ctx.clearRect(0,0,W,H);
let lo=Infinity,hi=-Infinity;
arrs.forEach(a=>a.forEach(v=>{if(v!=null){lo=Math.min(lo,v);hi=Math.max(hi,v);}}));
if(zero){lo=Math.min(lo,0);hi=Math.max(hi,0);}
if(!isFinite(lo)){lo=0;hi=1;} if(hi-lo<1e-6)hi=lo+1;
const pad=(hi-lo)*0.08; lo-=pad; hi+=pad;
const X=k=>k/(D.n-1)*W, Y=v=>H-(v-lo)/(hi-lo)*H;
// 样本窗口高亮
ctx.fillStyle='rgba(248,81,73,.16)';
ctx.fillRect(X(D.highlight[0]),0,X(D.highlight[1])-X(D.highlight[0]),H);
if(lo<0&&hi>0){ctx.strokeStyle='#30363d';ctx.lineWidth=devicePixelRatio;
ctx.beginPath();ctx.moveTo(0,Y(0));ctx.lineTo(W,Y(0));ctx.stroke();}
arrs.forEach((a,n)=>{ctx.strokeStyle=cols[n];ctx.lineWidth=1.4*devicePixelRatio;
ctx.beginPath();let up=false;
a.forEach((v,k)=>{if(v==null){up=false;return;}
up?ctx.lineTo(X(k),Y(v)):ctx.moveTo(X(k),Y(v));up=true;});
ctx.stroke();});
ctx.strokeStyle='#e6edf3';ctx.lineWidth=devicePixelRatio;
ctx.beginPath();ctx.moveTo(X(i),0);ctx.lineTo(X(i),H);ctx.stroke();
}
function draw(){ if(!D)return;
series($('cf'),[D.force_left,D.force_right],['#58a6ff','#ff9d4d'],true);
series($('cd'),[D.d2_left,D.d2_right],['#58a6ff','#ff9d4d'],true); }
$('slider').oninput=e=>show(+e.target.value);
$('prev').onclick=()=>show(i-step); $('next').onclick=()=>show(i+step);
$('stepmode').onclick=e=>{step=step===1?2:1;
e.target.textContent=step===2?'步长 2 (15Hz)':'步长 1 (30Hz)';e.target.classList.toggle('on',step===2);};
$('jump').onclick=()=>show(D.highlight[0]);
$('play').onclick=()=>{playing=!playing;$('play').textContent=playing?'⏸ 暂停':'▶ 播放';
if(playing){timer=setInterval(()=>{if(i>=D.n-1){show(0);}else show(i+step);},1000/30*step);}
else clearInterval(timer);};
addEventListener('keydown',e=>{
if(e.key==='ArrowLeft'){show(i-step);e.preventDefault();}
if(e.key==='ArrowRight'){show(i+step);e.preventDefault();}
if(e.key==='Home'){show(0);e.preventDefault();}
if(e.code==='Space'){$('play').click();e.preventDefault();}});
addEventListener('resize',draw);
</script></body></html>