mudra-skills 3.1.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/skills/mudra-master/SKILL.md +1 -1
- package/skills/mudra-master/mudra-preview/SKILL.md +9 -9
- package/skills/mudra-master/mudra-preview/assets/ar-menu.html +1 -1
- package/skills/mudra-master/mudra-preview/assets/document-scroller.html +2 -2
- package/skills/mudra-master/mudra-preview/assets/drum-machine.html +1 -1
- package/skills/mudra-master/mudra-preview/assets/emg-visualizer.html +6 -6
- package/skills/mudra-master/mudra-preview/assets/generative-art.html +1 -1
- package/skills/mudra-master/mudra-preview/assets/gesture-assistant.html +1 -1
- package/skills/mudra-master/mudra-preview/assets/gesture-speech.html +1 -1
- package/skills/mudra-master/mudra-preview/assets/hands-free-desktop.html +1 -1
- package/skills/mudra-master/mudra-preview/assets/model-rotator.html +1 -1
- package/skills/mudra-master/mudra-preview/assets/mudra-duel.html +48 -48
- package/skills/mudra-master/mudra-preview/assets/mudra-monitor.html +55 -55
- package/skills/mudra-master/mudra-preview/assets/mudra-ultimate-template.html +13 -13
- package/skills/mudra-master/mudra-preview/assets/music-sequencer.html +1 -1
- package/skills/mudra-master/mudra-preview/assets/neural-pong.html +1 -1
- package/skills/mudra-master/mudra-preview/assets/neural-snake.html +1 -1
- package/skills/mudra-master/mudra-preview/assets/presentation-controller.html +1 -1
- package/skills/mudra-master/mudra-preview/assets/pressure-painter.html +4 -4
- package/skills/mudra-master/mudra-preview/assets/runner.html +11 -11
- package/skills/mudra-master/mudra-preview/assets/smart-home.html +1 -1
- package/skills/mudra-master/mudra-preview/assets/space-invaders.html +1 -1
- package/skills/mudra-master/mudra-preview/assets/waterful-ring-toss.html +1 -1
- package/skills/mudra-master/mudra-preview/references/agent_protocol.json +34 -34
- package/skills/mudra-master/mudra-preview/references/prompt.md +182 -182
- package/skills/mudra-master/mudra-xr/SKILL.md +7 -7
- package/skills/mudra-master/mudra-xr/references/agent_protocol.json +20 -20
- package/skills/mudra-master/mudra-xr/references/prompt.md +28 -28
- package/skills/mudra-preview/SKILL.md +9 -9
- package/skills/mudra-preview/assets/ar-menu.html +1 -1
- package/skills/mudra-preview/assets/document-scroller.html +2 -2
- package/skills/mudra-preview/assets/drum-machine.html +1 -1
- package/skills/mudra-preview/assets/emg-visualizer.html +6 -6
- package/skills/mudra-preview/assets/generative-art.html +1 -1
- package/skills/mudra-preview/assets/gesture-assistant.html +1 -1
- package/skills/mudra-preview/assets/gesture-speech.html +1 -1
- package/skills/mudra-preview/assets/hands-free-desktop.html +1 -1
- package/skills/mudra-preview/assets/model-rotator.html +1 -1
- package/skills/mudra-preview/assets/mudra-duel.html +48 -48
- package/skills/mudra-preview/assets/mudra-monitor.html +55 -55
- package/skills/mudra-preview/assets/mudra-ultimate-template.html +13 -13
- package/skills/mudra-preview/assets/music-sequencer.html +1 -1
- package/skills/mudra-preview/assets/neural-pong.html +1 -1
- package/skills/mudra-preview/assets/neural-snake.html +1 -1
- package/skills/mudra-preview/assets/presentation-controller.html +1 -1
- package/skills/mudra-preview/assets/pressure-painter.html +4 -4
- package/skills/mudra-preview/assets/runner.html +11 -11
- package/skills/mudra-preview/assets/smart-home.html +1 -1
- package/skills/mudra-preview/assets/space-invaders.html +1 -1
- package/skills/mudra-preview/assets/waterful-ring-toss.html +1 -1
- package/skills/mudra-preview/references/agent_protocol.json +34 -34
- package/skills/mudra-preview/references/prompt.md +182 -182
- package/skills/mudra-xr/SKILL.md +7 -7
- package/skills/mudra-xr/references/agent_protocol.json +20 -20
- package/skills/mudra-xr/references/prompt.md +28 -28
|
@@ -193,24 +193,24 @@
|
|
|
193
193
|
.button-stat-val { font-size: 1.1rem; font-weight: 700; color: var(--primary); }
|
|
194
194
|
.button-stat-label { font-size: 0.68rem; color: var(--text-secondary); margin-top: 2px; }
|
|
195
195
|
|
|
196
|
-
/* ──
|
|
197
|
-
.
|
|
198
|
-
.
|
|
199
|
-
.
|
|
200
|
-
.
|
|
196
|
+
/* ── EMG Panel (wide) ── */
|
|
197
|
+
.emg-panel { grid-column: 1 / 3; grid-row: 2; }
|
|
198
|
+
.emg-graph-wrap { position: relative; height: 150px; margin-top: 4px; }
|
|
199
|
+
.emg-canvas { width: 100%; height: 100%; display: block; border-radius: 8px; background: var(--card-inner); }
|
|
200
|
+
.emg-legend {
|
|
201
201
|
display: flex; gap: 16px; margin-top: 8px; font-size: 0.75rem;
|
|
202
202
|
}
|
|
203
|
-
.
|
|
204
|
-
.
|
|
205
|
-
.
|
|
203
|
+
.emg-legend-item { display: flex; align-items: center; gap: 5px; }
|
|
204
|
+
.emg-legend-dot { width: 8px; height: 8px; border-radius: 50%; }
|
|
205
|
+
.emg-stats {
|
|
206
206
|
display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 8px;
|
|
207
207
|
}
|
|
208
|
-
.
|
|
208
|
+
.emg-stat {
|
|
209
209
|
padding: 6px 8px; border-radius: 8px; background: var(--card-inner);
|
|
210
210
|
border: 1px solid var(--border-dim); text-align: center;
|
|
211
211
|
}
|
|
212
|
-
.
|
|
213
|
-
.
|
|
212
|
+
.emg-stat-val { font-size: 1rem; font-weight: 700; font-family: 'JetBrains Mono', monospace; }
|
|
213
|
+
.emg-stat-label { font-size: 0.68rem; color: var(--text-secondary); margin-top: 2px; }
|
|
214
214
|
|
|
215
215
|
/* ── Navigation / Motion Panel ── */
|
|
216
216
|
.motion-panel { grid-column: 3; grid-row: 2; }
|
|
@@ -409,7 +409,7 @@
|
|
|
409
409
|
.dashboard {
|
|
410
410
|
grid-template-columns: 1fr 1fr;
|
|
411
411
|
}
|
|
412
|
-
.
|
|
412
|
+
.emg-panel { grid-column: 1 / 3; }
|
|
413
413
|
.motion-panel { grid-column: 1; grid-row: 3; }
|
|
414
414
|
.imu-panel { grid-column: 1 / 3; grid-row: 4; }
|
|
415
415
|
.controls-panel { grid-column: 2; grid-row: 3; }
|
|
@@ -417,7 +417,7 @@
|
|
|
417
417
|
}
|
|
418
418
|
@media (max-width: 700px) {
|
|
419
419
|
.dashboard { grid-template-columns: 1fr; }
|
|
420
|
-
.
|
|
420
|
+
.emg-panel, .imu-panel, .log-panel { grid-column: 1; }
|
|
421
421
|
.motion-panel, .controls-panel { grid-column: 1; }
|
|
422
422
|
.imu-graphs { grid-template-columns: 1fr; }
|
|
423
423
|
}
|
|
@@ -518,29 +518,29 @@
|
|
|
518
518
|
</div>
|
|
519
519
|
</section>
|
|
520
520
|
|
|
521
|
-
<!--
|
|
522
|
-
<section class="card
|
|
523
|
-
<div class="card-title"><span class="icon">⚔</span>
|
|
524
|
-
<div class="
|
|
525
|
-
<canvas class="
|
|
521
|
+
<!-- EMG Panel -->
|
|
522
|
+
<section class="card emg-panel">
|
|
523
|
+
<div class="card-title"><span class="icon">⚔</span> EMG — Surface Nerve Conductance</div>
|
|
524
|
+
<div class="emg-graph-wrap">
|
|
525
|
+
<canvas class="emg-canvas" id="sncCanvas"></canvas>
|
|
526
526
|
</div>
|
|
527
|
-
<div class="
|
|
528
|
-
<div class="
|
|
529
|
-
<div class="
|
|
530
|
-
<div class="
|
|
527
|
+
<div class="emg-legend">
|
|
528
|
+
<div class="emg-legend-item"><div class="emg-legend-dot" style="background:#77eae9"></div>Ulnar</div>
|
|
529
|
+
<div class="emg-legend-item"><div class="emg-legend-dot" style="background:#a78bfa"></div>Median</div>
|
|
530
|
+
<div class="emg-legend-item"><div class="emg-legend-dot" style="background:#f472b6"></div>Radial</div>
|
|
531
531
|
</div>
|
|
532
|
-
<div class="
|
|
533
|
-
<div class="
|
|
534
|
-
<div class="
|
|
535
|
-
<div class="
|
|
532
|
+
<div class="emg-stats">
|
|
533
|
+
<div class="emg-stat">
|
|
534
|
+
<div class="emg-stat-val" id="sncUlnar" style="color:#77eae9">0.000</div>
|
|
535
|
+
<div class="emg-stat-label">Ulnar</div>
|
|
536
536
|
</div>
|
|
537
|
-
<div class="
|
|
538
|
-
<div class="
|
|
539
|
-
<div class="
|
|
537
|
+
<div class="emg-stat">
|
|
538
|
+
<div class="emg-stat-val" id="sncMedian" style="color:#a78bfa">0.000</div>
|
|
539
|
+
<div class="emg-stat-label">Median</div>
|
|
540
540
|
</div>
|
|
541
|
-
<div class="
|
|
542
|
-
<div class="
|
|
543
|
-
<div class="
|
|
541
|
+
<div class="emg-stat">
|
|
542
|
+
<div class="emg-stat-val" id="sncRadial" style="color:#f472b6">0.000</div>
|
|
543
|
+
<div class="emg-stat-label">Radial</div>
|
|
544
544
|
</div>
|
|
545
545
|
</div>
|
|
546
546
|
</section>
|
|
@@ -607,9 +607,9 @@
|
|
|
607
607
|
</div>
|
|
608
608
|
</div>
|
|
609
609
|
<div class="imu-legend">
|
|
610
|
-
<div class="
|
|
611
|
-
<div class="
|
|
612
|
-
<div class="
|
|
610
|
+
<div class="emg-legend-item"><div class="emg-legend-dot" style="background:#77eae9"></div>X Axis</div>
|
|
611
|
+
<div class="emg-legend-item"><div class="emg-legend-dot" style="background:#a78bfa"></div>Y Axis</div>
|
|
612
|
+
<div class="emg-legend-item"><div class="emg-legend-dot" style="background:#f472b6"></div>Z Axis</div>
|
|
613
613
|
</div>
|
|
614
614
|
</section>
|
|
615
615
|
|
|
@@ -631,10 +631,10 @@
|
|
|
631
631
|
<span class="sig-pkts" id="pkts-pressure">0</span>
|
|
632
632
|
<div class="toggle-switch on" data-sub="pressure"></div>
|
|
633
633
|
</div>
|
|
634
|
-
<div class="toggle-row" data-signal="
|
|
635
|
-
<span class="sig-name">
|
|
636
|
-
<span class="sig-pkts" id="pkts-
|
|
637
|
-
<div class="toggle-switch on" data-sub="
|
|
634
|
+
<div class="toggle-row" data-signal="emg">
|
|
635
|
+
<span class="sig-name">emg</span>
|
|
636
|
+
<span class="sig-pkts" id="pkts-emg">0</span>
|
|
637
|
+
<div class="toggle-switch on" data-sub="emg"></div>
|
|
638
638
|
</div>
|
|
639
639
|
<div class="toggle-row" data-signal="battery">
|
|
640
640
|
<span class="sig-name">battery</span>
|
|
@@ -721,7 +721,7 @@
|
|
|
721
721
|
<button class="log-filter" data-filter="gesture">Gesture</button>
|
|
722
722
|
<button class="log-filter" data-filter="pressure">Pressure</button>
|
|
723
723
|
<button class="log-filter" data-filter="imu">IMU</button>
|
|
724
|
-
<button class="log-filter" data-filter="
|
|
724
|
+
<button class="log-filter" data-filter="emg">EMG</button>
|
|
725
725
|
<button class="log-filter" data-filter="nav">Nav</button>
|
|
726
726
|
<button class="log-filter" data-filter="cmd">Commands</button>
|
|
727
727
|
</div>
|
|
@@ -754,9 +754,9 @@ let logFilter = "all";
|
|
|
754
754
|
const pressureSamples = [];
|
|
755
755
|
|
|
756
756
|
// Subscription tracking
|
|
757
|
-
const activeSubs = new Set(["gesture", "pressure", "
|
|
757
|
+
const activeSubs = new Set(["gesture", "pressure", "emg", "battery", "navigation", "button"]);
|
|
758
758
|
const packetCounts = {};
|
|
759
|
-
const ALL_SIGNALS = ["gesture", "pressure", "
|
|
759
|
+
const ALL_SIGNALS = ["gesture", "pressure", "emg", "battery", "navigation", "button", "nav_direction", "imu_acc", "imu_gyro"];
|
|
760
760
|
const MOTION_CONFLICTS = {
|
|
761
761
|
navigation: ["nav_direction", "imu_acc", "imu_gyro"],
|
|
762
762
|
button: ["nav_direction", "imu_acc", "imu_gyro"],
|
|
@@ -773,7 +773,7 @@ const state = {
|
|
|
773
773
|
navDirection: "None",
|
|
774
774
|
imuAcc: [0, 0, 9.81],
|
|
775
775
|
imuGyro: [0, 0, 0],
|
|
776
|
-
|
|
776
|
+
emg: [0, 0, 0],
|
|
777
777
|
battery: { level: -1, charging: false }
|
|
778
778
|
};
|
|
779
779
|
|
|
@@ -837,7 +837,7 @@ function connect() {
|
|
|
837
837
|
try { ws = new WebSocket(WS_URL); } catch { setConn("err", "Invalid URL"); return; }
|
|
838
838
|
|
|
839
839
|
ws.onopen = () => {
|
|
840
|
-
setConn("on", "Connected to Mudra
|
|
840
|
+
setConn("on", "Connected to Mudra Link");
|
|
841
841
|
subscribeAll();
|
|
842
842
|
};
|
|
843
843
|
ws.onclose = () => {
|
|
@@ -957,7 +957,7 @@ function switchMode(mode) {
|
|
|
957
957
|
});
|
|
958
958
|
|
|
959
959
|
// Also re-subscribe universal ones
|
|
960
|
-
["gesture", "pressure", "
|
|
960
|
+
["gesture", "pressure", "emg", "battery"].forEach(s => {
|
|
961
961
|
if (activeSubs.has(s)) send({ command: "subscribe", signal: s });
|
|
962
962
|
});
|
|
963
963
|
|
|
@@ -995,7 +995,7 @@ function routeMessage(msg) {
|
|
|
995
995
|
else if (t === "nav_direction") onNavDirection(d);
|
|
996
996
|
else if (t === "imu_acc") onImuAcc(d);
|
|
997
997
|
else if (t === "imu_gyro") onImuGyro(d);
|
|
998
|
-
else if (t === "
|
|
998
|
+
else if (t === "emg") onEmg(d);
|
|
999
999
|
else if (t === "battery") onBattery(d);
|
|
1000
1000
|
|
|
1001
1001
|
addLog(categorize(t), t, d);
|
|
@@ -1005,7 +1005,7 @@ function categorize(t) {
|
|
|
1005
1005
|
if (t === "gesture") return "gesture";
|
|
1006
1006
|
if (t === "pressure") return "pressure";
|
|
1007
1007
|
if (t === "imu_acc" || t === "imu_gyro") return "imu";
|
|
1008
|
-
if (t === "
|
|
1008
|
+
if (t === "emg") return "emg";
|
|
1009
1009
|
if (t === "navigation" || t === "nav_direction" || t === "button") return "nav";
|
|
1010
1010
|
return "other";
|
|
1011
1011
|
}
|
|
@@ -1122,17 +1122,17 @@ function onImuGyro(d) {
|
|
|
1122
1122
|
if (controlMode === "imu") projectImu();
|
|
1123
1123
|
}
|
|
1124
1124
|
|
|
1125
|
-
function
|
|
1125
|
+
function onEmg(d) {
|
|
1126
1126
|
const v = Array.isArray(d.values) ? d.values : [0, 0, 0];
|
|
1127
|
-
state.
|
|
1127
|
+
state.emg = [Number(v[0]||0), Number(v[1]||0), Number(v[2]||0)];
|
|
1128
1128
|
|
|
1129
|
-
pushRing(sncHistory.ch0, state.
|
|
1130
|
-
pushRing(sncHistory.ch1, state.
|
|
1131
|
-
pushRing(sncHistory.ch2, state.
|
|
1129
|
+
pushRing(sncHistory.ch0, state.emg[0]);
|
|
1130
|
+
pushRing(sncHistory.ch1, state.emg[1]);
|
|
1131
|
+
pushRing(sncHistory.ch2, state.emg[2]);
|
|
1132
1132
|
|
|
1133
|
-
ui.sncUlnar.textContent = state.
|
|
1134
|
-
ui.sncMedian.textContent = state.
|
|
1135
|
-
ui.sncRadial.textContent = state.
|
|
1133
|
+
ui.sncUlnar.textContent = state.emg[0].toFixed(3);
|
|
1134
|
+
ui.sncMedian.textContent = state.emg[1].toFixed(3);
|
|
1135
|
+
ui.sncRadial.textContent = state.emg[2].toFixed(3);
|
|
1136
1136
|
}
|
|
1137
1137
|
|
|
1138
1138
|
function onBattery(d) {
|
|
@@ -370,7 +370,7 @@
|
|
|
370
370
|
<div class="status-row">
|
|
371
371
|
<div class="status" id="status">
|
|
372
372
|
<span class="dot" id="statusDot"></span>
|
|
373
|
-
<span id="statusText">Connecting to Mudra
|
|
373
|
+
<span id="statusText">Connecting to Mudra Link…</span>
|
|
374
374
|
</div>
|
|
375
375
|
<span class="hand-chip none" id="handChip">None</span>
|
|
376
376
|
</div>
|
|
@@ -416,7 +416,7 @@
|
|
|
416
416
|
<div class="metric-value" id="motionValue">x:0 y:0</div>
|
|
417
417
|
</div>
|
|
418
418
|
<div class="metric">
|
|
419
|
-
<div class="metric-label">
|
|
419
|
+
<div class="metric-label">EMG Energy</div>
|
|
420
420
|
<div class="metric-value" id="sncValue">0.00</div>
|
|
421
421
|
<div class="bar"><span id="sncBar"></span></div>
|
|
422
422
|
</div>
|
|
@@ -447,7 +447,7 @@
|
|
|
447
447
|
const WS_URL = "ws://127.0.0.1:8766";
|
|
448
448
|
const MAX_LOG_LINES = 80;
|
|
449
449
|
const PRESSURE_WINDOW = 5;
|
|
450
|
-
const
|
|
450
|
+
const EMG_BUFFER_SIZE = 500;
|
|
451
451
|
const STATUS_POLL_INTERVAL = 2000;
|
|
452
452
|
const BACKOFF_SCHEDULE = [1000, 2000, 5000, 10000];
|
|
453
453
|
|
|
@@ -479,7 +479,7 @@
|
|
|
479
479
|
navY: 0,
|
|
480
480
|
imuAcc: [0, 0, 9.81],
|
|
481
481
|
imuGyro: [0, 0, 0],
|
|
482
|
-
|
|
482
|
+
emg: [0, 0, 0],
|
|
483
483
|
navDirection: "None"
|
|
484
484
|
};
|
|
485
485
|
|
|
@@ -604,7 +604,7 @@
|
|
|
604
604
|
if (msg.type === "nav_direction") handleNavDirection(msg.data);
|
|
605
605
|
if (msg.type === "imu_acc") handleImuAcc(msg.data);
|
|
606
606
|
if (msg.type === "imu_gyro") handleImuGyro(msg.data);
|
|
607
|
-
if (msg.type === "
|
|
607
|
+
if (msg.type === "emg") handleEmg(msg.data);
|
|
608
608
|
}
|
|
609
609
|
|
|
610
610
|
// ─── Connection label ─────────────────────────────────────────────────────
|
|
@@ -641,12 +641,12 @@
|
|
|
641
641
|
case "in-use":
|
|
642
642
|
el.classList.add("in-use");
|
|
643
643
|
dot.style.background = "var(--error)";
|
|
644
|
-
text.textContent = "Mudra
|
|
644
|
+
text.textContent = "Mudra Link is already in use by another tab — please close it before continuing.";
|
|
645
645
|
chip.textContent = "None";
|
|
646
646
|
chip.classList.add("none");
|
|
647
647
|
break;
|
|
648
648
|
default: // "connecting"
|
|
649
|
-
text.textContent = "Connecting to Mudra
|
|
649
|
+
text.textContent = "Connecting to Mudra Link…";
|
|
650
650
|
chip.textContent = "None";
|
|
651
651
|
chip.classList.add("none");
|
|
652
652
|
}
|
|
@@ -670,7 +670,7 @@
|
|
|
670
670
|
// ─── Signal subscriptions ─────────────────────────────────────────────────
|
|
671
671
|
|
|
672
672
|
function getActiveSignalNames() {
|
|
673
|
-
const base = ["gesture", "pressure", "
|
|
673
|
+
const base = ["gesture", "pressure", "emg"];
|
|
674
674
|
if (controlMode === "pointer") return [...base, "navigation", "button"];
|
|
675
675
|
if (controlMode === "direction") return [...base, "nav_direction"];
|
|
676
676
|
return [...base, "imu_acc", "imu_gyro"];
|
|
@@ -757,17 +757,17 @@
|
|
|
757
757
|
render();
|
|
758
758
|
}
|
|
759
759
|
|
|
760
|
-
function
|
|
760
|
+
function handleEmg(data = {}) {
|
|
761
761
|
const channels = Array.isArray(data.values) && data.values.length === 3
|
|
762
762
|
? data.values : [[0], [0], [0]];
|
|
763
763
|
for (let i = 0; i < 3; i++) {
|
|
764
764
|
sncBuffers[i].push(...channels[i]);
|
|
765
|
-
if (sncBuffers[i].length >
|
|
766
|
-
sncBuffers[i].splice(0, sncBuffers[i].length -
|
|
765
|
+
if (sncBuffers[i].length > EMG_BUFFER_SIZE) {
|
|
766
|
+
sncBuffers[i].splice(0, sncBuffers[i].length - EMG_BUFFER_SIZE);
|
|
767
767
|
}
|
|
768
768
|
}
|
|
769
|
-
state.
|
|
770
|
-
const energy = Math.min(1, (Math.abs(state.
|
|
769
|
+
state.emg = sncBuffers.map(buf => Number(buf[buf.length - 1] || 0));
|
|
770
|
+
const energy = Math.min(1, (Math.abs(state.emg[0]) + Math.abs(state.emg[1]) + Math.abs(state.emg[2])) / 3);
|
|
771
771
|
ui.sncValue.textContent = energy.toFixed(2);
|
|
772
772
|
ui.sncBar.style.width = `${Math.round(energy * 100)}%`;
|
|
773
773
|
}
|
|
@@ -159,7 +159,7 @@ function triggerGesture(type) {
|
|
|
159
159
|
else emit('gesture', { type, confidence: 0.95, timestamp: Date.now() });
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
/* ── Simulation (when
|
|
162
|
+
/* ── Simulation (when Link not reachable) ─────────────── */
|
|
163
163
|
function startSim() {
|
|
164
164
|
if (simTimer) return;
|
|
165
165
|
simulating = true;
|
|
@@ -141,7 +141,7 @@ function triggerGesture(type) {
|
|
|
141
141
|
else emit('gesture', { type, confidence: 0.95, timestamp: Date.now() });
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
/* ── Simulation (when
|
|
144
|
+
/* ── Simulation (when Link not reachable) ─────────────── */
|
|
145
145
|
let simAutoServe = null;
|
|
146
146
|
function startSim() {
|
|
147
147
|
if (simTimer) return;
|
|
@@ -152,7 +152,7 @@ function triggerGesture(type) {
|
|
|
152
152
|
else emit('gesture', { type, confidence: 0.95, timestamp: Date.now() });
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
/* ── Simulation (when
|
|
155
|
+
/* ── Simulation (when Link not reachable) ─────────────── */
|
|
156
156
|
function startSim() {
|
|
157
157
|
if (simTimer) return;
|
|
158
158
|
simulating = true;
|
|
@@ -183,7 +183,7 @@ function triggerGesture(type) {
|
|
|
183
183
|
else emit('gesture', { type, confidence: 0.95, timestamp: Date.now() });
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
/* ── Simulation (when
|
|
186
|
+
/* ── Simulation (when Link not reachable) ─────────────── */
|
|
187
187
|
function startSim() {
|
|
188
188
|
if (simTimer) return;
|
|
189
189
|
simulating = true;
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
<script>
|
|
82
82
|
/* ── Mudra Protocol Connection (v2) ────────────────────────── */
|
|
83
83
|
const WS_URL = 'ws://127.0.0.1:8766';
|
|
84
|
-
const SIGNALS = ['pressure', 'gesture', '
|
|
84
|
+
const SIGNALS = ['pressure', 'gesture', 'emg'];
|
|
85
85
|
|
|
86
86
|
let ws, reconnectTimer, simTimer;
|
|
87
87
|
let wsOpen = false, deviceReady = false, simulating = true;
|
|
@@ -127,7 +127,7 @@ function triggerGesture(type) {
|
|
|
127
127
|
else emit('gesture', { type, confidence: 0.95, timestamp: Date.now() });
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
/* ── Simulation (when
|
|
130
|
+
/* ── Simulation (when Link not reachable) ─────────────── */
|
|
131
131
|
function startSim() {
|
|
132
132
|
if (simTimer) return;
|
|
133
133
|
simulating = true;
|
|
@@ -136,7 +136,7 @@ function startSim() {
|
|
|
136
136
|
t += 0.05;
|
|
137
137
|
const n = Math.max(0, Math.min(1, 0.3 + 0.25 * Math.sin(t * 0.7) + (Math.random() - 0.5) * 0.1));
|
|
138
138
|
emit('pressure', { value: Math.round(n * 100), normalized: n, timestamp: Date.now() });
|
|
139
|
-
emit('
|
|
139
|
+
emit('emg', {
|
|
140
140
|
values: [
|
|
141
141
|
0.3 * Math.sin(t * 1.3) + (Math.random() - 0.5) * 0.1,
|
|
142
142
|
0.2 * Math.cos(t * 1.7) + (Math.random() - 0.5) * 0.1,
|
|
@@ -206,7 +206,7 @@ on('pressure', (d) => {
|
|
|
206
206
|
pressFill.style.width = Math.round(pressure * 100) + '%';
|
|
207
207
|
});
|
|
208
208
|
|
|
209
|
-
on('
|
|
209
|
+
on('emg', (d) => {
|
|
210
210
|
const vals = d.values || [0, 0, 0];
|
|
211
211
|
const energy = (Math.abs(vals[0]) + Math.abs(vals[1]) + Math.abs(vals[2])) / 3;
|
|
212
212
|
hue = Math.round((energy * 720) % 360);
|
|
@@ -619,11 +619,11 @@ body {
|
|
|
619
619
|
<h2>The Ghost Trail</h2>
|
|
620
620
|
<p class="tagline">See the gap between thought and action</p>
|
|
621
621
|
<div class="controls-info">
|
|
622
|
-
<p><span class="key ghost-key">Mudra
|
|
622
|
+
<p><span class="key ghost-key">Mudra EMG</span> = Ghost (Intent)</p>
|
|
623
623
|
<p><span class="key physical-key">Mouse Click</span> = Runner (Action)</p>
|
|
624
624
|
</div>
|
|
625
625
|
<div class="threshold-setting">
|
|
626
|
-
<label for="threshold-slider">
|
|
626
|
+
<label for="threshold-slider">EMG Threshold</label>
|
|
627
627
|
<div class="slider-container">
|
|
628
628
|
<input type="range" id="threshold-slider" min="0.1" max="0.9" step="0.05" value="0.3">
|
|
629
629
|
<span id="threshold-value">0.30</span>
|
|
@@ -632,7 +632,7 @@ body {
|
|
|
632
632
|
<div id="pressure-bar"></div>
|
|
633
633
|
<div id="threshold-marker"></div>
|
|
634
634
|
</div>
|
|
635
|
-
<span class="threshold-hint">Flex to test - bar shows live
|
|
635
|
+
<span class="threshold-hint">Flex to test - bar shows live EMG magnitude</span>
|
|
636
636
|
</div>
|
|
637
637
|
<button id="start-btn" class="game-btn">Start Run</button>
|
|
638
638
|
<div id="title-connection-status">
|
|
@@ -852,11 +852,11 @@ class IntentRunner {
|
|
|
852
852
|
this.ws = new WebSocket('ws://127.0.0.1:8766');
|
|
853
853
|
|
|
854
854
|
this.ws.onopen = () => {
|
|
855
|
-
console.log('Connected to Mudra
|
|
856
|
-
// Subscribe to
|
|
855
|
+
console.log('Connected to Mudra Link');
|
|
856
|
+
// Subscribe to EMG (neuromuscular EMG) events
|
|
857
857
|
this.ws.send(JSON.stringify({
|
|
858
858
|
command: 'subscribe',
|
|
859
|
-
signal: '
|
|
859
|
+
signal: 'emg'
|
|
860
860
|
}));
|
|
861
861
|
};
|
|
862
862
|
|
|
@@ -869,9 +869,9 @@ class IntentRunner {
|
|
|
869
869
|
|
|
870
870
|
if (data.type === 'connection_status' && data.data?.status === 'connected') {
|
|
871
871
|
this.setMudraConnected(true);
|
|
872
|
-
} else if (data.type === '
|
|
873
|
-
// Handle
|
|
874
|
-
//
|
|
872
|
+
} else if (data.type === 'emg') {
|
|
873
|
+
// Handle EMG (neuromuscular EMG) signal
|
|
874
|
+
// EMG data is typically an array of values in range -1 to 1
|
|
875
875
|
const rawData = data.data;
|
|
876
876
|
let magnitude = 0;
|
|
877
877
|
|
|
@@ -885,7 +885,7 @@ class IntentRunner {
|
|
|
885
885
|
values = rawData.flat().filter(v => typeof v === 'number');
|
|
886
886
|
} else if (typeof rawData === 'object' && rawData !== null) {
|
|
887
887
|
// Try to find array or number in object
|
|
888
|
-
const possibleKeys = ['value', 'values', 'data', '
|
|
888
|
+
const possibleKeys = ['value', 'values', 'data', 'emg', 'magnitude', 'samples'];
|
|
889
889
|
for (const key of possibleKeys) {
|
|
890
890
|
if (rawData[key] !== undefined) {
|
|
891
891
|
const val = rawData[key];
|
|
@@ -910,7 +910,7 @@ class IntentRunner {
|
|
|
910
910
|
magnitude = Math.sqrt(sumSquares / values.length);
|
|
911
911
|
}
|
|
912
912
|
|
|
913
|
-
//
|
|
913
|
+
// EMG is in range -1 to 1, so magnitude should be 0 to 1
|
|
914
914
|
// But clamp just in case
|
|
915
915
|
magnitude = Math.max(0, Math.min(1, magnitude));
|
|
916
916
|
|
|
@@ -159,7 +159,7 @@ function triggerGesture(type) {
|
|
|
159
159
|
else emit('gesture', { type, confidence: 0.95, timestamp: Date.now() });
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
/* ── Simulation (when
|
|
162
|
+
/* ── Simulation (when Link not reachable) ─────────────── */
|
|
163
163
|
function startSim() {
|
|
164
164
|
if (simTimer) return;
|
|
165
165
|
simulating = true;
|
|
@@ -365,7 +365,7 @@
|
|
|
365
365
|
let msg;
|
|
366
366
|
try { msg = JSON.parse(event.data); } catch { return; }
|
|
367
367
|
|
|
368
|
-
// Connection status from
|
|
368
|
+
// Connection status from Link
|
|
369
369
|
if (msg.type === "connection_status") {
|
|
370
370
|
mudra.deviceReady = msg.data?.status === "connected";
|
|
371
371
|
setMudraStatus(mudra.deviceReady, mudra.deviceReady ? "Mudra Band ready" : "Connect your Mudra Band");
|
|
@@ -683,7 +683,7 @@
|
|
|
683
683
|
ws = new WebSocket(WS_URL);
|
|
684
684
|
|
|
685
685
|
ws.onopen = () => {
|
|
686
|
-
setStatus(true, "Connected to Mudra
|
|
686
|
+
setStatus(true, "Connected to Mudra Link");
|
|
687
687
|
// Subscribe to pressure (analog jet control)
|
|
688
688
|
send({ command: "subscribe", signal: "pressure" });
|
|
689
689
|
};
|