File size: 6,625 Bytes
189b8c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112f82f
189b8c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<!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>