{(['inputs', 'cena', 'xr', 'macro', 'logs'] as Tab[]).map((t) => (
))}
{tab === 'inputs' && (
fake: {isFakeActive() ? 'ATIVO' : 'inativo'}
{(['left', 'right'] as const).map((h) => {
const s = fakeInput[h];
const grabbing = s.gripValue > 0.7;
const p = [s.gripWorld.elements[12], s.gripWorld.elements[13], s.gripWorld.elements[14]];
return (
{h.toUpperCase()} {grabbing && '✊ GRAB'}
grip: {s.gripValue.toFixed(2)} trg: {s.triggerValue.toFixed(2)}
pose: {p.map((v) => v.toFixed(2)).join(', ')}
);
})}
Atalhos teclado
Q/W = grip L/R · A/S = trigger L/R
1/2 = ativar mão · setas = mover
Shift+↑/↓ = Z (frente/trás) · R = reset
)}
{tab === 'cena' && (
n.toFixed(3)).join(' × ')} />
n.toFixed(3)).join(', ')} />
n.toFixed(1)).join(', ')} />
)}
{tab === 'xr' && (
)}
{tab === 'macro' && (
fase: {macroState.phase}
{macroState.phase !== 'recording' ? (
) : (
)}
{macroState.phase !== 'playing' ? (
) : (
)}
{
const f = e.target.files?.[0];
if (!f) return;
try {
const macro = await loadMacroFromFile(f);
macroState.current = macro;
tick((n) => n + 1);
} catch { /* noop */ }
e.target.value = '';
}}
/>
{macroState.current && (
{macroState.current.name}
frames: {macroState.current.frames.length}
duração: {(macroState.current.duration / 1000).toFixed(1)}s
)}
{macroState.phase === 'recording' && (
gravando… {macroState.rec.length} frames
)}
Use os atalhos do teclado para encenar o bug; depois clique em Parar e Salvar.
No próximo round, Carregar o JSON e clicar em Play reproduz exatamente.
)}
{tab === 'logs' && (
setLogFilter(e.target.value)}
placeholder="filtro… (ex: [XR])"
className="w-full rounded border border-green-500/30 bg-black/60 px-1 py-0.5 text-green-300"
/>
{logBuffer
.filter((l) => !logFilter || l.msg.toLowerCase().includes(logFilter.toLowerCase()))
.slice(-80).reverse()
.map((l, i) => (
{l.msg}
))}
{logBuffer.length === 0 &&
sem logs ainda…
}
)}