@rydr/game-sdk 7.0.0 → 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 (99) 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/three/perf-overlay.d.ts +52 -2
  9. package/dist/three/perf-overlay.d.ts.map +1 -1
  10. package/dist/three/perf-overlay.js +188 -3
  11. package/dist/three/perf-overlay.js.map +1 -1
  12. package/dist/ui/README.md +119 -0
  13. package/dist/ui/controller/combo-demo.d.ts +74 -0
  14. package/dist/ui/controller/combo-demo.d.ts.map +1 -0
  15. package/dist/ui/controller/combo-demo.js +295 -0
  16. package/dist/ui/controller/combo-demo.js.map +1 -0
  17. package/dist/ui/controller/combo-strip.d.ts +36 -0
  18. package/dist/ui/controller/combo-strip.d.ts.map +1 -0
  19. package/dist/ui/controller/combo-strip.js +66 -0
  20. package/dist/ui/controller/combo-strip.js.map +1 -0
  21. package/dist/ui/controller/control-trainer.d.ts +132 -0
  22. package/dist/ui/controller/control-trainer.d.ts.map +1 -0
  23. package/dist/ui/controller/control-trainer.js +570 -0
  24. package/dist/ui/controller/control-trainer.js.map +1 -0
  25. package/dist/ui/controller/controller-map.d.ts +82 -0
  26. package/dist/ui/controller/controller-map.d.ts.map +1 -0
  27. package/dist/ui/controller/controller-map.js +291 -0
  28. package/dist/ui/controller/controller-map.js.map +1 -0
  29. package/dist/ui/controller/controller-pad.d.ts +122 -0
  30. package/dist/ui/controller/controller-pad.d.ts.map +1 -0
  31. package/dist/ui/controller/controller-pad.js +200 -0
  32. package/dist/ui/controller/controller-pad.js.map +1 -0
  33. package/dist/ui/controller/index.d.ts +26 -0
  34. package/dist/ui/controller/index.d.ts.map +1 -0
  35. package/dist/ui/controller/index.js +26 -0
  36. package/dist/ui/controller/index.js.map +1 -0
  37. package/dist/ui/controller/input-chip.d.ts +100 -0
  38. package/dist/ui/controller/input-chip.d.ts.map +1 -0
  39. package/dist/ui/controller/input-chip.js +322 -0
  40. package/dist/ui/controller/input-chip.js.map +1 -0
  41. package/dist/ui/controller/layouts/dualsense.d.ts +12 -0
  42. package/dist/ui/controller/layouts/dualsense.d.ts.map +1 -0
  43. package/dist/ui/controller/layouts/dualsense.js +64 -0
  44. package/dist/ui/controller/layouts/dualsense.js.map +1 -0
  45. package/dist/ui/controller/layouts/index.d.ts +20 -0
  46. package/dist/ui/controller/layouts/index.d.ts.map +1 -0
  47. package/dist/ui/controller/layouts/index.js +34 -0
  48. package/dist/ui/controller/layouts/index.js.map +1 -0
  49. package/dist/ui/controller/layouts/joycon.d.ts +21 -0
  50. package/dist/ui/controller/layouts/joycon.d.ts.map +1 -0
  51. package/dist/ui/controller/layouts/joycon.js +76 -0
  52. package/dist/ui/controller/layouts/joycon.js.map +1 -0
  53. package/dist/ui/controller/layouts/switch-pro.d.ts +12 -0
  54. package/dist/ui/controller/layouts/switch-pro.d.ts.map +1 -0
  55. package/dist/ui/controller/layouts/switch-pro.js +60 -0
  56. package/dist/ui/controller/layouts/switch-pro.js.map +1 -0
  57. package/dist/ui/controller/layouts/xbox.d.ts +11 -0
  58. package/dist/ui/controller/layouts/xbox.d.ts.map +1 -0
  59. package/dist/ui/controller/layouts/xbox.js +59 -0
  60. package/dist/ui/controller/layouts/xbox.js.map +1 -0
  61. package/dist/ui/controller/layouts/zwift.d.ts +27 -0
  62. package/dist/ui/controller/layouts/zwift.d.ts.map +1 -0
  63. package/dist/ui/controller/layouts/zwift.js +62 -0
  64. package/dist/ui/controller/layouts/zwift.js.map +1 -0
  65. package/dist/ui/controller/pad-svg.d.ts +105 -0
  66. package/dist/ui/controller/pad-svg.d.ts.map +1 -0
  67. package/dist/ui/controller/pad-svg.js +289 -0
  68. package/dist/ui/controller/pad-svg.js.map +1 -0
  69. package/dist/ui/controller/parts-map.d.ts +96 -0
  70. package/dist/ui/controller/parts-map.d.ts.map +1 -0
  71. package/dist/ui/controller/parts-map.js +187 -0
  72. package/dist/ui/controller/parts-map.js.map +1 -0
  73. package/dist/ui/controller/styles.d.ts +19 -0
  74. package/dist/ui/controller/styles.d.ts.map +1 -0
  75. package/dist/ui/controller/styles.js +364 -0
  76. package/dist/ui/controller/styles.js.map +1 -0
  77. package/dist/ui/controller/tokens.d.ts +106 -0
  78. package/dist/ui/controller/tokens.d.ts.map +1 -0
  79. package/dist/ui/controller/tokens.js +50 -0
  80. package/dist/ui/controller/tokens.js.map +1 -0
  81. package/dist/ui/controller/types.d.ts +157 -0
  82. package/dist/ui/controller/types.d.ts.map +1 -0
  83. package/dist/ui/controller/types.js +61 -0
  84. package/dist/ui/controller/types.js.map +1 -0
  85. package/dist/ui/index.d.ts +8 -0
  86. package/dist/ui/index.d.ts.map +1 -1
  87. package/dist/ui/index.js +8 -0
  88. package/dist/ui/index.js.map +1 -1
  89. package/dist/ui/keycap.d.ts +14 -0
  90. package/dist/ui/keycap.d.ts.map +1 -1
  91. package/dist/ui/keycap.js +5 -0
  92. package/dist/ui/keycap.js.map +1 -1
  93. package/dist/ui/showcase/gallery.d.ts +13 -1
  94. package/dist/ui/showcase/gallery.d.ts.map +1 -1
  95. package/dist/ui/showcase/gallery.js.map +1 -1
  96. package/dist/ui/showcase/index.d.ts.map +1 -1
  97. package/dist/ui/showcase/index.js +257 -1
  98. package/dist/ui/showcase/index.js.map +1 -1
  99. package/package.json +1 -1
@@ -0,0 +1,82 @@
1
+ /**
2
+ * CONTROLLER MAP — the static "here are your controls" view: the live pad, flanked by callout cards, each
3
+ * joined by a line to the button it belongs to.
4
+ *
5
+ * ```ts
6
+ * const map = mountControllerMap(host, {
7
+ * press: session,
8
+ * actions: [
9
+ * { label: "Se déplacer", combo: ["LSTICK"] },
10
+ * { label: "Freiner", combo: ["LT"] },
11
+ * { label: "Tir chargé", combo: ["RT", "DIAMOND_DOWN"] },
12
+ * { label: "Dash", combo: ["LT", "LSTICK:left"] },
13
+ * ],
14
+ * });
15
+ * ```
16
+ *
17
+ * **It is an inline widget, not a screen.** It renders a self-sized block into whatever host you give it
18
+ * — no fixed positioning, no backdrop, no close button, no back-button handling. Whether that host is a
19
+ * pause overlay, a pre-race panel or a settings tab is the game's call, same as every other component in
20
+ * the kit. For a pause overlay, own the host yourself:
21
+ *
22
+ * ```ts
23
+ * const overlay = document.createElement("div");
24
+ * overlay.style.cssText = "position:fixed; inset:0; display:grid; place-items:center; background:rgba(4,8,14,.72)";
25
+ * document.body.appendChild(overlay);
26
+ * mountControllerMap(overlay, { press: session, actions });
27
+ * ```
28
+ *
29
+ * The one genuinely new mechanic here is the **connector layer**. The lines join DOM cards to SVG nodes,
30
+ * so they can't live in the pad's own viewBox — they're drawn in page-pixel space from measured
31
+ * rectangles. Re-routing is therefore layout-dependent and is triggered by a `ResizeObserver` on the root
32
+ * *and on every card* (a label wrapping to two lines moves every card below it), by `document.fonts.ready`
33
+ * (a web font swapping in reflows labels after first paint), and by a pad re-letter. Every trigger is
34
+ * coalesced into a single `requestAnimationFrame` — this never runs per frame.
35
+ */
36
+ import type { ButtonName } from "../../protocol/buttons.js";
37
+ import type { GlyphSet } from "../../protocol/glyphs.js";
38
+ import type { StickPictogramStyle } from "./input-chip.js";
39
+ import { type ControllerPad, type PadInputSource } from "./controller-pad.js";
40
+ import type { ControlAction } from "./tokens.js";
41
+ import type { PadBrandKey } from "./types.js";
42
+ export interface ControllerMapOptions {
43
+ /** What the rider can do, and what they press to do it. */
44
+ actions: readonly ControlAction[];
45
+ /**
46
+ * The rider's input source. Used for the LETTERING (and to re-letter on a pad swap) even when
47
+ * {@link live} is false — a controls screen must print the letters on the pad actually in their hands.
48
+ */
49
+ press?: PadInputSource;
50
+ /**
51
+ * Reflect real presses: light the pad and sink the matching chips as the rider presses. **Default
52
+ * false** — this is a reference diagram, and it stays legible precisely by not moving. A rider
53
+ * resting a thumb on a stick would otherwise light half the drawing while they are trying to read it.
54
+ * Pass `true` where a live "watch it respond" affordance is the point.
55
+ */
56
+ live?: boolean;
57
+ brand?: PadBrandKey;
58
+ glyphSet?: GlyphSet;
59
+ /** Draw the pad's own button letters. Default true. */
60
+ labels?: boolean;
61
+ /** Per-role hue on face keycaps in the chips (confirm green, back amber). Default true. */
62
+ colored?: boolean;
63
+ /** Drawing convention for the joystick pictograms in the chips. See {@link StickPictogramStyle}. */
64
+ stickStyle?: StickPictogramStyle;
65
+ /** Pad size preset. Default `"lg"` — this view is meant to be read. */
66
+ size?: "sm" | "md" | "lg";
67
+ accent?: string;
68
+ /** Optional heading above the pad. */
69
+ title?: string;
70
+ /** Buttons the rider's device actually has; the rest are drawn faint. See `ControllerPadOptions`. */
71
+ available?: readonly ButtonName[];
72
+ }
73
+ export interface ControllerMap {
74
+ readonly root: HTMLDivElement;
75
+ readonly pad: ControllerPad;
76
+ /** Replace the action list. Rebuilds the callouts and re-routes the lines. */
77
+ setActions(actions: readonly ControlAction[]): void;
78
+ setVisible(on: boolean): void;
79
+ dispose(): void;
80
+ }
81
+ export declare function mountControllerMap(host: HTMLElement, opts: ControllerMapOptions): ControllerMap;
82
+ //# sourceMappingURL=controller-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller-map.d.ts","sourceRoot":"","sources":["../../../src/ui/controller/controller-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAGzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAsB,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGlG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAW,MAAM,YAAY,CAAC;AAEvD,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;IAClC;;;OAGG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,uDAAuD;IACvD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2FAA2F;IAC3F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oGAAoG;IACpG,UAAU,CAAC,EAAE,mBAAmB,CAAC;IACjC,uEAAuE;IACvE,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qGAAqG;IACrG,SAAS,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAC5B,8EAA8E;IAC9E,UAAU,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI,CAAC;IACpD,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9B,OAAO,IAAI,IAAI,CAAC;CACjB;AA4BD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,oBAAoB,GAAG,aAAa,CAmQ/F"}
@@ -0,0 +1,291 @@
1
+ /**
2
+ * CONTROLLER MAP — the static "here are your controls" view: the live pad, flanked by callout cards, each
3
+ * joined by a line to the button it belongs to.
4
+ *
5
+ * ```ts
6
+ * const map = mountControllerMap(host, {
7
+ * press: session,
8
+ * actions: [
9
+ * { label: "Se déplacer", combo: ["LSTICK"] },
10
+ * { label: "Freiner", combo: ["LT"] },
11
+ * { label: "Tir chargé", combo: ["RT", "DIAMOND_DOWN"] },
12
+ * { label: "Dash", combo: ["LT", "LSTICK:left"] },
13
+ * ],
14
+ * });
15
+ * ```
16
+ *
17
+ * **It is an inline widget, not a screen.** It renders a self-sized block into whatever host you give it
18
+ * — no fixed positioning, no backdrop, no close button, no back-button handling. Whether that host is a
19
+ * pause overlay, a pre-race panel or a settings tab is the game's call, same as every other component in
20
+ * the kit. For a pause overlay, own the host yourself:
21
+ *
22
+ * ```ts
23
+ * const overlay = document.createElement("div");
24
+ * overlay.style.cssText = "position:fixed; inset:0; display:grid; place-items:center; background:rgba(4,8,14,.72)";
25
+ * document.body.appendChild(overlay);
26
+ * mountControllerMap(overlay, { press: session, actions });
27
+ * ```
28
+ *
29
+ * The one genuinely new mechanic here is the **connector layer**. The lines join DOM cards to SVG nodes,
30
+ * so they can't live in the pad's own viewBox — they're drawn in page-pixel space from measured
31
+ * rectangles. Re-routing is therefore layout-dependent and is triggered by a `ResizeObserver` on the root
32
+ * *and on every card* (a label wrapping to two lines moves every card below it), by `document.fonts.ready`
33
+ * (a web font swapping in reflows labels after first paint), and by a pad re-letter. Every trigger is
34
+ * coalesced into a single `requestAnimationFrame` — this never runs per frame.
35
+ */
36
+ import { buildCard } from "../card.js";
37
+ import { createComboStrip } from "./combo-strip.js";
38
+ import { mountControllerPad } from "./controller-pad.js";
39
+ import { anchorPartOfToken, anchorTokenOf, partCentreOf, sideOfPart } from "./parts-map.js";
40
+ import { injectPadCss } from "./styles.js";
41
+ const NS = "http://www.w3.org/2000/svg";
42
+ /**
43
+ * Beyond this many callouts per side the lines get dense and the cards squeeze. The component still
44
+ * renders — it warns rather than truncating, because silently dropping a control from a controls screen
45
+ * is worse than a crowded one.
46
+ */
47
+ const SOFT_CAP_PER_SIDE = 5;
48
+ /**
49
+ * The pad column's width per size preset. Upper bounds match the `.rydr-pad.sm/.md/.lg` max-widths; the
50
+ * `clamp` lets the pad shrink on a narrow host instead of squeezing the callout columns to nothing.
51
+ */
52
+ const PAD_COL_WIDTH = {
53
+ sm: "clamp(150px, 20vw, 220px)",
54
+ md: "clamp(190px, 28vw, 340px)",
55
+ lg: "clamp(220px, 34vw, 460px)",
56
+ };
57
+ export function mountControllerMap(host, opts) {
58
+ injectPadCss();
59
+ const root = document.createElement("div");
60
+ root.className = "rydr-padmap";
61
+ // The shared card padding is asymmetric by default to seat a straddling keycap; these callouts have
62
+ // none, so even padding reads better. Set on the ancestor, which is the documented way to retune it.
63
+ root.style.setProperty("--rydr-ui-card-pad", "12px 16px 14px");
64
+ let titleEl;
65
+ if (opts.title) {
66
+ titleEl = document.createElement("h3");
67
+ titleEl.className = "rydr-padmap-title";
68
+ titleEl.textContent = opts.title;
69
+ root.appendChild(titleEl);
70
+ }
71
+ // The pad column needs a DEFINITE width (see styles.ts) — derive it from the size preset rather than
72
+ // letting an `auto` track and a `width:100%` SVG try to size each other.
73
+ const size = opts.size ?? "lg";
74
+ root.style.setProperty("--rydr-padmap-pad-w", PAD_COL_WIDTH[size]);
75
+ const leftCol = document.createElement("ul");
76
+ leftCol.className = "rydr-padmap-col left";
77
+ const padCell = document.createElement("div");
78
+ padCell.className = "rydr-padmap-padcell";
79
+ const rightCol = document.createElement("ul");
80
+ rightCol.className = "rydr-padmap-col right";
81
+ root.appendChild(leftCol);
82
+ root.appendChild(padCell);
83
+ root.appendChild(rightCol);
84
+ const lines = document.createElementNS(NS, "svg");
85
+ lines.setAttribute("class", "rydr-padmap-lines");
86
+ lines.setAttribute("aria-hidden", "true");
87
+ root.appendChild(lines);
88
+ host.appendChild(root);
89
+ // Glyph-only unless `live`: the pad still learns the rider's lettering and follows a pad swap, but its
90
+ // inert `onButton` means nothing reflects presses.
91
+ const padPress = opts.press && (opts.live ? opts.press : { onButton: () => () => { }, hardware: opts.press.hardware });
92
+ const pad = mountControllerPad(padCell, {
93
+ press: padPress,
94
+ brand: opts.brand,
95
+ glyphSet: opts.glyphSet,
96
+ size: opts.size ?? "lg",
97
+ labels: opts.labels,
98
+ accent: opts.accent,
99
+ available: opts.available,
100
+ });
101
+ let callouts = [];
102
+ let warnedCap = false;
103
+ // ── routing ─────────────────────────────────────────────────────────────────────────────────────
104
+ let routeQueued = 0;
105
+ function queueRoute() {
106
+ if (routeQueued)
107
+ return;
108
+ routeQueued = requestAnimationFrame(() => {
109
+ routeQueued = 0;
110
+ route();
111
+ });
112
+ }
113
+ function route() {
114
+ const rootRect = root.getBoundingClientRect();
115
+ if (rootRect.width < 1 || rootRect.height < 1)
116
+ return; // not laid out yet (hidden host, etc.)
117
+ // The narrow-host media query stacks the columns and hides this layer; don't compute geometry for
118
+ // something nobody can see.
119
+ if (getComputedStyle(lines).display === "none")
120
+ return;
121
+ lines.setAttribute("viewBox", `0 0 ${rootRect.width} ${rootRect.height}`);
122
+ lines.setAttribute("width", String(rootRect.width));
123
+ lines.setAttribute("height", String(rootRect.height));
124
+ lines.textContent = "";
125
+ const parts = pad.svg().parts;
126
+ // Stagger the elbow per card WITHIN its column so parallel runs don't overlap into one thick line.
127
+ const seen = { left: 0, right: 0 };
128
+ for (const c of callouts) {
129
+ const partNode = c.anchorPart ? parts.get(c.anchorPart) : undefined;
130
+ if (!partNode)
131
+ continue; // this layout has no such part (a Zwift pod has no sticks)
132
+ const pr = partNode.getBoundingClientRect();
133
+ const cr = c.li.getBoundingClientRect();
134
+ if (pr.width < 1 || cr.width < 1)
135
+ continue;
136
+ const i = seen[c.side]++;
137
+ const leftward = c.side === "left";
138
+ const from = {
139
+ x: (leftward ? cr.right : cr.left) - rootRect.left,
140
+ y: cr.top - rootRect.top + cr.height / 2,
141
+ };
142
+ // Stop at the button's NEAR EDGE, not its centre, so the terminating dot marks the control
143
+ // without sitting on top of its letter.
144
+ const to = {
145
+ x: (leftward ? pr.left - 4 : pr.right + 4) - rootRect.left,
146
+ y: pr.top - rootRect.top + pr.height / 2,
147
+ };
148
+ // The elbow MUST land between the two endpoints. Clamping it against the TARGET alone (which is
149
+ // what this did) can put it behind `from`, and the polyline then juts backwards out of the card
150
+ // before doubling back on itself — a visibly broken line. Clamping to the span can't do that.
151
+ const lo = Math.min(from.x, to.x) + 2;
152
+ const hi = Math.max(from.x, to.x) - 2;
153
+ const spur = leftward ? from.x + (10 + i * 8) : from.x - (10 + i * 8);
154
+ const elbowX = hi <= lo ? (from.x + to.x) / 2 : Math.min(hi, Math.max(lo, spur));
155
+ // A card sitting level with its button needs a straight line, not a 4-point path with a
156
+ // zero-length riser — which is what produced the kinked stub at the join.
157
+ const level = Math.abs(from.y - to.y) < 2;
158
+ const p = (x, y) => `${x.toFixed(1)},${y.toFixed(1)}`;
159
+ const poly = document.createElementNS(NS, "polyline");
160
+ poly.setAttribute("class", "rydr-padmap-line" + (c.action.disabled ? " dim" : ""));
161
+ poly.setAttribute("points", level
162
+ ? `${p(from.x, from.y)} ${p(to.x, to.y)}`
163
+ : `${p(from.x, from.y)} ${p(elbowX, from.y)} ${p(elbowX, to.y)} ${p(to.x, to.y)}`);
164
+ lines.appendChild(poly);
165
+ // A DOT, not an arrowhead. See `.rydr-padmap-dot` in styles.ts for why.
166
+ const dot = document.createElementNS(NS, "circle");
167
+ dot.setAttribute("class", "rydr-padmap-dot" + (c.action.disabled ? " dim" : ""));
168
+ dot.setAttribute("cx", to.x.toFixed(1));
169
+ dot.setAttribute("cy", to.y.toFixed(1));
170
+ dot.setAttribute("r", "2.9");
171
+ lines.appendChild(dot);
172
+ }
173
+ }
174
+ // ── callouts ────────────────────────────────────────────────────────────────────────────────────
175
+ const cardObserver = new ResizeObserver(() => queueRoute());
176
+ function clearCallouts() {
177
+ cardObserver.disconnect();
178
+ for (const c of callouts) {
179
+ c.strip.dispose();
180
+ c.disposeCard();
181
+ c.li.remove();
182
+ }
183
+ callouts = [];
184
+ }
185
+ function buildCallouts(actions) {
186
+ clearCallouts();
187
+ const layout = pad.layout();
188
+ const prepared = actions.map((action) => {
189
+ const token = anchorTokenOf(action);
190
+ const anchorPart = token ? anchorPartOfToken(token) : undefined;
191
+ const side = action.side ?? (anchorPart ? sideOfPart(layout, anchorPart) : "left");
192
+ // Order down the column by the anchor's height on the pad, so lines run roughly parallel and
193
+ // don't cross. Anchorless actions sink to the bottom.
194
+ const order = (anchorPart ? partCentreOf(layout, anchorPart)?.y : undefined) ?? Number.MAX_SAFE_INTEGER;
195
+ return { action, anchorPart, side, order };
196
+ });
197
+ prepared.sort((a, b) => a.order - b.order);
198
+ for (const p of prepared) {
199
+ const li = document.createElement("li");
200
+ li.className = "rydr-padmap-item" + (p.action.disabled ? " disabled" : "");
201
+ const box = document.createElement("div");
202
+ box.className = "rydr-padmap-callout";
203
+ const label = document.createElement("div");
204
+ label.className = "rydr-padmap-label";
205
+ label.textContent = p.action.label;
206
+ box.appendChild(label);
207
+ if (p.action.hint) {
208
+ const hint = document.createElement("div");
209
+ hint.className = "rydr-padmap-hint";
210
+ hint.textContent = p.action.hint;
211
+ box.appendChild(hint);
212
+ }
213
+ const strip = createComboStrip(p.action, {
214
+ glyphSet: opts.glyphSet ?? pad.glyphSet(),
215
+ press: opts.live ? opts.press : undefined,
216
+ colored: opts.colored,
217
+ stickStyle: opts.stickStyle,
218
+ });
219
+ box.appendChild(strip.el);
220
+ const card = buildCard({ inline: true, content: box });
221
+ if (p.action.disabled)
222
+ card.setDisabled(true);
223
+ li.appendChild(card.root);
224
+ (p.side === "left" ? leftCol : rightCol).appendChild(li);
225
+ cardObserver.observe(li);
226
+ callouts.push({
227
+ action: p.action,
228
+ li,
229
+ strip,
230
+ disposeCard: card.dispose,
231
+ anchorPart: p.anchorPart,
232
+ side: p.side,
233
+ });
234
+ }
235
+ // Un-dim any control an action actually names. `SHELL_OWNED_PARTS` draws the bumpers / View / Guide
236
+ // faint because they are not a GAME's to use — but when a callout points at one it is the subject of
237
+ // the screen, and drawing the subject at 40% reads as "disabled". The shell's own resistance map is
238
+ // exactly that case.
239
+ const referenced = new Set();
240
+ for (const c of callouts) {
241
+ for (const token of c.action.combo) {
242
+ const part = anchorPartOfToken(token);
243
+ if (part)
244
+ referenced.add(part);
245
+ }
246
+ }
247
+ for (const [name, node] of pad.svg().parts)
248
+ node.classList.toggle("referenced", referenced.has(name));
249
+ const perSide = { left: 0, right: 0 };
250
+ for (const c of callouts)
251
+ perSide[c.side]++;
252
+ if (!warnedCap && (perSide.left > SOFT_CAP_PER_SIDE || perSide.right > SOFT_CAP_PER_SIDE)) {
253
+ warnedCap = true;
254
+ console.warn(`[rydr/ui] mountControllerMap: ${perSide.left} left / ${perSide.right} right callouts — the ` +
255
+ `layout is tuned for up to ${SOFT_CAP_PER_SIDE} per side. Nothing is dropped, but consider ` +
256
+ `splitting the list across screens or setting \`side\` to rebalance.`);
257
+ }
258
+ queueRoute();
259
+ }
260
+ buildCallouts(opts.actions);
261
+ const rootObserver = new ResizeObserver(() => queueRoute());
262
+ rootObserver.observe(root);
263
+ // A web font swapping in after first paint re-wraps every label, which moves every card.
264
+ void document.fonts?.ready.then(() => queueRoute()).catch(() => { });
265
+ // A pad swap rebuilds the drawing, so the cached part nodes we measure are new ones.
266
+ const offRebuild = pad.onRebuild(() => {
267
+ buildCallouts(callouts.map((c) => c.action));
268
+ });
269
+ return {
270
+ root,
271
+ pad,
272
+ setActions(actions) {
273
+ buildCallouts(actions);
274
+ },
275
+ setVisible(on) {
276
+ root.style.display = on ? "" : "none";
277
+ if (on)
278
+ queueRoute();
279
+ },
280
+ dispose() {
281
+ if (routeQueued)
282
+ cancelAnimationFrame(routeQueued);
283
+ rootObserver.disconnect();
284
+ offRebuild();
285
+ clearCallouts();
286
+ pad.dispose();
287
+ root.remove();
288
+ },
289
+ };
290
+ }
291
+ //# sourceMappingURL=controller-map.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller-map.js","sourceRoot":"","sources":["../../../src/ui/controller/controller-map.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * CONTROLLER PAD — the live drawing of the controller in the rider's hands.
3
+ *
4
+ * ```ts
5
+ * const pad = mountControllerPad(host, { press: session, size: "lg" });
6
+ * // …buttons light as they're pressed, stick caps follow the real axes. Nothing to call per frame.
7
+ * pad.dispose();
8
+ * ```
9
+ *
10
+ * This is the game-side counterpart of the shell's own controller diagram, and it is deliberately fed by
11
+ * **canonical input, not raw hardware**: a game never sees gamepad indices, per-device bindings or analog
12
+ * trigger travel, so this module translates `ButtonName` → drawn position (`parts-map.ts`) and polls
13
+ * `session.axis()` for the sticks. Three consequences worth knowing:
14
+ *
15
+ * 1. **Axes are polled, buttons are not.** `onButton` is an edge stream, so presses cost nothing between
16
+ * events; axes have no event, so when the source exposes `axis()` the pad runs its own
17
+ * `requestAnimationFrame` loop. It stops on `dispose()` and while the tab is hidden, and the
18
+ * `PadDriver` dirty-guard means an idle stick performs zero DOM writes per frame.
19
+ *
20
+ * 2. **The d-pad is shared with the left stick.** `UP`/`DOWN`/`LEFT`/`RIGHT` are emitted by the d-pad
21
+ * *and* by the left stick past its deadzone (see `protocol/buttons.ts`), and nothing in the canonical
22
+ * stream says which. When `axis()` is available we suppress the d-pad highlight for a direction the
23
+ * stick already explains — so pushing the stick moves the cap instead of falsely lighting the d-pad.
24
+ * Without `axis()` the d-pad lights for both, which is the honest best we can do.
25
+ *
26
+ * 3. **Triggers are clicks.** `LT`/`RT` have no analog travel in this protocol, so their travel meters
27
+ * are driven 0/1 from button state.
28
+ *
29
+ * It also **re-letters itself.** A keycap resolves its glyph set once, on the reasoning that keycaps are
30
+ * cheap to recreate; a controls screen is long-lived and `/hardware` is exactly where a rider swaps pads.
31
+ * So if the source exposes `hardware.subscribe`, the pad rebuilds its drawing in place when the resolved
32
+ * set changes, restoring anything currently held. Callers keep their handle and need to know nothing.
33
+ */
34
+ import type { AxisName, ButtonEdge, ButtonName } from "../../protocol/buttons.js";
35
+ import { type GlyphSet } from "../../protocol/glyphs.js";
36
+ import { type PadDriver, type PadSvg } from "./pad-svg.js";
37
+ import type { ControlName } from "./tokens.js";
38
+ import type { PadBrandKey, PadLayout } from "./types.js";
39
+ /**
40
+ * The slice of a `PlatformSession` the controller views consume. A session satisfies it structurally —
41
+ * just pass `session`. Everything but `onButton` is optional so a hand-rolled stub or a showcase double
42
+ * works too; the pad simply does less with less.
43
+ */
44
+ export interface PadInputSource {
45
+ /**
46
+ * Typed on {@link ControlName} — a superset of `ButtonName` that also covers the shell-owned controls.
47
+ *
48
+ * A `PlatformSession` only ever emits the `ButtonName` subset and still satisfies this, while a
49
+ * SHELL-side source can hand the same object to both this and `mountControlTrainer`. They used to
50
+ * take different shapes, which meant a caller with one source could feed the lesson but not the map.
51
+ */
52
+ onButton(cb: (e: {
53
+ name: ControlName;
54
+ edge: ButtonEdge;
55
+ }) => void): () => void;
56
+ /** Analog stick positions, −1..1 (right = +1, up = +1). Present → the sticks and cap glow come alive. */
57
+ axis?(name: AxisName): number;
58
+ /** Currently-held buttons, for showing the right state immediately on mount instead of on next press. */
59
+ buttonsDown?(): ButtonName[];
60
+ /** The hardware store — read for the rider's glyph set, subscribed to for pad swaps. */
61
+ hardware?: {
62
+ current: {
63
+ glyphSet?: GlyphSet;
64
+ };
65
+ subscribe?(cb: (snapshot: {
66
+ glyphSet?: GlyphSet;
67
+ }) => void): () => void;
68
+ };
69
+ }
70
+ export interface ControllerPadOptions {
71
+ /** Wire to real input. Omit for static art. */
72
+ press?: PadInputSource;
73
+ /** Force a silhouette. Default: derived from the glyph set (`"joycon"` is only reachable this way). */
74
+ brand?: PadBrandKey;
75
+ /**
76
+ * Force the lettering. Default: the set reported by `press`, else `"xbox"`. Setting this explicitly
77
+ * also PINS it — the pad stops following pad swaps, which is what a settings preview wants.
78
+ */
79
+ glyphSet?: GlyphSet;
80
+ /** Max width preset. Default `"md"` (340px). */
81
+ size?: "sm" | "md" | "lg";
82
+ /** Draw the face/trigger/bumper letters on the art. Default true. */
83
+ labels?: boolean;
84
+ /** Dim the whole drawing and skip the input subscription — a placeholder or a backdrop. */
85
+ ghost?: boolean;
86
+ /** Accent colour for pressed parts. Themed through this option, never by overriding the CSS. */
87
+ accent?: string;
88
+ /**
89
+ * Buttons the rider's device actually has; everything else is drawn faint.
90
+ *
91
+ * Opt-in because the protocol cannot tell us: `HardwareSnapshot` reports `controllerConnected` and
92
+ * `glyphSet` but not a button inventory, while `protocol/buttons.ts` warns that a phone exposes only
93
+ * the `DIAMOND_DOWN`/`DIAMOND_RIGHT` pair. Pass it when your game knows better.
94
+ */
95
+ available?: readonly ButtonName[];
96
+ /**
97
+ * Let the drawing light the shell-owned controls (the resistance bumpers, View, Guide).
98
+ *
99
+ * Off by default and normally correct that way: a game never receives them, so lighting them would
100
+ * tell the rider something untrue. The legitimate caller is a lesson that TEACHES those controls —
101
+ * `mountControlTrainer` sets it when a step names one.
102
+ */
103
+ allowShellOwned?: boolean;
104
+ }
105
+ export interface ControllerPad {
106
+ readonly root: HTMLDivElement;
107
+ /** The current drawing's node cache. Replaced on a re-letter — don't hold onto it across one. */
108
+ svg(): PadSvg;
109
+ driver(): PadDriver;
110
+ layout(): PadLayout;
111
+ /**
112
+ * The lettering currently drawn. Read it rather than re-deriving: anything built alongside the pad
113
+ * (the map's chips) must use the SAME set, or a pad swap leaves the two disagreeing.
114
+ */
115
+ glyphSet(): GlyphSet;
116
+ /** Fires after the drawing is rebuilt (a pad swap). The map view uses it to re-route its lines. */
117
+ onRebuild(cb: () => void): () => void;
118
+ setVisible(on: boolean): void;
119
+ dispose(): void;
120
+ }
121
+ export declare function mountControllerPad(host: HTMLElement, opts?: ControllerPadOptions): ControllerPad;
122
+ //# sourceMappingURL=controller-pad.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller-pad.d.ts","sourceRoot":"","sources":["../../../src/ui/controller/controller-pad.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAqB,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAG5E,OAAO,EAAoD,KAAK,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAE7G,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAW,MAAM,YAAY,CAAC;AAElE;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC/E,yGAAyG;IACzG,IAAI,CAAC,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC9B,yGAAyG;IACzG,WAAW,CAAC,IAAI,UAAU,EAAE,CAAC;IAC7B,wFAAwF;IACxF,QAAQ,CAAC,EAAE;QACT,OAAO,EAAE;YAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;SAAE,CAAC;QACjC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,EAAE;YAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;SAAE,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;KACzE,CAAC;CACH;AAED,MAAM,WAAW,oBAAoB;IACnC,+CAA+C;IAC/C,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,uGAAuG;IACvG,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,gDAAgD;IAChD,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,qEAAqE;IACrE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2FAA2F;IAC3F,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,gGAAgG;IAChG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAClC;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,iGAAiG;IACjG,GAAG,IAAI,MAAM,CAAC;IACd,MAAM,IAAI,SAAS,CAAC;IACpB,MAAM,IAAI,SAAS,CAAC;IACpB;;;OAGG;IACH,QAAQ,IAAI,QAAQ,CAAC;IACrB,mGAAmG;IACnG,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IACtC,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9B,OAAO,IAAI,IAAI,CAAC;CACjB;AAUD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,GAAE,oBAAyB,GAAG,aAAa,CAuJpG"}