@rydr/game-sdk 7.0.1 → 7.1.0

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.
Files changed (95) hide show
  1. package/README.md +19 -0
  2. package/dist/protocol/glyphs.d.ts +7 -0
  3. package/dist/protocol/glyphs.d.ts.map +1 -1
  4. package/dist/protocol/glyphs.js +26 -0
  5. package/dist/protocol/glyphs.js.map +1 -1
  6. package/dist/protocol/version.d.ts +1 -1
  7. package/dist/protocol/version.js +1 -1
  8. package/dist/ui/README.md +119 -0
  9. package/dist/ui/controller/combo-demo.d.ts +74 -0
  10. package/dist/ui/controller/combo-demo.d.ts.map +1 -0
  11. package/dist/ui/controller/combo-demo.js +295 -0
  12. package/dist/ui/controller/combo-demo.js.map +1 -0
  13. package/dist/ui/controller/combo-strip.d.ts +36 -0
  14. package/dist/ui/controller/combo-strip.d.ts.map +1 -0
  15. package/dist/ui/controller/combo-strip.js +66 -0
  16. package/dist/ui/controller/combo-strip.js.map +1 -0
  17. package/dist/ui/controller/control-trainer.d.ts +132 -0
  18. package/dist/ui/controller/control-trainer.d.ts.map +1 -0
  19. package/dist/ui/controller/control-trainer.js +570 -0
  20. package/dist/ui/controller/control-trainer.js.map +1 -0
  21. package/dist/ui/controller/controller-map.d.ts +82 -0
  22. package/dist/ui/controller/controller-map.d.ts.map +1 -0
  23. package/dist/ui/controller/controller-map.js +291 -0
  24. package/dist/ui/controller/controller-map.js.map +1 -0
  25. package/dist/ui/controller/controller-pad.d.ts +122 -0
  26. package/dist/ui/controller/controller-pad.d.ts.map +1 -0
  27. package/dist/ui/controller/controller-pad.js +200 -0
  28. package/dist/ui/controller/controller-pad.js.map +1 -0
  29. package/dist/ui/controller/index.d.ts +26 -0
  30. package/dist/ui/controller/index.d.ts.map +1 -0
  31. package/dist/ui/controller/index.js +26 -0
  32. package/dist/ui/controller/index.js.map +1 -0
  33. package/dist/ui/controller/input-chip.d.ts +100 -0
  34. package/dist/ui/controller/input-chip.d.ts.map +1 -0
  35. package/dist/ui/controller/input-chip.js +322 -0
  36. package/dist/ui/controller/input-chip.js.map +1 -0
  37. package/dist/ui/controller/layouts/dualsense.d.ts +12 -0
  38. package/dist/ui/controller/layouts/dualsense.d.ts.map +1 -0
  39. package/dist/ui/controller/layouts/dualsense.js +64 -0
  40. package/dist/ui/controller/layouts/dualsense.js.map +1 -0
  41. package/dist/ui/controller/layouts/index.d.ts +20 -0
  42. package/dist/ui/controller/layouts/index.d.ts.map +1 -0
  43. package/dist/ui/controller/layouts/index.js +34 -0
  44. package/dist/ui/controller/layouts/index.js.map +1 -0
  45. package/dist/ui/controller/layouts/joycon.d.ts +21 -0
  46. package/dist/ui/controller/layouts/joycon.d.ts.map +1 -0
  47. package/dist/ui/controller/layouts/joycon.js +76 -0
  48. package/dist/ui/controller/layouts/joycon.js.map +1 -0
  49. package/dist/ui/controller/layouts/switch-pro.d.ts +12 -0
  50. package/dist/ui/controller/layouts/switch-pro.d.ts.map +1 -0
  51. package/dist/ui/controller/layouts/switch-pro.js +60 -0
  52. package/dist/ui/controller/layouts/switch-pro.js.map +1 -0
  53. package/dist/ui/controller/layouts/xbox.d.ts +11 -0
  54. package/dist/ui/controller/layouts/xbox.d.ts.map +1 -0
  55. package/dist/ui/controller/layouts/xbox.js +59 -0
  56. package/dist/ui/controller/layouts/xbox.js.map +1 -0
  57. package/dist/ui/controller/layouts/zwift.d.ts +27 -0
  58. package/dist/ui/controller/layouts/zwift.d.ts.map +1 -0
  59. package/dist/ui/controller/layouts/zwift.js +62 -0
  60. package/dist/ui/controller/layouts/zwift.js.map +1 -0
  61. package/dist/ui/controller/pad-svg.d.ts +105 -0
  62. package/dist/ui/controller/pad-svg.d.ts.map +1 -0
  63. package/dist/ui/controller/pad-svg.js +289 -0
  64. package/dist/ui/controller/pad-svg.js.map +1 -0
  65. package/dist/ui/controller/parts-map.d.ts +96 -0
  66. package/dist/ui/controller/parts-map.d.ts.map +1 -0
  67. package/dist/ui/controller/parts-map.js +187 -0
  68. package/dist/ui/controller/parts-map.js.map +1 -0
  69. package/dist/ui/controller/styles.d.ts +19 -0
  70. package/dist/ui/controller/styles.d.ts.map +1 -0
  71. package/dist/ui/controller/styles.js +364 -0
  72. package/dist/ui/controller/styles.js.map +1 -0
  73. package/dist/ui/controller/tokens.d.ts +106 -0
  74. package/dist/ui/controller/tokens.d.ts.map +1 -0
  75. package/dist/ui/controller/tokens.js +50 -0
  76. package/dist/ui/controller/tokens.js.map +1 -0
  77. package/dist/ui/controller/types.d.ts +157 -0
  78. package/dist/ui/controller/types.d.ts.map +1 -0
  79. package/dist/ui/controller/types.js +61 -0
  80. package/dist/ui/controller/types.js.map +1 -0
  81. package/dist/ui/index.d.ts +8 -0
  82. package/dist/ui/index.d.ts.map +1 -1
  83. package/dist/ui/index.js +8 -0
  84. package/dist/ui/index.js.map +1 -1
  85. package/dist/ui/keycap.d.ts +14 -0
  86. package/dist/ui/keycap.d.ts.map +1 -1
  87. package/dist/ui/keycap.js +5 -0
  88. package/dist/ui/keycap.js.map +1 -1
  89. package/dist/ui/showcase/gallery.d.ts +13 -1
  90. package/dist/ui/showcase/gallery.d.ts.map +1 -1
  91. package/dist/ui/showcase/gallery.js.map +1 -1
  92. package/dist/ui/showcase/index.d.ts.map +1 -1
  93. package/dist/ui/showcase/index.js +257 -1
  94. package/dist/ui/showcase/index.js.map +1 -1
  95. package/package.json +1 -1
package/README.md CHANGED
@@ -519,6 +519,25 @@ dia.cards.Z.keycap.setCooldown(0.4, 3); // depleting ring + countdown
519
519
  - **`mountDialogueCard`** — an NPC dialogue box (name + typewriter line + continue keycap). **`mountChoiceCard`** — a player menu (prompt + selectable list, ▲▼ + confirm). **`mountLabeledDiamond`** — a pip cluster with a card-seated label radiating from each active button.
520
520
  - **`createLabelKeycap(button, opts)`** — a keycap for a button printed with a WORD, not a letter (`OPTIONS` → "Menu"/"Options"/"+", `LT`/`RT`, the stick clicks): a pill instead of a pip, same state machine.
521
521
  - **`mountPauseMenu(host, opts)`** — **the shared in-game menu**, opened by the game's own `OPTIONS` button: the game's name big at the top *in the game's own font*, a free `Resume` row plus the game's own rows, each able to advertise the `shortcut` that does the same thing during play. It takes the controller over while up (`session.grabInput`) and declares `setActivity("menu")` so the trainer eases — but it **cannot** pause your loop; freeze that yourself in `onOpen`/`onClose`. Don't hand-roll a pause screen: [`src/ui/README.md`](src/ui/README.md) has the full contract.
522
+ - **The controller family** — for showing a rider their whole control scheme. **`mountControllerPad`** draws the controller in their hands and lights it as they press (stick caps follow the real axes). **`mountControllerMap`** surrounds that pad with action callouts, each joined by a line to its button — the "Commandes" screen. **`mountComboDemo`** loops an animation of the combination one action requires. Describe your controls **once**, declaratively, and the SDK owns the rest:
523
+
524
+ ```ts
525
+ import { mountControllerMap, type ControlAction } from "@rydr/game-sdk/ui";
526
+
527
+ const CONTROLS: ControlAction[] = [
528
+ { label: "Se déplacer", combo: ["LSTICK"] }, // a whole stick = free movement
529
+ { label: "Freiner", combo: ["LT"] },
530
+ { label: "Tir chargé", combo: ["RT", "DIAMOND_DOWN"], hint: "Maintenir" },
531
+ { label: "Dash", combo: ["LSTICK_PRESS", "LT"] }, // the line lands on the trigger
532
+ ];
533
+ mountControllerMap(pauseOverlay, { press: session, actions: CONTROLS, title: "Commandes" });
534
+ ```
535
+
536
+ You never name a letter: you say `DIAMOND_DOWN` and the rider sees `A`, `✕` or `B` on the correct
537
+ physical button — on a Switch Pro the highlight stays on the *bottom* button while only the lettering
538
+ shifts — and it re-letters itself if they swap pads mid-screen. Six silhouettes ship, including a
539
+ **Zwift Play / Ride** shape whose absent controls (no joysticks, no triggers) simply aren't drawn.
540
+ Read [`src/ui/README.md`](src/ui/README.md) → "Showing your controls" before building a legend by hand.
522
541
 
523
542
  Wire any keycap to real input by passing the `session` — it structurally satisfies `ButtonSource`, and the keycap then shows its pressed sink while its button is held. The kit speaks the canonical `ButtonName`/`ButtonEdge` vocabulary.
524
543
 
@@ -44,4 +44,11 @@ export declare function buttonLabel(set: GlyphSet, name: ButtonName): string;
44
44
  * positionally (keycaps, prompts) and treats everything else as a plain label.
45
45
  */
46
46
  export declare function isDiamondButton(name: ButtonName): boolean;
47
+ /**
48
+ * The label printed on an upper shoulder button for `set` — `("playstation", "left")` → `"L1"`.
49
+ *
50
+ * Use it when naming the rider's resistance controls. Like {@link buttonLabel}, this is the only place
51
+ * allowed to know these strings; never hard-code `"L1"` in a prompt.
52
+ */
53
+ export declare function bumperLabel(set: GlyphSet, side: "left" | "right"): string;
47
54
  //# sourceMappingURL=glyphs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"glyphs.d.ts","sourceRoot":"","sources":["../../src/protocol/glyphs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C;;;;;;;GAOG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,aAAa,GAAG,UAAU,GAAG,OAAO,CAAC;AAErE,0DAA0D;AAC1D,eAAO,MAAM,iBAAiB,EAAE,QAAiB,CAAC;AA4DlD;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAGnE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAEzD"}
1
+ {"version":3,"file":"glyphs.d.ts","sourceRoot":"","sources":["../../src/protocol/glyphs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C;;;;;;;GAOG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,aAAa,GAAG,UAAU,GAAG,OAAO,CAAC;AAErE,0DAA0D;AAC1D,eAAO,MAAM,iBAAiB,EAAE,QAAiB,CAAC;AA4DlD;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAGnE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAEzD;AAoBD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAEzE"}
@@ -93,4 +93,30 @@ export function buttonLabel(set, name) {
93
93
  export function isDiamondButton(name) {
94
94
  return name in FACE[DEFAULT_GLYPH_SET];
95
95
  }
96
+ /**
97
+ * The UPPER shoulder buttons — the ones that are NOT game input.
98
+ *
99
+ * They deliberately have no {@link ButtonName}: on this platform the upper shoulders are the rider's
100
+ * trainer resistance − / + and are handled by the shell, never delivered to a game (see the shell-owned
101
+ * keys in `protocol/buttons.ts`). They still need lettering, though, because the shell has to *name*
102
+ * them when teaching a rider where they are — and because the controller drawing prints them.
103
+ *
104
+ * Separate from {@link SHOULDERS}, which covers the lower triggers a game DOES receive (`LT`/`RT`).
105
+ */
106
+ const BUMPERS = {
107
+ xbox: { left: "LB", right: "RB" },
108
+ playstation: { left: "L1", right: "R1" },
109
+ nintendo: { left: "L", right: "R" },
110
+ // Zwift Play has no lettering on its paddles at all, so say what they DO.
111
+ zwift: { left: "−", right: "+" },
112
+ };
113
+ /**
114
+ * The label printed on an upper shoulder button for `set` — `("playstation", "left")` → `"L1"`.
115
+ *
116
+ * Use it when naming the rider's resistance controls. Like {@link buttonLabel}, this is the only place
117
+ * allowed to know these strings; never hard-code `"L1"` in a prompt.
118
+ */
119
+ export function bumperLabel(set, side) {
120
+ return (BUMPERS[set] ?? BUMPERS[DEFAULT_GLYPH_SET])[side];
121
+ }
96
122
  //# sourceMappingURL=glyphs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"glyphs.js","sourceRoot":"","sources":["../../src/protocol/glyphs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAcH,0DAA0D;AAC1D,MAAM,CAAC,MAAM,iBAAiB,GAAa,MAAM,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,IAAI,GAAiE;IACzE,2DAA2D;IAC3D,IAAI,EAAE,YAAY,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE;IAChG,WAAW,EAAE,KAAK,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE;IAChG,QAAQ,EAAE,QAAQ,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE;IAChG,KAAK,EAAE,WAAW,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE;CACjG,CAAC;AAEF,gGAAgG;AAChG,MAAM,SAAS,GAAiE;IAC9E,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE;IACpE,WAAW,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE;IAC3E,QAAQ,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE;IACtE,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE;CACtE,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,GAAiE;IAC3E,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;IACzB,WAAW,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE;IACnC,QAAQ,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE;IAC1B,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;CAC3B,CAAC;AAEF,+EAA+E;AAC/E,MAAM,UAAU,GAAqC;IACnD,EAAE,EAAE,GAAG;IACP,IAAI,EAAE,GAAG;IACT,IAAI,EAAE,GAAG;IACT,KAAK,EAAE,GAAG;IACV,GAAG,EAAE,GAAG;IACR,KAAK,EAAE,GAAG;IACV,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;CACZ,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,GAAa,EAAE,IAAgB;IACzD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,iBAAiB,CAAC;IACnD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;AAC3G,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAgB;IAC9C,OAAO,IAAI,IAAI,IAAI,CAAC,iBAAiB,CAAC,CAAC;AACzC,CAAC"}
1
+ {"version":3,"file":"glyphs.js","sourceRoot":"","sources":["../../src/protocol/glyphs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAcH,0DAA0D;AAC1D,MAAM,CAAC,MAAM,iBAAiB,GAAa,MAAM,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,IAAI,GAAiE;IACzE,2DAA2D;IAC3D,IAAI,EAAE,YAAY,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE;IAChG,WAAW,EAAE,KAAK,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE;IAChG,QAAQ,EAAE,QAAQ,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE;IAChG,KAAK,EAAE,WAAW,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE;CACjG,CAAC;AAEF,gGAAgG;AAChG,MAAM,SAAS,GAAiE;IAC9E,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE;IACpE,WAAW,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE;IAC3E,QAAQ,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE;IACtE,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE;CACtE,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,GAAiE;IAC3E,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;IACzB,WAAW,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE;IACnC,QAAQ,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE;IAC1B,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;CAC3B,CAAC;AAEF,+EAA+E;AAC/E,MAAM,UAAU,GAAqC;IACnD,EAAE,EAAE,GAAG;IACP,IAAI,EAAE,GAAG;IACT,IAAI,EAAE,GAAG;IACT,KAAK,EAAE,GAAG;IACV,GAAG,EAAE,GAAG;IACR,KAAK,EAAE,GAAG;IACV,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;CACZ,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,GAAa,EAAE,IAAgB;IACzD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,iBAAiB,CAAC;IACnD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;AAC3G,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAgB;IAC9C,OAAO,IAAI,IAAI,IAAI,CAAC,iBAAiB,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,GAA2E;IACtF,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IACjC,WAAW,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IACxC,QAAQ,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE;IACnC,0EAA0E;IAC1E,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE;CACjC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,GAAa,EAAE,IAAsB;IAC/D,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAC5D,CAAC"}
@@ -9,5 +9,5 @@
9
9
  export declare const RYDR_PROTOCOL_VERSION: 30;
10
10
  /** Semver of this SDK build. Sent in the handshake for telemetry/debugging.
11
11
  * (Bumped to 2.0.0 by `npm version major` on release — see CHANGELOG [Unreleased].) */
12
- export declare const RYDR_SDK_VERSION = "7.0.1";
12
+ export declare const RYDR_SDK_VERSION = "7.1.0";
13
13
  //# sourceMappingURL=version.d.ts.map
@@ -176,5 +176,5 @@
176
176
  export const RYDR_PROTOCOL_VERSION = 30;
177
177
  /** Semver of this SDK build. Sent in the handshake for telemetry/debugging.
178
178
  * (Bumped to 2.0.0 by `npm version major` on release — see CHANGELOG [Unreleased].) */
179
- export const RYDR_SDK_VERSION = "7.0.1";
179
+ export const RYDR_SDK_VERSION = "7.1.0";
180
180
  //# sourceMappingURL=version.js.map
package/dist/ui/README.md CHANGED
@@ -53,6 +53,13 @@ feed its `{x, y, onScreen}` into `setScreenPos`/`setVisible` — don't compute p
53
53
  | `mountOverheatHudBar(host, opts)` | fn | **The whole overheat package**: centred inferno bar + label + COOLING DOWN countdown, composing the vignette and glitch above. One `update(heat01, locked, lockoutRemaining)` per frame. |
54
54
  | `createLabelKeycap(button, opts)` | fn | A keycap for a button printed with a WORD, not a letter — `OPTIONS` ("Menu"/"Options"/"+"), `LT`/`RT`, the stick clicks. A pill, not a pip. |
55
55
  | `mountPauseMenu(host, opts)` | fn | **The in-game menu**, opened by the game's own `OPTIONS` button: game name big in YOUR font, a free `Resume` row + your own rows, each able to advertise its in-game `shortcut`. Takes the controller over while up and eases the trainer. See below. |
56
+ | `mountControllerPad(host, opts)` | fn | **The rider's controller, drawn live** — buttons light while held, stick caps follow the real axes. Pass `press: session`. |
57
+ | `mountControllerMap(host, opts)` | fn | **The controls screen** — that pad flanked by action callouts, each joined by a line to its button. Takes a declarative `ControlAction[]`. |
58
+ | `mountComboDemo(host, opts)` | fn | **"Do this" animation** — loops the combination one action requires: buttons sink in, the stick swings, release, repeat. |
59
+ | `mountControlTrainer(host, opts)` | fn | **Guided lesson** — a title, one panel per step side by side, one shared controller below. Watches the rider: white "do this" → blue "doing it" → green ✓. Fires `onComplete` when every step is done. |
60
+ | `createComboStrip(action, opts)` | fn | Just the chip row for one action (`[L2] + [✕]`), for embedding in your own layout. |
61
+ | `createInputChip(token, opts)` | fn | One control's pictogram, for embedding in your own layout. Delegates to `createKeycap` / `createDpadKeycap` / `createLabelKeycap` for the buttons, and draws its own stick pictograms. The atom the two views are built from. |
62
+ | `DEFAULT_STICK_STYLE` | const | The joystick drawing convention the kit uses (`"profile"`). Override per-call with `stickStyle`. |
56
63
  | `injectCss()` | fn | Force the `.rydr-ui-*` CSS into `<head>` early (components call it for you). |
57
64
 
58
65
  Types: `DiamondButton` (`"DIAMOND_UP"\|"DIAMOND_DOWN"\|"DIAMOND_LEFT"\|"DIAMOND_RIGHT"`), `DirButton`, `KeycapVariant` (`"full"\|"solo"`),
@@ -100,6 +107,118 @@ freeze them in `onOpen`/`onClose`. What it *guarantees* is that a game which kee
100
107
  handlers go quiet, `isDown`/`stick` read resting, and anything held is released first. It also declares
101
108
  `setActivity("menu")` while up and `"playing"` on close, so trainer resistance eases like on any other
102
109
  non-racing screen.
110
+ `LabeledDiamond`, `ActionDiamond`, `Rarity`, `RarityCard`,
111
+ `ControlAction`, `ComboToken`, `StickDir`, `StickPictogramStyle`, `PadInputSource`, `PadBrandKey`, `PadPart`, `PadLayout`,
112
+ `ControllerPad`, `ControllerMap`, `ComboDemo`, `InputChip`, `ComboStrip`.
113
+
114
+ ---
115
+
116
+ ## Showing your controls
117
+
118
+ **Don't hand-build a control legend.** Describe your scheme once and pass it in; the SDK owns which
119
+ physical button that is on the rider's pad, the letter printed on it, the layout, the callout lines and
120
+ the animation.
121
+
122
+ ```ts
123
+ import { mountControllerMap, mountComboDemo, type ControlAction } from "@rydr/game-sdk/ui";
124
+
125
+ const CONTROLS: ControlAction[] = [
126
+ { label: "Se déplacer", combo: ["LSTICK"], hint: "Stick gauche" },
127
+ { label: "Freiner", combo: ["LT"] },
128
+ { label: "Sauter", combo: ["DIAMOND_DOWN"] },
129
+ { label: "Tir chargé", combo: ["RT", "DIAMOND_DOWN"], hint: "Maintenir" },
130
+ { label: "Dash", combo: ["LSTICK_PRESS", "LT"] },
131
+ { label: "Ultime", combo: ["DIAMOND_UP", "DIAMOND_LEFT"], disabled: true },
132
+ ];
133
+
134
+ // STATIC — the whole scheme at once. An inline widget: you own the host.
135
+ mountControllerMap(pauseOverlay, { press: session, actions: CONTROLS, title: "Commandes" });
136
+
137
+ // DYNAMIC — "here's how you do this one", looping.
138
+ const demo = mountComboDemo(tutorialHost, { press: session, action: CONTROLS[4] });
139
+ demo.setAction(CONTROLS[3]); // swaps cleanly mid-loop
140
+ ```
141
+
142
+ A **combo token** is a `ButtonName` (`"LT"`, `"DIAMOND_DOWN"`, `"UP"`), a whole stick (`"LSTICK"` —
143
+ free movement), or a stick pushed one way (`"LSTICK:left"`). One token is a simple control; several
144
+ render as `[L2] + [✕]`, or `[L2] → [✕]` with `sequence: true`.
145
+
146
+ **The callout line points at the first *non-generic* token** — sticks and the d-pad are "generic"
147
+ because many actions share them, so `["LSTICK_PRESS", "LT"]` points at the trigger, not at the stick
148
+ every other action also mentions. Set `anchor` on the action to override. Sides and column order are
149
+ derived from where each button physically sits on the layout, so they stay right across Xbox,
150
+ PlayStation, Switch Pro, Joy-Con and Zwift shapes; set `side` to override.
151
+
152
+ **It's an inline widget, not a screen** — no backdrop, no close button, no fixed positioning. You decide
153
+ whether the host is a pause overlay, a pre-race panel or a settings tab:
154
+
155
+ ```ts
156
+ const overlay = document.createElement("div");
157
+ overlay.style.cssText = "position:fixed;inset:0;display:grid;place-items:center;background:rgba(4,8,14,.72)";
158
+ document.body.appendChild(overlay);
159
+ mountControllerMap(overlay, { press: session, actions: CONTROLS });
160
+ ```
161
+
162
+ Notes worth knowing:
163
+
164
+ - **Never name a letter.** You say `DIAMOND_DOWN`; the rider sees `A`, `✕` or `B` on the right physical
165
+ button. On a Switch Pro the highlight stays on the *bottom* button while the letters shift — that's
166
+ the whole point of the positional vocabulary. The views also **re-letter themselves** if the rider
167
+ swaps pads mid-screen (pass `press: session`), so you never rebuild them.
168
+ - **`LT`/`RT` are your shoulder buttons**, and they letter as `LT` / `L2` / `ZL`. The upper `L1`/`R1`
169
+ bumpers are the shell's resistance − / +, and `View`/`Guide` are the shell's too — all of them are
170
+ drawn faint and can never light, so a rider can tell "this game doesn't use that" from "that isn't
171
+ the game's to use".
172
+ - **Devices differ.** A Zwift Play has no joysticks and no triggers, so they aren't drawn at all. A
173
+ phone exposes only the confirm/back pair — the protocol can't tell us which buttons a device has, so
174
+ pass `available: [...]` if your game knows, and don't gate a required flow behind a rare control.
175
+ - **The joysticks are pictures, not letters.** `LSTICK` draws a well ringed by four arrows (move any
176
+ way), `LSTICK:left` slides the cap that way with one arrow, and `LSTICK_PRESS` draws a **side view** of
177
+ the stick being pushed down — because a click is motion straight at the viewer, which a top-down
178
+ drawing cannot show, and because "L3" tells a rider nothing about what to do with their thumb (the
179
+ brand letter is still in the chip's tooltip). Three conventions exist via `stickStyle`: `"profile"`
180
+ (the default — use it), `"topdown"`, `"glyph"`. Every *button* shape comes from the keycap factories, so
181
+ a trigger or OPTIONS reads identically in a legend and in an in-game prompt.
182
+ - Tuned for up to **5 callouts per side**; past that it warns rather than dropping one.
183
+ - Live catalog: `npm run showcase` in the SDK repo, or the platform's `/game-ui` route.
184
+
185
+ ### Teaching a control — `mountControlTrainer`
186
+
187
+ For a lesson the rider has to *perform*, not just watch. Built for paired controls: one concept, two
188
+ linked halves, **one shared controller drawing** below both panels.
189
+
190
+ ```ts
191
+ mountControlTrainer(modalBody, {
192
+ title: "Changer la résistance",
193
+ press: shellInput,
194
+ steps: [
195
+ { label: "Réduire la résistance", combo: ["LSTICK_PRESS", "LB"] },
196
+ { label: "Augmenter la résistance", combo: ["LSTICK_PRESS", "RB"] },
197
+ ],
198
+ onComplete: () => closeModal(), // the component never closes itself
199
+ });
200
+ ```
201
+
202
+ Each step walks three states, on both its chips and the shared pad: **whitish + pulsing** ("do this"),
203
+ **accent, pulse stops** ("you're doing it"), **green + ✓** ("done"). The prompt shows **one step at a
204
+ time, alternating**, and builds each combination **cumulatively** — the stick click lights, stays lit, and
205
+ the shoulder joins it a beat later; then the other half takes its turn, and it loops. **The two surfaces speak different languages.** The
206
+ **pad** is physical — white = *press this*, blue = *you're pressing it*, and **never green**; white and blue
207
+ have already taught the rider that colour there means *now*, so progress as a third colour reads as "and this
208
+ one too". The **panels** are logical — grey = to do, green + ✓ = done, and that stays for the rest of the
209
+ lesson. The confirmation still lands on the controls the rider just pressed, because a panel's chips picture
210
+ the same combination one line up. Nothing special happens to the pad at completion: blue persists while the
211
+ controls are held and clears when they let go, which is simply the truth of the hardware. A step goes green **the moment its whole
212
+ combination is held** — holding all of it *is* doing it, so there is nothing to wait for, and it stays green
213
+ whether or not the rider keeps the controls down. Blue is the state in between: on a two-control step, after
214
+ the first control and before the second. Focus also lingers on a step that has just gone green before the
215
+ rotation moves on, so "you did it" lands before "now this one".
216
+
217
+ > **⚠️ `LB` / `RB` (the upper shoulders) are shell-owned and a game cannot observe them.** The SDK client turns those
218
+ > keys into `trainer.adjustResistance` intents; `session.onButton` never fires for them. A game can train
219
+ > its *own* buttons, but a resistance lesson has to be driven by the shell — which is also the only place
220
+ > that lesson belongs. `mountControllerMap` may still *point at* them; only the interactive trainer needs
221
+ > a source that sees them.
103
222
 
104
223
  ---
105
224
 
@@ -0,0 +1,74 @@
1
+ /**
2
+ * COMBO DEMO — the dynamic view: a looping animation of the combination a rider must perform.
3
+ *
4
+ * ```ts
5
+ * const demo = mountComboDemo(host, {
6
+ * press: session, // for the rider's lettering, NOT for input
7
+ * action: { label: "Dash", combo: ["LT", "LSTICK:left"] },
8
+ * });
9
+ * demo.setAction(next); // swaps cleanly mid-loop
10
+ * ```
11
+ *
12
+ * The timeline is **derived from the combo**, never authored per action, which is what keeps it generic:
13
+ *
14
+ * - *hold* (default) — idle, then each control engages in turn 200 ms apart (a stick eases to full
15
+ * deflection rather than snapping), everything holds together for a beat, releases, and repeats. The
16
+ * stagger is the point: it shows that the controls are held *simultaneously*, which a single
17
+ * all-at-once flash does not.
18
+ * - *sequence* (`action.sequence`) — each control presses, holds and releases in turn, with the strip's
19
+ * `→` separators filling in behind the current step.
20
+ * - a stick token with no direction sweeps once around its travel circle: "any direction".
21
+ *
22
+ * It drives exactly the same `PadDriver.setPart` / `setStick` and `ComboStrip.setPressed` that real input
23
+ * drives in the static view, so the demo and the live pad cannot drift apart — there is one renderer.
24
+ *
25
+ * **Self-driving, unlike `createPowerRace`.** This is an attract animation with no gameplay coupling, so
26
+ * it owns its own `requestAnimationFrame` rather than asking the game for a `tick(dt)`. It pauses while
27
+ * the tab is hidden, and `play()`/`pause()` are there for the caller.
28
+ *
29
+ * **`prefers-reduced-motion` is honoured by not animating at all**: the demo renders the fully-engaged end
30
+ * state statically. A looping motion graphic is precisely what that media query exists to suppress, and a
31
+ * still frame of "all of this, held together" still answers the rider's question.
32
+ */
33
+ import type { GlyphSet } from "../../protocol/glyphs.js";
34
+ import { type ControllerPad, type PadInputSource } from "./controller-pad.js";
35
+ import type { StickPictogramStyle } from "./input-chip.js";
36
+ import { type ControlAction } from "./tokens.js";
37
+ import type { PadBrandKey } from "./types.js";
38
+ export interface ComboDemoOptions {
39
+ /** The single action to demonstrate. */
40
+ action: ControlAction;
41
+ /**
42
+ * A session, used ONLY to learn the rider's lettering and to follow pad swaps — the demo deliberately
43
+ * does not wire it to input. The animation is the sole writer of the drawing's state; letting real
44
+ * presses write it too would make the two fight for the same classes every frame.
45
+ */
46
+ press?: PadInputSource;
47
+ brand?: PadBrandKey;
48
+ glyphSet?: GlyphSet;
49
+ /** Draw the controller. Default true; `false` gives a compact chips-only cue. */
50
+ showPad?: boolean;
51
+ /** Show the action's label above the animation. Default true. */
52
+ showLabel?: boolean;
53
+ size?: "sm" | "md" | "lg";
54
+ labels?: boolean;
55
+ colored?: boolean;
56
+ accent?: string;
57
+ /** Drawing convention for the joystick pictograms in the chips. See {@link StickPictogramStyle}. */
58
+ stickStyle?: StickPictogramStyle;
59
+ /** Playback rate. `1` = normal, `0.5` = half speed for a slow walkthrough. */
60
+ speed?: number;
61
+ /** Called each time the loop completes a full cycle. */
62
+ onCycle?: () => void;
63
+ }
64
+ export interface ComboDemo {
65
+ readonly root: HTMLDivElement;
66
+ readonly pad: ControllerPad | undefined;
67
+ setAction(action: ControlAction): void;
68
+ play(): void;
69
+ pause(): void;
70
+ setSpeed(mult: number): void;
71
+ dispose(): void;
72
+ }
73
+ export declare function mountComboDemo(host: HTMLElement, opts: ComboDemoOptions): ComboDemo;
74
+ //# sourceMappingURL=combo-demo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combo-demo.d.ts","sourceRoot":"","sources":["../../../src/ui/controller/combo-demo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzD,OAAO,EAAsB,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAClG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAG3D,OAAO,EAAgC,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,KAAK,EAAE,WAAW,EAAW,MAAM,YAAY,CAAC;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,MAAM,EAAE,aAAa,CAAC;IACtB;;;;OAIG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,iFAAiF;IACjF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iEAAiE;IACjE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oGAAoG;IACpG,UAAU,CAAC,EAAE,mBAAmB,CAAC;IACjC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,SAAS,CAAC;IACxC,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IACvC,IAAI,IAAI,IAAI,CAAC;IACb,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,IAAI,IAAI,CAAC;CACjB;AA4CD,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,gBAAgB,GAAG,SAAS,CA4OnF"}
@@ -0,0 +1,295 @@
1
+ /**
2
+ * COMBO DEMO — the dynamic view: a looping animation of the combination a rider must perform.
3
+ *
4
+ * ```ts
5
+ * const demo = mountComboDemo(host, {
6
+ * press: session, // for the rider's lettering, NOT for input
7
+ * action: { label: "Dash", combo: ["LT", "LSTICK:left"] },
8
+ * });
9
+ * demo.setAction(next); // swaps cleanly mid-loop
10
+ * ```
11
+ *
12
+ * The timeline is **derived from the combo**, never authored per action, which is what keeps it generic:
13
+ *
14
+ * - *hold* (default) — idle, then each control engages in turn 200 ms apart (a stick eases to full
15
+ * deflection rather than snapping), everything holds together for a beat, releases, and repeats. The
16
+ * stagger is the point: it shows that the controls are held *simultaneously*, which a single
17
+ * all-at-once flash does not.
18
+ * - *sequence* (`action.sequence`) — each control presses, holds and releases in turn, with the strip's
19
+ * `→` separators filling in behind the current step.
20
+ * - a stick token with no direction sweeps once around its travel circle: "any direction".
21
+ *
22
+ * It drives exactly the same `PadDriver.setPart` / `setStick` and `ComboStrip.setPressed` that real input
23
+ * drives in the static view, so the demo and the live pad cannot drift apart — there is one renderer.
24
+ *
25
+ * **Self-driving, unlike `createPowerRace`.** This is an attract animation with no gameplay coupling, so
26
+ * it owns its own `requestAnimationFrame` rather than asking the game for a `tick(dt)`. It pauses while
27
+ * the tab is hidden, and `play()`/`pause()` are there for the caller.
28
+ *
29
+ * **`prefers-reduced-motion` is honoured by not animating at all**: the demo renders the fully-engaged end
30
+ * state statically. A looping motion graphic is precisely what that media query exists to suppress, and a
31
+ * still frame of "all of this, held together" still answers the rider's question.
32
+ */
33
+ import { createComboStrip } from "./combo-strip.js";
34
+ import { mountControllerPad } from "./controller-pad.js";
35
+ import { litPartOfControl } from "./parts-map.js";
36
+ import { injectPadCss } from "./styles.js";
37
+ import { parseToken, STICK_DIR_VECTOR } from "./tokens.js";
38
+ // Timings in seconds. Tuned to read at a glance from a few feet away, not to be brisk.
39
+ const HOLD_IDLE = 0.4;
40
+ const HOLD_STAGGER = 0.2;
41
+ const HOLD_RAMP = 0.2;
42
+ const HOLD_SUSTAIN = 0.7;
43
+ const HOLD_RELEASE = 0.25;
44
+ const HOLD_TAIL = 0.5;
45
+ const SEQ_IDLE = 0.4;
46
+ const SEQ_PRESS = 0.35;
47
+ const SEQ_GAP = 0.15;
48
+ const SEQ_TAIL = 0.4;
49
+ /** One full revolution of an undirected stick sweep. */
50
+ const SWEEP_PERIOD = 1.2;
51
+ const smoothstep = (t) => {
52
+ const c = Math.max(0, Math.min(1, t));
53
+ return c * c * (3 - 2 * c);
54
+ };
55
+ function channelsOf(action) {
56
+ return action.combo.map((token, index) => {
57
+ const t = parseToken(token);
58
+ if (t.kind === "stick") {
59
+ return {
60
+ index,
61
+ stick: {
62
+ side: t.stick === "LSTICK" ? "l" : "r",
63
+ vec: t.dir ? STICK_DIR_VECTOR[t.dir] : null, // null = undirected → sweep
64
+ },
65
+ };
66
+ }
67
+ return { index, part: litPartOfControl(t.name) };
68
+ });
69
+ }
70
+ export function mountComboDemo(host, opts) {
71
+ injectPadCss();
72
+ const root = document.createElement("div");
73
+ root.className = "rydr-paddemo";
74
+ host.appendChild(root);
75
+ const showPad = opts.showPad ?? true;
76
+ const showLabel = opts.showLabel ?? true;
77
+ let speed = opts.speed ?? 1;
78
+ let labelEl;
79
+ let hintEl;
80
+ if (showLabel) {
81
+ labelEl = document.createElement("div");
82
+ labelEl.className = "rydr-paddemo-label";
83
+ root.appendChild(labelEl);
84
+ hintEl = document.createElement("div");
85
+ hintEl.className = "rydr-paddemo-hint";
86
+ root.appendChild(hintEl);
87
+ }
88
+ const padCell = document.createElement("div");
89
+ const stripCell = document.createElement("div");
90
+ if (showPad)
91
+ root.appendChild(padCell);
92
+ root.appendChild(stripCell);
93
+ // Glyph-only shim: gives the pad the rider's lettering and pad-swap notifications, while its inert
94
+ // `onButton` guarantees real presses never write the drawing the animation owns.
95
+ const glyphOnly = opts.press && {
96
+ onButton: () => () => { },
97
+ hardware: opts.press.hardware,
98
+ };
99
+ const pad = showPad
100
+ ? mountControllerPad(padCell, {
101
+ press: glyphOnly,
102
+ brand: opts.brand,
103
+ glyphSet: opts.glyphSet,
104
+ size: opts.size ?? "md",
105
+ labels: opts.labels,
106
+ accent: opts.accent,
107
+ })
108
+ : undefined;
109
+ let action = opts.action;
110
+ let channels = channelsOf(action);
111
+ let strip;
112
+ function buildStrip() {
113
+ strip = createComboStrip(action, {
114
+ glyphSet: opts.glyphSet ?? pad?.glyphSet() ?? undefined,
115
+ colored: opts.colored,
116
+ stickStyle: opts.stickStyle,
117
+ });
118
+ stripCell.textContent = "";
119
+ stripCell.appendChild(strip.el);
120
+ }
121
+ function syncLabel() {
122
+ if (labelEl)
123
+ labelEl.textContent = action.label;
124
+ if (hintEl) {
125
+ hintEl.textContent = action.hint ?? "";
126
+ hintEl.style.display = action.hint ? "" : "none";
127
+ }
128
+ }
129
+ buildStrip();
130
+ syncLabel();
131
+ /** Total cycle length, in seconds at speed 1. */
132
+ function cycleLength() {
133
+ const n = Math.max(1, action.combo.length);
134
+ if (action.sequence)
135
+ return SEQ_IDLE + n * (SEQ_PRESS + SEQ_GAP) + SEQ_TAIL;
136
+ return HOLD_IDLE + (n - 1) * HOLD_STAGGER + HOLD_RAMP + HOLD_SUSTAIN + HOLD_RELEASE + HOLD_TAIL;
137
+ }
138
+ /**
139
+ * Write the state of the whole combination at cycle time `t` (seconds).
140
+ *
141
+ * One function for both modes and for the reduced-motion still, so there is exactly one place that
142
+ * decides what "engaged" looks like.
143
+ */
144
+ function renderAt(t) {
145
+ const driver = pad?.driver();
146
+ if (action.sequence) {
147
+ let completed = 0;
148
+ channels.forEach((ch) => {
149
+ const start = SEQ_IDLE + ch.index * (SEQ_PRESS + SEQ_GAP);
150
+ const end = start + SEQ_PRESS;
151
+ if (t >= end)
152
+ completed++;
153
+ const on = t >= start && t < end;
154
+ const ramp = on ? smoothstep((t - start) / Math.min(0.12, SEQ_PRESS / 3)) : 0;
155
+ applyChannel(ch, on, ramp, t - start, driver);
156
+ });
157
+ strip.setStep(completed);
158
+ return;
159
+ }
160
+ const n = Math.max(1, action.combo.length);
161
+ const allDown = HOLD_IDLE + (n - 1) * HOLD_STAGGER + HOLD_RAMP;
162
+ const sustainEnd = allDown + HOLD_SUSTAIN;
163
+ const releaseEnd = sustainEnd + HOLD_RELEASE;
164
+ for (const ch of channels) {
165
+ const onset = HOLD_IDLE + ch.index * HOLD_STAGGER;
166
+ const on = t >= onset && t < sustainEnd;
167
+ let ramp = 0;
168
+ if (t >= onset && t < sustainEnd)
169
+ ramp = smoothstep((t - onset) / HOLD_RAMP);
170
+ else if (t >= sustainEnd && t < releaseEnd)
171
+ ramp = 1 - smoothstep((t - sustainEnd) / HOLD_RELEASE);
172
+ applyChannel(ch, on, ramp, t - onset, driver);
173
+ }
174
+ }
175
+ function applyChannel(ch, on, ramp, since, driver) {
176
+ if (ch.stick) {
177
+ let x;
178
+ let y;
179
+ if (ch.stick.vec) {
180
+ x = ch.stick.vec[0] * ramp;
181
+ y = ch.stick.vec[1] * ramp;
182
+ }
183
+ else {
184
+ // Undirected: sweep the cap around its circle so it reads as "any direction".
185
+ const angle = (Math.max(0, since) / SWEEP_PERIOD) * Math.PI * 2;
186
+ x = Math.cos(angle) * ramp;
187
+ y = Math.sin(angle) * ramp;
188
+ }
189
+ driver?.setStick(ch.stick.side, x, y);
190
+ strip.setDeflectionAt(ch.index, x, y);
191
+ // A stick is "pressed" in the strip once it's meaningfully deflected — matches the pad's own glow.
192
+ strip.setPressedAt(ch.index, ramp > 0.35);
193
+ return;
194
+ }
195
+ strip.setPressedAt(ch.index, on);
196
+ if (ch.part && driver) {
197
+ driver.setPart(ch.part, on);
198
+ if (ch.part === "lt")
199
+ driver.setMeter("l", on ? 1 : 0);
200
+ if (ch.part === "rt")
201
+ driver.setMeter("r", on ? 1 : 0);
202
+ }
203
+ }
204
+ /** The still frame for reduced motion: everything engaged, sticks at full deflection. */
205
+ function renderStill() {
206
+ const driver = pad?.driver();
207
+ for (const ch of channels)
208
+ applyChannel(ch, true, 1, 0, driver);
209
+ strip.setStep(action.combo.length);
210
+ }
211
+ // ── the loop ────────────────────────────────────────────────────────────────────────────────────
212
+ const reduceMotion = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
213
+ let raf = 0;
214
+ let elapsed = 0;
215
+ let lastTs = 0;
216
+ let wantPlaying = true;
217
+ function frame(ts) {
218
+ raf = requestAnimationFrame(frame);
219
+ const dt = lastTs ? Math.min(0.05, (ts - lastTs) / 1000) : 0;
220
+ lastTs = ts;
221
+ elapsed += dt * speed;
222
+ const len = cycleLength();
223
+ if (elapsed >= len) {
224
+ elapsed -= len * Math.floor(elapsed / len);
225
+ opts.onCycle?.();
226
+ }
227
+ renderAt(elapsed);
228
+ }
229
+ function start() {
230
+ if (raf || reduceMotion)
231
+ return;
232
+ lastTs = 0;
233
+ raf = requestAnimationFrame(frame);
234
+ }
235
+ function stop() {
236
+ if (raf)
237
+ cancelAnimationFrame(raf);
238
+ raf = 0;
239
+ }
240
+ const onVisibility = () => {
241
+ if (document.visibilityState === "hidden")
242
+ stop();
243
+ else if (wantPlaying)
244
+ start();
245
+ };
246
+ document.addEventListener("visibilitychange", onVisibility);
247
+ if (reduceMotion)
248
+ renderStill();
249
+ else
250
+ start();
251
+ // A pad swap rebuilds the drawing (new nodes, new driver) and re-letters, so the chips must follow.
252
+ const offRebuild = pad?.onRebuild(() => {
253
+ buildStrip();
254
+ if (reduceMotion)
255
+ renderStill();
256
+ });
257
+ function reset(next) {
258
+ action = next;
259
+ channels = channelsOf(action);
260
+ // Release the previous combination before the new one draws, or a part the old action lit and the
261
+ // new one doesn't would stay lit forever.
262
+ pad?.driver().clearAll();
263
+ buildStrip();
264
+ syncLabel();
265
+ elapsed = 0;
266
+ lastTs = 0;
267
+ if (reduceMotion)
268
+ renderStill();
269
+ }
270
+ return {
271
+ root,
272
+ pad,
273
+ setAction: reset,
274
+ play() {
275
+ wantPlaying = true;
276
+ start();
277
+ },
278
+ pause() {
279
+ wantPlaying = false;
280
+ stop();
281
+ },
282
+ setSpeed(mult) {
283
+ speed = Math.max(0.05, mult);
284
+ },
285
+ dispose() {
286
+ stop();
287
+ document.removeEventListener("visibilitychange", onVisibility);
288
+ offRebuild?.();
289
+ strip.dispose();
290
+ pad?.dispose();
291
+ root.remove();
292
+ },
293
+ };
294
+ }
295
+ //# sourceMappingURL=combo-demo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combo-demo.js","sourceRoot":"","sources":["../../../src/ui/controller/combo-demo.ts"],"names":[],"mappings":"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"}