mudra-skills 1.2.0 → 3.0.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 CHANGED
@@ -84,7 +84,6 @@ Every generated app includes:
84
84
  | `imu_acc` | analog | accelerometer (x, y, z) |
85
85
  | `imu_gyro` | analog | gyroscope (x, y, z) |
86
86
  | `snc` | analog | 3-channel bio signal |
87
- | `battery` | discrete | battery level |
88
87
 
89
88
  ## Requirements
90
89
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mudra-skills",
3
- "version": "1.2.0",
3
+ "version": "3.0.2",
4
4
  "description": "Mudra Band skills for Claude Code, Codex, and Antigravity — 2D, 3D/XR, or auto-classified",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,6 +26,6 @@
26
26
  "webxr"
27
27
  ],
28
28
  "dependencies": {
29
- "mudra-skills": "^1.1.0"
29
+ "mudra-skills": "^3.0.0"
30
30
  }
31
31
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: mudra-master
3
- version: 2.2.0
3
+ version: 3.0.0
4
4
  description: Master router for Mudra Band app builds. Use when the user describes an app, experience, prototype, timer, counter, or tool to build with the Mudra Band but has not made clear whether they want a 2D (flat/screen) or 3D (spatial/XR/VR/AR) version. This skill classifies the prompt deterministically and hands off the original prompt verbatim to either mudra-preview (2D) or mudra-xr (3D), asking exactly one 2D-or-3D disambiguation question only when the dimension cannot be inferred. Do not use for pure code edits, repo questions, or any request that is not a Mudra-app build.
5
5
  ---
6
6
  # Mudra Master Skill
@@ -16,12 +16,6 @@ Activate when the user:
16
16
  - Describes something they want to **build / make / create / prototyp e** with the Mudra Band — an app, experience, tool, timer, counter, demo, game, dashboard, visualizer, panel, or scene — AND
17
17
  - Has not made clear whether they want a 2D (screen-based) or 3D (spatial / XR / VR / AR) version.
18
18
 
19
- I have 2 sklills and I want to build other skill master that can use both skill, how it can be done ?
20
-
21
-
22
-
23
- and what the structure should look like ?Prefer letting `mudra-preview` or `mudra-xr` activate directly when Claude Code's skill-discovery heuristic matches a child skill's description confidently on an explicit-dimension prompt. This skill is the router for ambiguous prompts and the safe fallback for explicit-dimension prompts that still reach it.
24
-
25
19
  ### The verbatim-prompt rule (FR-012)
26
20
 
27
21
  At no point may the agent modify, summarise, enrich, rephrase, prefix, suffix, or markdown-wrap the user's prompt. The string received is the exact string passed as `args` to the child skill, trimmed only at its leading and trailing whitespace edges. No "Build a 2D …" rewrites. No context injection. No examples appended.
@@ -8,23 +8,23 @@ description: Generate a working Mudra Band interactive app preview as a single-f
8
8
 
9
9
  Generate a complete, working single-file HTML app controlled by Mudra Band signals.
10
10
 
11
- **Mandatory feature (v1.1.0, updated v1.4.0):** Every generated app MUST
11
+ **Mandatory feature :** Every generated app MUST
12
12
  include a Manual/Mudra **Mode toggle** as defined in `references/promt.md`
13
13
  § "Mode Toggle (Manual / Mudra) — Required". Manual is the default; Mudra
14
14
  opens a single WebSocket lazily and disables the simulator panel so signals
15
15
  come only from the band.
16
16
 
17
- **Removed in v1.4.0:** Do **not** render a "Band disconnected" overlay,
17
+ **No disconnect overlay:** Do **not** render a "Band disconnected" overlay,
18
18
  toast, or any other separate disconnect alert. Connection state is
19
19
  communicated **only** through the existing connection-status pill (which
20
20
  shows "Connecting…" / "Connected" / "Disconnected"). No extra notice,
21
21
  no banner, no modal.
22
22
 
23
- **Branding (v1.4.0):** The footer/badge text MUST be exactly
23
+ **Branding:** The footer/badge text MUST be exactly
24
24
  **"Created by Mudra"** — never "Created with Mudra Studio" or any
25
25
  other variant.
26
26
 
27
- **Mandatory feature (v1.2.0):** Connection state MUST reflect the **band**, not
27
+ **Mandatory feature:** Connection state MUST reflect the **band**, not
28
28
  the WebSocket. The Companion service accepts socket connections even when no
29
29
  band is paired, so flipping to "Connected" on `ws.onopen` is a lie. Every
30
30
  generated app MUST send `{command:"get_status"}` on open and poll it every
@@ -32,7 +32,7 @@ generated app MUST send `{command:"get_status"}` on open and poll it every
32
32
  `data.device.state === "connected"`. See `references/promt.md` §
33
33
  "Disconnect detection — band state via `get_status` polling (mandatory)".
34
34
 
35
- The canonical protocol is in `references/agent_protocol.json` (v2.0).
35
+ The canonical protocol is in `references/agent_protocol.json`.
36
36
 
37
37
  ## Steps
38
38
 
@@ -87,7 +87,7 @@ Every generated app MUST include a **compact, always-visible simulator panel** w
87
87
 
88
88
  6. **Report the file path** so the user can open it in a browser immediately.
89
89
 
90
- ## Default Signal Set (v1.3.0 — Required)
90
+ ## Default Signal Set
91
91
 
92
92
  Unless the user explicitly asks for a different signal, every generated
93
93
  app MUST restrict itself to **at most these signals** (subject to the
@@ -127,7 +127,7 @@ Rules:
127
127
  `nav_direction` — pick directional motion OR the IMU+Biometric bundle,
128
128
  never both.
129
129
  - Other gesture subtypes (`twist`, `double_twist`, etc.) and other
130
- signals (`button`, `imu_acc`, `imu_gyro`, `snc`, `battery`) are
130
+ signals (`button`, `imu_acc`, `imu_gyro`, `snc`) are
131
131
  **off by default**. Only include them when the user's prompt names
132
132
  them, names a synonym from the Signal Inference table in
133
133
  `references/promt.md` § "Signal Inference Reference", or describes
@@ -146,6 +146,5 @@ Rules:
146
146
  - Motion modes are mutually exclusive: Pointer (`navigation`+`button`) / Direction (`nav_direction`) / IMU+Biometric (`imu_acc`+`imu_gyro`+`snc`, always all three together)
147
147
  - IMU+Biometric bundle: `imu_acc`, `imu_gyro`, `snc` always subscribed together — never partially. The bundle is mutually exclusive with `navigation` and `nav_direction`.
148
148
  - `gesture` and `pressure` are mutually exclusive — never combine them
149
- - `button` and `battery` combine freely (subject to the Pointer XOR — `button` belongs to Pointer mode and never combines with `nav_direction`)
150
149
  - **Navigation sensitivity is gentle by default**: keyboard `step = 3`, sim button `±3`, cursor multiplier `0.002`. Raise only when the prompt explicitly asks for fast/snappy movement. See `references/promt.md` § "Navigation sensitivity defaults".
151
- - Canonical protocol JSON: `references/agent_protocol.json` (v2.0)
150
+ - Canonical protocol JSON: `references/agent_protocol.json`
@@ -299,18 +299,6 @@
299
299
  "The data array is size 3, each cell corresponds to one sensor"
300
300
  ]
301
301
  },
302
- "battery": {
303
- "description": "Battery level",
304
- "data_format": {
305
- "type": "battery",
306
- "data": {
307
- "level": 85,
308
- "charging": false,
309
- "timestamp": 1234567890
310
- },
311
- "timestamp": 1234567890
312
- }
313
- },
314
302
  "button": {
315
303
  "description": "Air touch button events",
316
304
  "states": [
@@ -45,7 +45,7 @@ UX feel, correct protocol usage, and a fast testing loop.
45
45
  `{ "command": "subscribe", "signal": "<name>" }`
46
46
  - **NEVER** use `signals` (plural), arrays, or batch subscribe commands
47
47
  - Valid signals: `gesture`, `button`, `pressure`, `navigation`,
48
- `nav_direction`, `imu_acc`, `imu_gyro`, `snc`, `battery`
48
+ `nav_direction`, `imu_acc`, `imu_gyro`, `snc`
49
49
  - Full command surface: `subscribe`, `unsubscribe`,
50
50
  `get_subscriptions`, `enable`, `disable`, `get_status`,
51
51
  `get_docs`, `trigger_gesture`
@@ -79,7 +79,7 @@ app MUST restrict itself to **at most these four signals**:
79
79
 
80
80
  Drop any of the four when the concept does not need it (e.g. a pure
81
81
  tap-counter subscribes to `gesture` only). All other signals
82
- (`button`, `imu_acc`, `imu_gyro`, `snc`, `battery`) and other gesture
82
+ (`button`, `imu_acc`, `imu_gyro`, `snc`) and other gesture
83
83
  subtypes (`twist`, `double_twist`, …) are **off by default** — only
84
84
  include them when the user names them, names a synonym from the Signal
85
85
  Inference Reference below, or describes an interaction that genuinely
@@ -129,11 +129,6 @@ ws.send(JSON.stringify({ command: 'subscribe', signal: 'imu_acc' })); // miss
129
129
  - `nav_direction` + `imu_acc` / `imu_gyro` / `snc`
130
130
  - `button` + `nav_direction`
131
131
 
132
- ### Free-combining signals
133
-
134
- `button` and `battery` combine freely with any group (subject to the
135
- XOR rules above — `button` belongs to Pointer mode and never combines
136
- with `nav_direction`).
137
132
 
138
133
  When a conflict appears, explain the limitation and recommend one path.
139
134
 
@@ -342,11 +337,6 @@ class MudraWebSocket {
342
337
  }
343
338
  }, 50));
344
339
 
345
- // Battery: once on connect
346
- this._timers.push(setTimeout(() => {
347
- if (!this._subscriptions.has('battery')) return;
348
- this._emit({ type: 'battery', data: { level: 85, charging: false, timestamp: Date.now() }, timestamp: Date.now() });
349
- }, 500));
350
340
  }
351
341
 
352
342
  close() {
@@ -391,7 +381,7 @@ The user picks how the app is driven via a visible **Mode** control:
391
381
  `aria-disabled="true"`) and emits no synthetic signals. If the band
392
382
  disconnects, the connection-status pill turns red and reads
393
383
  "Disconnected" — **no separate overlay, toast, or banner is rendered
394
- (v1.4.0)**. The WebSocket retries with backoff until reconnect or the
384
+ **. The WebSocket retries with backoff until reconnect or the
395
385
  user switches back to Manual.
396
386
 
397
387
  **The Mode toggle MUST remain fully clickable and keyboard-focusable at
@@ -481,7 +471,7 @@ Outbound:
481
471
 
482
472
  Inbound shape: `{ "type": "...", "data": { ... }, "timestamp": <ms> }`.
483
473
  Handle types: `connection_status`, `gesture`, `pressure`, `navigation`,
484
- `nav_direction`, `button`, `imu_acc`, `imu_gyro`, `snc`, `battery`.
474
+ `nav_direction`, `button`, `imu_acc`, `imu_gyro`, `snc`
485
475
  Anything else: log + ignore.
486
476
 
487
477
  ### Disconnect detection — band state via `get_status` polling (mandatory)
@@ -613,7 +603,6 @@ Every generated app MUST render these three elements at all times:
613
603
  case "imu_acc": handleImuAcc(msg.data); break;
614
604
  case "imu_gyro": handleImuGyro(msg.data); break;
615
605
  case "snc": handleSnc(msg.data); break;
616
- case "battery": handleBattery(msg.data); break;
617
606
  case "connection_status": handleConnectionStatus(msg.data); break;
618
607
  case "status": handleStatus(msg.data); break; // get_status response
619
608
  }
@@ -641,7 +630,7 @@ Every generated app MUST render these three elements at all times:
641
630
  ```
642
631
 
643
632
  - No disconnect notice ever — disconnect is shown only by the
644
- connection-status pill (v1.4.0).
633
+ connection-status pill.
645
634
 
646
635
  ### Mudra mode rules
647
636
 
@@ -898,8 +887,7 @@ Pick exactly ONE motion mode per app: **Pointer** (`navigation` + `button`)
898
887
  **XOR** **Direction** (`nav_direction`) **XOR** **IMU+Biometric**
899
888
  (`imu_acc` + `imu_gyro` + `snc`, always all three together). The Mode
900
889
  toggle does NOT relax this rule. Additional XOR rules: `gesture` and
901
- `pressure` are mutually exclusive — never combine them. `button` and
902
- `battery` combine freely (subject to the Pointer/Direction/IMU XOR).
890
+ `pressure` are mutually exclusive — never combine them. `button` combines freely (subject to the Pointer/Direction/IMU XOR).
903
891
 
904
892
  ---
905
893
 
@@ -1542,10 +1530,6 @@ Below are all reference apps. When generating a new app, select the best-matchin
1542
1530
  <div class="metric-label">Nav Direction</div>
1543
1531
  <div class="metric-value" id="navDirectionValue">None</div>
1544
1532
  </div>
1545
- <div class="metric">
1546
- <div class="metric-label">Battery</div>
1547
- <div class="metric-value" id="batteryValue">--</div>
1548
- </div>
1549
1533
  </div>
1550
1534
  </article>
1551
1535
  </section>
@@ -1588,8 +1572,6 @@ Below are all reference apps. When generating a new app, select the best-matchin
1588
1572
  imuAcc: [0, 0, 9.81],
1589
1573
  imuGyro: [0, 0, 0],
1590
1574
  snc: [0, 0, 0],
1591
- battery: null,
1592
- charging: false,
1593
1575
  navDirection: "None"
1594
1576
  };
1595
1577
 
@@ -1608,7 +1590,6 @@ Below are all reference apps. When generating a new app, select the best-matchin
1608
1590
  sncValue: document.getElementById("sncValue"),
1609
1591
  sncBar: document.getElementById("sncBar"),
1610
1592
  navDirectionValue: document.getElementById("navDirectionValue"),
1611
- batteryValue: document.getElementById("batteryValue"),
1612
1593
  log: document.getElementById("log")
1613
1594
  };
1614
1595
 
@@ -1696,12 +1677,11 @@ Below are all reference apps. When generating a new app, select the best-matchin
1696
1677
  if (msg.type === "imu_acc") handleImuAcc(msg.data);
1697
1678
  if (msg.type === "imu_gyro") handleImuGyro(msg.data);
1698
1679
  if (msg.type === "snc") handleSnc(msg.data);
1699
- if (msg.type === "battery") handleBattery(msg.data);
1700
1680
  };
1701
1681
  }
1702
1682
 
1703
1683
  function getActiveSignals() {
1704
- const base = ["gesture", "pressure", "snc", "battery"];
1684
+ const base = ["gesture", "pressure", "snc"];
1705
1685
  if (controlMode === "pointer") return [...base, "navigation", "button"];
1706
1686
  if (controlMode === "direction") return [...base, "nav_direction"];
1707
1687
  return [...base, "imu_acc", "imu_gyro"];
@@ -1802,12 +1782,6 @@ Below are all reference apps. When generating a new app, select the best-matchin
1802
1782
  ui.sncBar.style.width = `${Math.round(energy * 100)}%`;
1803
1783
  }
1804
1784
 
1805
- function handleBattery(data = {}) {
1806
- if (typeof data.level !== "number") return;
1807
- state.battery = data.level;
1808
- state.charging = Boolean(data.charging);
1809
- ui.batteryValue.textContent = `${state.battery}% ${state.charging ? "(charging)" : ""}`;
1810
- }
1811
1785
 
1812
1786
  function projectImuToStage() {
1813
1787
  const x = clamp(state.imuAcc[0] * 12 + state.imuGyro[2] * 0.15, -110, 110);
@@ -2024,27 +1998,6 @@ Below are all reference apps. When generating a new app, select the best-matchin
2024
1998
  .conn-dot.on { background: var(--success); box-shadow: 0 0 0 3px rgba(34,197,94,0.18); }
2025
1999
  .conn-dot.err { background: var(--error); box-shadow: 0 0 0 3px rgba(239,68,68,0.18); }
2026
2000
 
2027
- .battery-pill {
2028
- display: inline-flex; align-items: center; gap: 6px;
2029
- padding: 6px 12px; border-radius: 999px;
2030
- border: 1px solid var(--border-dim);
2031
- background: rgba(0,0,0,0.02);
2032
- font-size: 0.82rem; font-weight: 500;
2033
- }
2034
- .battery-icon {
2035
- width: 22px; height: 12px; border: 1.5px solid var(--text-secondary);
2036
- border-radius: 2px; position: relative;
2037
- }
2038
- .battery-icon::after {
2039
- content: ''; position: absolute; right: -4px; top: 2px;
2040
- width: 2px; height: 6px; background: var(--text-secondary); border-radius: 0 1px 1px 0;
2041
- }
2042
- .battery-fill {
2043
- height: 100%; border-radius: 1px;
2044
- background: var(--success); transition: width 300ms;
2045
- }
2046
- .battery-fill.low { background: var(--error); }
2047
- .battery-fill.mid { background: var(--warning); }
2048
2001
 
2049
2002
  /* ── Dashboard Grid ── */
2050
2003
  .dashboard {
@@ -2401,10 +2354,6 @@ Below are all reference apps. When generating a new app, select the best-matchin
2401
2354
  </div>
2402
2355
  </div>
2403
2356
  <div class="topbar-right">
2404
- <div class="battery-pill" id="batteryPill" style="display:none">
2405
- <div class="battery-icon"><div class="battery-fill" id="batteryFill" style="width:0%"></div></div>
2406
- <span id="batteryText">--%</span>
2407
- </div>
2408
2357
  <div class="pkt-counter"><span id="pktCount">0</span> packets</div>
2409
2358
  </div>
2410
2359
  </header>
@@ -2597,11 +2546,6 @@ Below are all reference apps. When generating a new app, select the best-matchin
2597
2546
  <span class="sig-pkts" id="pkts-snc">0</span>
2598
2547
  <div class="toggle-switch on" data-sub="snc"></div>
2599
2548
  </div>
2600
- <div class="toggle-row" data-signal="battery">
2601
- <span class="sig-name">battery</span>
2602
- <span class="sig-pkts" id="pkts-battery">0</span>
2603
- <div class="toggle-switch on" data-sub="battery"></div>
2604
- </div>
2605
2549
 
2606
2550
  <div class="sub-group-label">Pointer Mode</div>
2607
2551
  <div class="toggle-row" data-signal="navigation">
@@ -2715,9 +2659,9 @@ let logFilter = "all";
2715
2659
  const pressureSamples = [];
2716
2660
 
2717
2661
  // Subscription tracking
2718
- const activeSubs = new Set(["gesture", "pressure", "snc", "battery", "navigation", "button"]);
2662
+ const activeSubs = new Set(["gesture", "pressure", "snc", "navigation", "button"]);
2719
2663
  const packetCounts = {};
2720
- const ALL_SIGNALS = ["gesture", "pressure", "snc", "battery", "navigation", "button", "nav_direction", "imu_acc", "imu_gyro"];
2664
+ const ALL_SIGNALS = ["gesture", "pressure", "snc", "navigation", "button", "nav_direction", "imu_acc", "imu_gyro"];
2721
2665
  const MOTION_CONFLICTS = {
2722
2666
  navigation: ["nav_direction", "imu_acc", "imu_gyro"],
2723
2667
  button: ["nav_direction", "imu_acc", "imu_gyro"],
@@ -2735,7 +2679,6 @@ const state = {
2735
2679
  imuAcc: [0, 0, 9.81],
2736
2680
  imuGyro: [0, 0, 0],
2737
2681
  snc: [0, 0, 0],
2738
- battery: { level: -1, charging: false }
2739
2682
  };
2740
2683
 
2741
2684
  const gestureHistory = [];
@@ -2757,7 +2700,6 @@ const logEntries = [];
2757
2700
  const $ = id => document.getElementById(id);
2758
2701
  const ui = {
2759
2702
  connDot: $("connDot"), connText: $("connText"),
2760
- batteryPill: $("batteryPill"), batteryFill: $("batteryFill"), batteryText: $("batteryText"),
2761
2703
  pktCount: $("pktCount"),
2762
2704
  gestureType: $("gestureType"), gestureConf: $("gestureConf"), gestureHistory: $("gestureHistory"),
2763
2705
  gaugeFill: $("gaugeFill"), gaugeText: $("gaugeText"),
@@ -2918,7 +2860,7 @@ function switchMode(mode) {
2918
2860
  });
2919
2861
 
2920
2862
  // Also re-subscribe universal ones
2921
- ["gesture", "pressure", "snc", "battery"].forEach(s => {
2863
+ ["gesture", "pressure", "snc"].forEach(s => {
2922
2864
  if (activeSubs.has(s)) send({ command: "subscribe", signal: s });
2923
2865
  });
2924
2866
 
@@ -2957,7 +2899,6 @@ function routeMessage(msg) {
2957
2899
  else if (t === "imu_acc") onImuAcc(d);
2958
2900
  else if (t === "imu_gyro") onImuGyro(d);
2959
2901
  else if (t === "snc") onSnc(d);
2960
- else if (t === "battery") onBattery(d);
2961
2902
 
2962
2903
  addLog(categorize(t), t, d);
2963
2904
  }
@@ -3096,16 +3037,6 @@ function onSnc(d) {
3096
3037
  ui.sncRadial.textContent = state.snc[2].toFixed(3);
3097
3038
  }
3098
3039
 
3099
- function onBattery(d) {
3100
- if (typeof d.level !== "number") return;
3101
- state.battery.level = d.level;
3102
- state.battery.charging = !!d.charging;
3103
-
3104
- ui.batteryPill.style.display = "inline-flex";
3105
- ui.batteryFill.style.width = d.level + "%";
3106
- ui.batteryFill.className = "battery-fill" + (d.level <= 15 ? " low" : d.level <= 40 ? " mid" : "");
3107
- ui.batteryText.textContent = d.level + "%" + (d.charging ? " &#9889;" : "");
3108
- }
3109
3040
 
3110
3041
  // ── Motion helpers ──
3111
3042
  function projectImu() {
@@ -3613,10 +3544,6 @@ window.addEventListener("resize", () => {
3613
3544
  <span class="hud-value" id="hudPressure">0%</span>
3614
3545
  </div>
3615
3546
  <div class="hud-bar"><span id="hudPressureBar"></span></div>
3616
- <div class="hud-row">
3617
- <span class="hud-label">Battery</span>
3618
- <span class="hud-value" id="hudBattery">--</span>
3619
- </div>
3620
3547
  </div>
3621
3548
  </div>
3622
3549
 
@@ -3750,8 +3677,6 @@ window.addEventListener("resize", () => {
3750
3677
  navDeltaDisplay: "0",
3751
3678
  pressure: 0,
3752
3679
  smoothPressure: 0,
3753
- battery: null,
3754
- charging: false,
3755
3680
  // Game-facing signals
3756
3681
  deltaX: 0, // accumulated navigation delta, consumed each frame
3757
3682
  shootTriggered: false,
@@ -3766,7 +3691,6 @@ window.addEventListener("resize", () => {
3766
3691
  direction: document.getElementById('hudDirection'),
3767
3692
  pressure: document.getElementById('hudPressure'),
3768
3693
  pressureBar: document.getElementById('hudPressureBar'),
3769
- battery: document.getElementById('hudBattery')
3770
3694
  };
3771
3695
 
3772
3696
  function connectMudra() {
@@ -3793,7 +3717,6 @@ window.addEventListener("resize", () => {
3793
3717
  if (msg.type === "gesture") handleMudraGesture(msg.data);
3794
3718
  if (msg.type === "navigation") handleMudraNavigation(msg.data);
3795
3719
  if (msg.type === "pressure") handleMudraPressure(msg.data);
3796
- if (msg.type === "battery") handleMudraBattery(msg.data);
3797
3720
  };
3798
3721
 
3799
3722
  mudra.ws.onerror = () => {
@@ -3816,7 +3739,7 @@ window.addEventListener("resize", () => {
3816
3739
  function subscribeSignals() {
3817
3740
  // Pointer mode: navigation for continuous movement + gesture/pressure freely combine
3818
3741
  // (no nav_direction, no imu — signal-safe)
3819
- const signals = ["gesture", "navigation", "pressure", "battery"];
3742
+ const signals = ["gesture", "navigation", "pressure"];
3820
3743
  signals.forEach(signal => sendMudra({ command: "subscribe", signal }));
3821
3744
  }
3822
3745
 
@@ -3871,12 +3794,6 @@ window.addEventListener("resize", () => {
3871
3794
  hud.pressureBar.style.width = `${Math.round(mudra.smoothPressure * 100)}%`;
3872
3795
  }
3873
3796
 
3874
- function handleMudraBattery(data = {}) {
3875
- if (typeof data.level !== "number") return;
3876
- mudra.battery = data.level;
3877
- mudra.charging = Boolean(data.charging);
3878
- hud.battery.textContent = `${mudra.battery}%${mudra.charging ? " ⚡" : ""}`;
3879
- }
3880
3797
 
3881
3798
  function pulseHud(el) {
3882
3799
  el.style.transition = 'none';
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: mudra-xr
3
- version: 2.2.0
3
+ version: 3.0.0
4
4
  description: Generate a single-file Mudra-controlled 3D/XR app using XR Blocks. Use when the user describes a 3D, XR, VR, or AR experience controlled by the Mudra Band.
5
5
  ---
6
6
 
@@ -9,13 +9,13 @@ description: Generate a single-file Mudra-controlled 3D/XR app using XR Blocks.
9
9
  Generate a complete, working single-file HTML 3D/XR app controlled by Mudra Band signals.
10
10
  The output runs in any modern Chromium browser — no build step, no server, no headset required.
11
11
 
12
- **Mandatory feature (v0.3.0):** Every generated app MUST include a
12
+ **Mandatory feature:** Every generated app MUST include a
13
13
  Manual/Mudra **Mode toggle** as defined in `references/promt.md`
14
14
  § Section 15 "Mode Toggle (Manual / Mudra) — Required". Manual is the
15
15
  default; Mudra opens a single WebSocket lazily and disables the
16
16
  simulator panel so signals come only from the band.
17
17
 
18
- **Mandatory feature (v0.3.0):** Connection state MUST reflect the
18
+ **Mandatory feature:** Connection state MUST reflect the
19
19
  **band**, not the WebSocket. The Companion service accepts socket
20
20
  connections even when no band is paired, so flipping to "Connected" on
21
21
  `ws.onopen` is a lie. Every generated app MUST send
@@ -29,7 +29,7 @@ overlay, toast, or any other separate disconnect alert. Connection
29
29
  state is communicated **only** through the existing connection-status
30
30
  pill (`Manual` / `Connecting…` / `Connected` / `Disconnected`).
31
31
 
32
- **Branding (v0.3.0):** The footer/badge text MUST be exactly
32
+ **Branding:** The footer/badge text MUST be exactly
33
33
  **"Created by Mudra"** — never "Created with Mudra Studio",
34
34
  "Powered by Mudra", or any other variant. See § Section 16.
35
35
 
@@ -38,7 +38,7 @@ sub-actions the app actually handles. If the app maps `nav_direction` to
38
38
  Up/Down only, omit Left/Right/Roll L/Roll R. If `gesture` only handles
39
39
  `tap`, omit Twist/2Twist/2Tap. See § Section 5.
40
40
 
41
- The canonical protocol is in `references/agent_protocol.json` (v2.0).
41
+ The canonical protocol rules are in `references/prompt.md` Section 1 (signals, commands, error frames) and Section 15 (state machine, hand chip). See also `specs/001-mudra-xr-connection-update/contracts/websocket-protocol.md` for the wire-protocol contract.
42
42
 
43
43
  ## Invocation
44
44
 
@@ -237,7 +237,7 @@ Print the absolute path to the written file and a one-line summary:
237
237
  - Motion modes are mutually exclusive: Pointer (`navigation`+`button`) / Direction (`nav_direction`) / IMU+Biometric (`imu_acc`+`imu_gyro`+`snc`)
238
238
  - IMU+Biometric bundle: `imu_acc`, `imu_gyro`, `snc` always subscribed together — never partially
239
239
  - `gesture` and `pressure` are mutually exclusive — never combine them
240
- - Free-combining signals (one or the other, not both): `gesture` OR `pressure`, plus `button`, `battery`
240
+ - Free-combining signals (one or the other, not both): `gesture` OR `pressure`, plus `button`
241
241
  - **Navigation sensitivity is gentle by default**: sim button + keyboard `I`/`J`/`K`/`L` emit `±3` per event; cursor multiplier on inbound `delta_x`/`delta_y` is `0.002`. Raise only when the prompt explicitly asks for fast/snappy movement. See Section 6 + Section 11 of `references/promt.md`.
242
242
  - **Reserved for XR Blocks desktop simulator** — Mudra never claims these: `W`/`A`/`S`/`D` and arrow keys (camera walk), `Q`/`E` (roll/vertical), `R` (reset), right-click drag (orbit), mouse wheel (zoom). Mudra navigation uses `I`/`J`/`K`/`L`; Mudra IMU uses `U`/`O`/`M`/`N`. See Section 6 of `references/promt.md`.
243
243
  - Keyboard handlers: `{ capture: true }` + `stopPropagation()` on Mudra-claimed keys
@@ -348,18 +348,6 @@
348
348
  "For HUD: use buf[buf.length-1]; for time-series plots: draw all buffered samples"
349
349
  ]
350
350
  },
351
- "battery": {
352
- "description": "Battery level",
353
- "data_format": {
354
- "type": "battery",
355
- "data": {
356
- "level": 85,
357
- "charging": false,
358
- "timestamp": 1234567890
359
- },
360
- "timestamp": 1234567890
361
- }
362
- },
363
351
  "button": {
364
352
  "description": "Air touch button events (Pointer mode — pairs with navigation)",
365
353
  "states": [
@@ -424,7 +412,6 @@
424
412
  "can_combine": [
425
413
  "gesture OR pressure",
426
414
  "button (with pointer/direction/imu_biometric/none)",
427
- "battery",
428
415
  "ONE OF: pointer_mode OR direction_mode OR imu_biometric_mode"
429
416
  ],
430
417
  "cannot_combine": [
@@ -492,7 +479,7 @@
492
479
  "reason": "Button is part of pointer mode - cannot mix with direction mode"
493
480
  }
494
481
  ],
495
- "guidance": "Three mutually exclusive motion groups: (1) Pointer mode: navigation+button for continuous cursor/drag/pan, (2) Direction mode: nav_direction for discrete swipe-like gestures, (3) IMU+Biometric mode: imu_acc+imu_gyro+snc as an inseparable bundle for orientation/tilt/rotation/biometric. Pick ONE per app. gesture XOR pressure; button and battery combine freely (subject to mode rules)."
482
+ "guidance": "Three mutually exclusive motion groups: (1) Pointer mode: navigation+button for continuous cursor/drag/pan, (2) Direction mode: nav_direction for discrete swipe-like gestures, (3) IMU+Biometric mode: imu_acc+imu_gyro+snc as an inseparable bundle for orientation/tilt/rotation/biometric. Pick ONE per app. gesture XOR pressure; button combines freely (subject to mode rules)."
496
483
  },
497
484
  "websocket_api": {
498
485
  "url": "ws://127.0.0.1:8766",