@rydr/game-sdk 4.0.0 → 6.0.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.
- package/README.md +36 -23
- package/dist/client/HardwareStore.d.ts +10 -0
- package/dist/client/HardwareStore.d.ts.map +1 -1
- package/dist/client/HardwareStore.js +10 -0
- package/dist/client/HardwareStore.js.map +1 -1
- package/dist/client/PlatformClient.d.ts +48 -13
- package/dist/client/PlatformClient.d.ts.map +1 -1
- package/dist/client/PlatformClient.js +44 -13
- package/dist/client/PlatformClient.js.map +1 -1
- package/dist/host/PlatformHost.d.ts +35 -12
- package/dist/host/PlatformHost.d.ts.map +1 -1
- package/dist/host/PlatformHost.js +41 -4
- package/dist/host/PlatformHost.js.map +1 -1
- package/dist/nav/README.md +5 -5
- package/dist/nav/scroll-nav.d.ts.map +1 -1
- package/dist/nav/scroll-nav.js +8 -4
- package/dist/nav/scroll-nav.js.map +1 -1
- package/dist/nav/spatial-nav.d.ts.map +1 -1
- package/dist/nav/spatial-nav.js +8 -4
- package/dist/nav/spatial-nav.js.map +1 -1
- package/dist/protocol/buttons.d.ts +96 -33
- package/dist/protocol/buttons.d.ts.map +1 -1
- package/dist/protocol/buttons.js +58 -21
- package/dist/protocol/buttons.js.map +1 -1
- package/dist/protocol/capabilities.d.ts +1 -1
- package/dist/protocol/glyphs.d.ts +47 -0
- package/dist/protocol/glyphs.d.ts.map +1 -0
- package/dist/protocol/glyphs.js +75 -0
- package/dist/protocol/glyphs.js.map +1 -0
- package/dist/protocol/index.d.ts +1 -0
- package/dist/protocol/index.d.ts.map +1 -1
- package/dist/protocol/index.js +1 -0
- package/dist/protocol/index.js.map +1 -1
- package/dist/protocol/messages.d.ts +24 -9
- package/dist/protocol/messages.d.ts.map +1 -1
- package/dist/protocol/perf.d.ts +37 -0
- package/dist/protocol/perf.d.ts.map +1 -0
- package/dist/protocol/perf.js +17 -0
- package/dist/protocol/perf.js.map +1 -0
- package/dist/protocol/version.d.ts +2 -2
- package/dist/protocol/version.d.ts.map +1 -1
- package/dist/protocol/version.js +48 -2
- package/dist/protocol/version.js.map +1 -1
- package/dist/three/README.md +1 -1
- package/dist/three/perf-marks.d.ts +2 -21
- package/dist/three/perf-marks.d.ts.map +1 -1
- package/dist/three/perf-marks.js.map +1 -1
- package/dist/ui/README.md +9 -6
- package/dist/ui/action-card.d.ts +4 -4
- package/dist/ui/action-card.d.ts.map +1 -1
- package/dist/ui/action-card.js.map +1 -1
- package/dist/ui/action-diamond.d.ts +3 -3
- package/dist/ui/action-diamond.d.ts.map +1 -1
- package/dist/ui/action-diamond.js +4 -2
- package/dist/ui/action-diamond.js.map +1 -1
- package/dist/ui/choice-card.d.ts +3 -3
- package/dist/ui/choice-card.d.ts.map +1 -1
- package/dist/ui/choice-card.js +1 -1
- package/dist/ui/choice-card.js.map +1 -1
- package/dist/ui/dialogue-card.d.ts +3 -3
- package/dist/ui/dialogue-card.d.ts.map +1 -1
- package/dist/ui/dialogue-card.js +1 -1
- package/dist/ui/dialogue-card.js.map +1 -1
- package/dist/ui/heat-vignette.d.ts +75 -0
- package/dist/ui/heat-vignette.d.ts.map +1 -0
- package/dist/ui/heat-vignette.js +126 -0
- package/dist/ui/heat-vignette.js.map +1 -0
- package/dist/ui/index.d.ts +7 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +7 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/keycap.d.ts +19 -5
- package/dist/ui/keycap.d.ts.map +1 -1
- package/dist/ui/keycap.js +63 -9
- package/dist/ui/keycap.js.map +1 -1
- package/dist/ui/labeled-diamond.d.ts +4 -4
- package/dist/ui/labeled-diamond.d.ts.map +1 -1
- package/dist/ui/labeled-diamond.js +7 -4
- package/dist/ui/labeled-diamond.js.map +1 -1
- package/dist/ui/overheat-glitch.d.ts +56 -0
- package/dist/ui/overheat-glitch.d.ts.map +1 -0
- package/dist/ui/overheat-glitch.js +177 -0
- package/dist/ui/overheat-glitch.js.map +1 -0
- package/dist/ui/overheat-hud-bar.d.ts +79 -0
- package/dist/ui/overheat-hud-bar.d.ts.map +1 -0
- package/dist/ui/overheat-hud-bar.js +302 -0
- package/dist/ui/overheat-hud-bar.js.map +1 -0
- package/dist/ui/showcase/index.d.ts.map +1 -1
- package/dist/ui/showcase/index.js +53 -27
- package/dist/ui/showcase/index.js.map +1 -1
- package/dist/ui/solo-labeled-diamond.d.ts +5 -5
- package/dist/ui/solo-labeled-diamond.d.ts.map +1 -1
- package/dist/ui/solo-labeled-diamond.js +4 -2
- package/dist/ui/solo-labeled-diamond.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -114,7 +114,7 @@ const session = await connectToPlatform({ gameId: "racing" });
|
|
|
114
114
|
|
|
115
115
|
session.hardware.subscribe((hw) => render(hw.power, hw.heartRate));
|
|
116
116
|
session.onButton(({ name, edge }) => handleInput(name, edge));
|
|
117
|
-
session.
|
|
117
|
+
session.setActivity("playing"); // full resistance while actually being ridden
|
|
118
118
|
session.ready();
|
|
119
119
|
```
|
|
120
120
|
|
|
@@ -130,7 +130,11 @@ exists but defaults to ALL; you don't set it.)
|
|
|
130
130
|
- `grantedCapabilities: readonly Capability[]` · `initialPath: string | undefined`.
|
|
131
131
|
- `hardware: HardwareStore` — `current: HardwareSnapshot`, and `subscribe(cb) => () => void` (fires immediately, then on every change).
|
|
132
132
|
- `ready()` · `reportLoadProgress(0..100)` · `reportError(message)`.
|
|
133
|
-
-
|
|
133
|
+
- ~~`setSimulation(gradePercent)` · `setTargetPower(watts)` · `setErgMode(enabled)`~~ — **inert on the
|
|
134
|
+
RYDR shell.** Trainer feel is rider-owned: the resistance level is chosen with the shift paddles and
|
|
135
|
+
persisted per trainer, so the shell deliberately no-ops these. They remain on the wire for
|
|
136
|
+
non-RYDR hosts. To influence resistance, use `setActivity()` below (the shell eases in menus) — the
|
|
137
|
+
game never sets a level.
|
|
134
138
|
- `setRoute(path)` · `createRouter({ onRoute })` · `setPowerBar(visible)` · `requestExit()` · `requestHardwareModal()`.
|
|
135
139
|
- `setPowerBar`: default visible — leave it during gameplay **and** menus so a keyboard rider can always feed power. The one exception is an editor/authoring tool, which has nothing to pedal and calls `setPowerBar(false)` (see [Build an in-game editor](#build-an-in-game-editor)).
|
|
136
140
|
|
|
@@ -187,10 +191,14 @@ session.onButton(({ name, edge }) => { if (edge === "down") confirm(name); });
|
|
|
187
191
|
const dx = (session.isDown("RIGHT") ? 1 : 0) - (session.isDown("LEFT") ? 1 : 0);
|
|
188
192
|
const anyHeld = session.buttonsDown().length > 0;
|
|
189
193
|
|
|
190
|
-
// Analog (hall-effect
|
|
191
|
-
const throttle = session.axis("RT"); // 0..1 (0 on a plain, non-hall trigger until pressed)
|
|
194
|
+
// Analog (hall-effect joysticks) — poll the value in your game loop:
|
|
192
195
|
const move = session.stick("LSTICK"); // { x, y, magnitude, angle }, radially deadzoned
|
|
193
196
|
player.x += move.x * speed; // smooth on hall sticks, clean 8-way on a d-pad
|
|
197
|
+
const aim = session.stick("RSTICK"); // the SECOND stick: aim / camera (RX/RY)
|
|
198
|
+
|
|
199
|
+
// The right stick is also readable digitally, and clicking a stick is a plain button:
|
|
200
|
+
const aimX = (session.isDown("RRIGHT") ? 1 : 0) - (session.isDown("RLEFT") ? 1 : 0);
|
|
201
|
+
session.onButton(({ name, edge }) => { if (name === "RSTICK_PRESS" && edge === "down") recentreCamera(); });
|
|
194
202
|
```
|
|
195
203
|
|
|
196
204
|
- **Do NOT** add `window.addEventListener("keydown"/"keyup")`, read `e.key`/`e.code`/`keyCode`,
|
|
@@ -201,24 +209,29 @@ player.x += move.x * speed; // smooth on hall sticks, clean
|
|
|
201
209
|
delivered per-frame and per-focus. `onButton`/`isDown`/`buttonsDown` are immune to all of that:
|
|
202
210
|
the SDK normalises **keyboard, phone, and Zwift Play** into one stream and delivers it whether
|
|
203
211
|
the shell or the game canvas holds focus.
|
|
204
|
-
- **Canonical names:** `UP`/`DOWN`/`LEFT`/`RIGHT`
|
|
205
|
-
|
|
206
|
-
`
|
|
207
|
-
|
|
208
|
-
|
|
212
|
+
- **Canonical names:** `UP`/`DOWN`/`LEFT`/`RIGHT` (D-pad **and** left stick) + right-stick directions
|
|
213
|
+
`RUP`/`RDOWN`/`RLEFT`/`RRIGHT` + face diamond `DIAMOND_UP`/`DIAMOND_DOWN`/`DIAMOND_LEFT`/`DIAMOND_RIGHT` + shoulder triggers `LT`/`RT` + joystick
|
|
214
|
+
clicks `LSTICK_PRESS`/`RSTICK_PRESS` + `OPTIONS` (the game's own menu/options button — distinct from
|
|
215
|
+
the platform's own overlay menu, which never reaches a game). House convention: `DIAMOND_DOWN` = confirm/primary, `DIAMOND_RIGHT` = back/cancel (as on Xbox/PlayStation/Nintendo). Never print a letter — resolve it per pad with `session.buttonLabel(name)`; every
|
|
216
|
+
other name is contextual (no built-in meaning). Show the actual glyph in prompts; never hard-code a
|
|
217
|
+
device. (Phones expose at least the `DIAMOND_DOWN`/`DIAMOND_RIGHT` pair, and most controllers have no `LT`/`RT`, no second
|
|
218
|
+
stick, no stick click, and no `OPTIONS` — don't require them for a flow that must work everywhere.)
|
|
209
219
|
- **Edges are real holds:** a held button emits exactly one `down` and one `up`. For "is it held
|
|
210
220
|
right now?" use `isDown`/`buttonsDown` rather than tracking edges yourself.
|
|
211
|
-
- **Analog is opt-in and additive:** when a controller has **hall-effect**
|
|
212
|
-
continuous position with `session.axis(name)` —
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
`stick(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
221
|
+
- **Analog is opt-in and additive:** when a controller has **hall-effect** joysticks, read the
|
|
222
|
+
continuous position with `session.axis(name)` — the stick axes `LX`/`LY`/`RX`/`RY` give `-1..1`
|
|
223
|
+
(right/up = +1), and they are the **only** axes: the shoulder triggers `LT`/`RT` are plain clicks
|
|
224
|
+
(no analog travel), read via `isDown`/`onButton` only. For a joystick prefer
|
|
225
|
+
`session.stick("LSTICK" | "RSTICK")`, which applies the correct **radial** deadzone and returns
|
|
226
|
+
`{ x, y, magnitude, angle }`. Pick by need: `stick()` for 2D movement/aim, `isDown`/`onButton` for
|
|
227
|
+
ON/OFF. The digital and analog streams run in **parallel** — `isDown("RLEFT")` and
|
|
228
|
+
`stick("RSTICK")` both work, so a game uses either or both. And `axis()`/`stick()` are **always
|
|
229
|
+
readable**: on a plain, non-hall controller the value is quantized to the endpoints (a d-pad snaps
|
|
219
230
|
`-1`/`0`/`+1`), so you never branch on "does this controller have hall?" — it rests at `0` until a
|
|
220
|
-
sample arrives. The **keyboard emulates axes too** (for local dev): the
|
|
221
|
-
and the
|
|
231
|
+
sample arrives. The **keyboard emulates axes too** (for local dev): the arrow keys drive the left
|
|
232
|
+
stick and the numpad 8/4/5/6 keys drive the right stick, so both `stick("LSTICK")` / `stick("RSTICK")`
|
|
233
|
+
work without a controller. The right stick is also readable digitally
|
|
234
|
+
(`RUP`/`RDOWN`/`RLEFT`/`RRIGHT`) and each stick's click is a plain button (numpad `1`/`3` on the keyboard).
|
|
222
235
|
- **The only legitimate exception is a non-gameplay authoring tool** — an admin-gated **world /
|
|
223
236
|
level editor** or a dev-only showcase page that runs outside the rider flow. There, raw keyboard
|
|
224
237
|
(Ctrl+Z, arrows to nudge, Delete) is the correct desktop UX. *Gameplay* never uses it.
|
|
@@ -486,16 +499,16 @@ A self-contained DOM/CSS kit for the **controller-button UI** — showing "which
|
|
|
486
499
|
import { createKeycap, mountActionDiamond, mountDialogueCard } from "@rydr/game-sdk/ui";
|
|
487
500
|
|
|
488
501
|
// a single A keycap that reflects real presses — pass the session (it satisfies `ButtonSource`)
|
|
489
|
-
host.appendChild(createKeycap("
|
|
502
|
+
host.appendChild(createKeycap("DIAMOND_DOWN", { press: session }).el);
|
|
490
503
|
|
|
491
|
-
// the full
|
|
504
|
+
// the full face-diamond move diamond, wired to input
|
|
492
505
|
const dia = mountActionDiamond(host, { press: session, cards: { A: { label: "Séisme" } } });
|
|
493
506
|
dia.cards.A.setActive(true);
|
|
494
507
|
dia.cards.Z.keycap.setCooldown(0.4, 3); // depleting ring + countdown
|
|
495
508
|
```
|
|
496
509
|
|
|
497
|
-
- **`createKeycap(button, opts)`** / **`createDpadKeycap(dir, opts)`** — a keycap of the
|
|
498
|
-
- **`mountCard`** — the dark glass container with a content slot. **`mountActionCard`** — that card + a keycap straddling its bottom edge. **`mountActionDiamond`** — the full
|
|
510
|
+
- **`createKeycap(button, opts)`** / **`createDpadKeycap(dir, opts)`** — a keycap of the face-diamond face buttons or the UP/DOWN/LEFT/RIGHT d-pad. A small state machine: `solo` vs `full` diamond, pulse, pressed, cooldown ring + countdown, colored vs mono, disabled, hidden.
|
|
511
|
+
- **`mountCard`** — the dark glass container with a content slot. **`mountActionCard`** — that card + a keycap straddling its bottom edge. **`mountActionDiamond`** — the full face-diamond move diamond.
|
|
499
512
|
- **`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.
|
|
500
513
|
|
|
501
514
|
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.
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Buttons are events (see `PlatformSession.onButton`), not state, so they live
|
|
7
7
|
* on the client rather than here.
|
|
8
8
|
*/
|
|
9
|
+
import { type GlyphSet } from "../protocol/glyphs.js";
|
|
9
10
|
export interface HardwareSnapshot {
|
|
10
11
|
/**
|
|
11
12
|
* Raw trainer power, watts — the exact last reading, updated only when a power message
|
|
@@ -45,6 +46,15 @@ export interface HardwareSnapshot {
|
|
|
45
46
|
* setup; button events themselves stay source-agnostic. Requires the `buttons` capability.
|
|
46
47
|
*/
|
|
47
48
|
controllerConnected: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Which lettering the paired controller carries, for rendering button prompts. Prefer
|
|
51
|
+
* `session.buttonLabel(name)` over reading this directly; it's exposed here so a UI that already
|
|
52
|
+
* subscribes to hardware re-renders its prompts when the rider swaps pads mid-session.
|
|
53
|
+
*
|
|
54
|
+
* A *rendering* hint, never a device identity — several brands share a set, and gameplay must not
|
|
55
|
+
* branch on it. Defaults to {@link DEFAULT_GLYPH_SET} until the shell says otherwise.
|
|
56
|
+
*/
|
|
57
|
+
glyphSet: GlyphSet;
|
|
48
58
|
/** ms timestamp of the most recent update (0 before any data). */
|
|
49
59
|
updatedAt: number;
|
|
50
60
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HardwareStore.d.ts","sourceRoot":"","sources":["../../src/client/HardwareStore.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"HardwareStore.d.ts","sourceRoot":"","sources":["../../src/client/HardwareStore.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAqB,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEzE,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;;;;OAYG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,gBAAgB,EAAE,OAAO,CAAC;IAC1B,yEAAyE;IACzE,YAAY,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,mBAAmB,EAAE,OAAO,CAAC;IAC7B;;;;;;;OAOG;IACH,QAAQ,EAAE,QAAQ,CAAC;IACnB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;CACnB;AAeD,MAAM,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAMxC,6EAA6E;AAC7E,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IAEzD,uFAAuF;IACvF,OAAO,CAAC,IAAI,CAAS;IACrB,kFAAkF;IAClF,OAAO,CAAC,UAAU,CAAK;gBAEX,UAAU,GAAE,MAA4B;IAIpD;iFAC6E;IAC7E,IAAI,OAAO,IAAI,gBAAgB,CAG9B;IAED,oGAAoG;IACpG,SAAS,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI;IAQjD;;;;OAIG;IACH,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAI1C;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI;IAO9C;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;CAazB"}
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive snapshot of bridged hardware state for a game.
|
|
3
|
+
*
|
|
4
|
+
* A game never owns hardware; it observes this store, which the
|
|
5
|
+
* {@link PlatformClient} keeps current from the platform's `hw.*` messages.
|
|
6
|
+
* Buttons are events (see `PlatformSession.onButton`), not state, so they live
|
|
7
|
+
* on the client rather than here.
|
|
8
|
+
*/
|
|
9
|
+
import { DEFAULT_GLYPH_SET } from "../protocol/glyphs.js";
|
|
1
10
|
const EMPTY = {
|
|
2
11
|
power: 0,
|
|
3
12
|
smoothedPower: 0,
|
|
@@ -7,6 +16,7 @@ const EMPTY = {
|
|
|
7
16
|
trainerConnected: false,
|
|
8
17
|
ergSupported: false,
|
|
9
18
|
controllerConnected: false,
|
|
19
|
+
glyphSet: DEFAULT_GLYPH_SET,
|
|
10
20
|
updatedAt: 0,
|
|
11
21
|
};
|
|
12
22
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HardwareStore.js","sourceRoot":"","sources":["../../src/client/HardwareStore.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HardwareStore.js","sourceRoot":"","sources":["../../src/client/HardwareStore.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,iBAAiB,EAAiB,MAAM,uBAAuB,CAAC;AAsDzE,MAAM,KAAK,GAAqB;IAC9B,KAAK,EAAE,CAAC;IACR,aAAa,EAAE,CAAC;IAChB,OAAO,EAAE,CAAC;IACV,SAAS,EAAE,CAAC;IACZ,KAAK,EAAE,CAAC;IACR,gBAAgB,EAAE,KAAK;IACvB,YAAY,EAAE,KAAK;IACnB,mBAAmB,EAAE,KAAK;IAC1B,QAAQ,EAAE,iBAAiB;IAC3B,SAAS,EAAE,CAAC;CACb,CAAC;AAIF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAExC;gFACgF;AAChF,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAEjC,6EAA6E;AAC7E,MAAM,OAAO,aAAa;IAChB,QAAQ,GAAqB,KAAK,CAAC;IAC1B,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;IAEzD,uFAAuF;IAC/E,IAAI,CAAS;IACrB,kFAAkF;IAC1E,UAAU,GAAG,CAAC,CAAC;IAEvB,YAAY,aAAqB,mBAAmB;QAClD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IACtC,CAAC;IAED;iFAC6E;IAC7E,IAAI,OAAO;QACT,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,oGAAoG;IACpG,SAAS,CAAC,QAA0B;QAClC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxB,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,gBAAgB,CAAC,UAAkB;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IACtC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,KAAgC;QACrC,IAAI,CAAC,QAAQ,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,KAAK,EAAE,CAAC;QAC/C,6FAA6F;QAC7F,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACxB,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS;YAAE,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjE,CAAC;IAED;;;;;OAKG;IACK,gBAAgB;QACtB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,IAAI,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,QAAQ,CAAC,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YAClD,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC;YACtB,OAAO;QACT,CAAC;QACD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;QAC1E,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC;QACtB,IAAI,EAAE,IAAI,CAAC;YAAE,OAAO;QACpB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5D,IAAI,CAAC,QAAQ,CAAC,aAAa,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACzF,CAAC;CACF"}
|
|
@@ -7,6 +7,7 @@ import type { GameDoc } from "../protocol/gamedata.js";
|
|
|
7
7
|
import type { CoreWorld } from "../protocol/worlds.js";
|
|
8
8
|
import type { CatalogCharacter, CharacterRig } from "../protocol/characters.js";
|
|
9
9
|
import type { ReplayBody, ReplayFrame, ReplayMeta, ReplayRef } from "../protocol/replays.js";
|
|
10
|
+
import "../protocol/perf.js";
|
|
10
11
|
import { HardwareStore } from "./HardwareStore.js";
|
|
11
12
|
import { type RoomHandle } from "./Room.js";
|
|
12
13
|
/** A canonical controller button event delivered to the game. */
|
|
@@ -419,14 +420,24 @@ export interface PlatformSession {
|
|
|
419
420
|
/**
|
|
420
421
|
* Report whether the rider is in **active gameplay** or navigating a **menu** (picker,
|
|
421
422
|
* results, leaderboard, pause screen, etc.). The shell uses this to ease trainer resistance
|
|
422
|
-
* whenever the rider isn't racing — so you keep spinning comfortably while browsing — and
|
|
423
|
+
* (~35%) whenever the rider isn't racing — so you keep spinning comfortably while browsing — and
|
|
423
424
|
* restores full resistance the instant you go back to `"playing"`. The game never sets a
|
|
424
425
|
* resistance value; it only declares state.
|
|
425
426
|
*
|
|
426
|
-
* - `"
|
|
427
|
-
*
|
|
427
|
+
* - `"menu"` — any non-racing screen; the shell eases resistance. **This is the default** — a
|
|
428
|
+
* freshly-launched game is treated as being in its own menus until it says otherwise.
|
|
429
|
+
* - `"playing"` — active gameplay; the shell holds FULL rider resistance.
|
|
428
430
|
*
|
|
429
|
-
*
|
|
431
|
+
* **The one rule: call `setActivity("playing")` when your gameplay loop is live, and `"menu"`
|
|
432
|
+
* on every other screen — including your title/menu screen at boot.** If you never call it, the
|
|
433
|
+
* rider stays eased (~35% lighter) the whole time — safe, but your race won't feel full. You
|
|
434
|
+
* only ever declare menu↔play; the shell owns all the *cleanup* — opening the platform menu,
|
|
435
|
+
* pausing, exiting, or a crash all reset you to the eased rest state automatically, so you can
|
|
436
|
+
* never leave the trainer stuck at full resistance.
|
|
437
|
+
*
|
|
438
|
+
* A DOM-menu game with a screen router should map every screen → state and call this on each
|
|
439
|
+
* transition (see the guitar-hero `ACTIVITY_STATE` map). A game with a phase enum should call
|
|
440
|
+
* `"playing"` on the play phase and `"menu"` on results/pause/etc.
|
|
430
441
|
*/
|
|
431
442
|
setActivity(state: "playing" | "menu"): void;
|
|
432
443
|
/** Ask the shell to exit the game (back to the launcher). */
|
|
@@ -444,11 +455,16 @@ export interface PlatformSession {
|
|
|
444
455
|
* acceptable ONLY in non-gameplay authoring tools). Then pick by what you're reading:
|
|
445
456
|
* - **Discrete press** (menu confirm, fire, jump) → `onButton` — react to the `down`/`up` edge.
|
|
446
457
|
* - **Held button** (hold-to-brake, digital steer) → `isDown(name)` / `buttonsDown()` — poll each frame.
|
|
447
|
-
*
|
|
458
|
+
* This includes the shoulder triggers: `LT`/`RT` are plain clicks (no analog travel, no axis).
|
|
448
459
|
* - **Joystick** (2D move/aim) → `stick("LSTICK"|"RSTICK")` — deadzoned `{x, y, magnitude, angle}`.
|
|
460
|
+
* - **Second stick, digitally** (8-way aim) → `RUP`/`RDOWN`/`RLEFT`/`RRIGHT` via `onButton`/`isDown` —
|
|
461
|
+
* the right stick's own edges, so they never collide with the D-pad's `UP`/`DOWN`/`LEFT`/`RIGHT`
|
|
462
|
+
* (which the left stick shares).
|
|
463
|
+
* - **Stick click** (crouch, recentre camera) → `LSTICK_PRESS`/`RSTICK_PRESS` via `onButton`/`isDown` —
|
|
464
|
+
* an ordinary button edge, not an axis. Rare hardware: always leave a second way to do it.
|
|
449
465
|
*
|
|
450
466
|
* The digital (`onButton`/`isDown`) and analog (`axis`/`stick`) streams run in parallel — the same
|
|
451
|
-
*
|
|
467
|
+
* stick is readable both ways (`isDown("RLEFT")` / `stick("RSTICK")`), so use whichever fits, or both.
|
|
452
468
|
* The SDK normalises keyboard, phone, and Zwift Play into these streams and delivers them whether
|
|
453
469
|
* the shell or the game canvas holds focus (raw DOM key events silently break across iframe focus).
|
|
454
470
|
*
|
|
@@ -466,15 +482,16 @@ export interface PlatformSession {
|
|
|
466
482
|
/** Snapshot of every button currently held. */
|
|
467
483
|
buttonsDown(): ButtonName[];
|
|
468
484
|
/**
|
|
469
|
-
* Current analog value of a controller axis — a hall-effect
|
|
470
|
-
* `
|
|
471
|
-
*
|
|
485
|
+
* Current analog value of a controller axis — a hall-effect joystick position. The stick axes
|
|
486
|
+
* `LX`/`LY`/`RX`/`RY` read `-1..1` (right/up = +1, centre 0) and are the ONLY axes — the shoulder
|
|
487
|
+
* triggers are plain clicks, read via `isDown("LT"|"RT")`. Raw (not deadzoned) — prefer `stick()`,
|
|
488
|
+
* which deadzones for you.
|
|
472
489
|
*
|
|
473
490
|
* Always readable: on a plain (non-hall) controller the value is quantized to the endpoints
|
|
474
|
-
* (
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
491
|
+
* (`-1`/`0`/`+1`) rather than continuous, so you never need to check whether the hardware has hall
|
|
492
|
+
* sensors. The digital `input.button` direction edges keep firing in parallel. Poll this in your
|
|
493
|
+
* game loop. `0` until the first sample (also the resting value), so a game never receiving axes
|
|
494
|
+
* simply reads `0`.
|
|
478
495
|
*/
|
|
479
496
|
axis(name: AxisName): number;
|
|
480
497
|
/**
|
|
@@ -483,8 +500,26 @@ export interface PlatformSession {
|
|
|
483
500
|
* the stick's two axes (`"LSTICK"` → `LX`/`LY`, `"RSTICK"` → `RX`/`RY`) into `{ x, y, magnitude,
|
|
484
501
|
* angle }`. Because axes are always populated, this returns a smooth vector on hall hardware and a
|
|
485
502
|
* clean 8-way vector on a d-pad — one code path. Default deadzone `0.1`; override via `opts`.
|
|
503
|
+
*
|
|
504
|
+
* Clicking the stick is a separate button edge (`LSTICK_PRESS`/`RSTICK_PRESS`), not part of this vector,
|
|
505
|
+
* and the right stick's deflection also arrives digitally as `RUP`/`RDOWN`/`RLEFT`/`RRIGHT`.
|
|
486
506
|
*/
|
|
487
507
|
stick(name: StickName, opts?: StickOptions): StickVector;
|
|
508
|
+
/**
|
|
509
|
+
* The label to PRINT for a button on the controller the rider is actually holding —
|
|
510
|
+
* `buttonLabel("DIAMOND_DOWN")` gives `"A"` on an Xbox pad, `"✕"` on a DualSense, `"B"` on a
|
|
511
|
+
* Switch Pro.
|
|
512
|
+
*
|
|
513
|
+
* **Use this everywhere you would otherwise type a button letter.** The wire vocabulary is
|
|
514
|
+
* positional ({@link ButtonName}) precisely because no letter is correct on every pad, so a
|
|
515
|
+
* hardcoded "Press A" is wrong for most riders. For a graphical prompt prefer a keycap from
|
|
516
|
+
* `@rydr/game-sdk/ui`, which calls this for you and draws the diamond.
|
|
517
|
+
*
|
|
518
|
+
* Reactive: the value changes when the rider swaps controllers mid-session. If you cache the
|
|
519
|
+
* string, re-read it in `onHardware` (the glyph set lives on the hardware snapshot) — or just call
|
|
520
|
+
* this at render time, it's a table lookup.
|
|
521
|
+
*/
|
|
522
|
+
buttonLabel(name: ButtonName): string;
|
|
488
523
|
/**
|
|
489
524
|
* Rumble the connected controller. Pass a named effect (`vibrate("hit")`) or a custom
|
|
490
525
|
* `{ pattern, intensity }` (`vibrate({ pattern: [100, 50, 100], intensity: 1 })`). The shell
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlatformClient.d.ts","sourceRoot":"","sources":["../../src/client/PlatformClient.ts"],"names":[],"mappings":"AASA,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAChF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"PlatformClient.d.ts","sourceRoot":"","sources":["../../src/client/PlatformClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,10 +8,16 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { RYDR_PROTOCOL_VERSION, RYDR_SDK_VERSION } from "../protocol/version.js";
|
|
10
10
|
import { ALL_CAPABILITIES } from "../protocol/capabilities.js";
|
|
11
|
-
import { KEY_TO_BUTTON } from "../protocol/buttons.js";
|
|
11
|
+
import { KEY_TO_BUTTON, NUMPAD_CODE_TO_BUTTON } from "../protocol/buttons.js";
|
|
12
|
+
import { buttonLabel } from "../protocol/glyphs.js";
|
|
12
13
|
import { setPlatformMaster } from "../sounds/platform-master.js";
|
|
13
14
|
import { decodeReplay, encodeReplay } from "./replayCodec.js";
|
|
14
15
|
import { isPlatformToGameMessage, isRydrMessage } from "../protocol/guards.js";
|
|
16
|
+
// Ambient only: declares `window.__PERF__` + the `rydr:perf-toggle` event that the secret
|
|
17
|
+
// perf-overlay button code below flips. Side-effect import (the module emits nothing) so the
|
|
18
|
+
// augmentation follows the client graph — the `three/` perf modules that also use it are an
|
|
19
|
+
// optional subpath a plain game never imports.
|
|
20
|
+
import "../protocol/perf.js";
|
|
15
21
|
import { HardwareStore, DEFAULT_POWER_TAU_S } from "./HardwareStore.js";
|
|
16
22
|
import { createRelayRoom, createLoopbackRoom } from "./Room.js";
|
|
17
23
|
function emit(set, value) {
|
|
@@ -27,11 +33,14 @@ function emit(set, value) {
|
|
|
27
33
|
* it stays renderer-agnostic — only a boolean flag + a DOM CustomEvent, no Three.js dependency.
|
|
28
34
|
*
|
|
29
35
|
* Two palindromes: the face-button code, and an arrows-only alternative for games where the face
|
|
30
|
-
* code can't be entered —
|
|
31
|
-
* the code completes; the D-pad only navigates *within* a game, never out of it.
|
|
36
|
+
* code can't be entered — `DIAMOND_RIGHT` is the house back/cancel button, so it pops menus/exits
|
|
37
|
+
* before the code completes; the D-pad only navigates *within* a game, never out of it.
|
|
38
|
+
*
|
|
39
|
+
* The face code is a clockwise sweep of the diamond and back (top → right → bottom → left, reversed),
|
|
40
|
+
* so it is the same physical gesture on every pad regardless of the letters printed on it.
|
|
32
41
|
*/
|
|
33
42
|
const PERF_CODES = [
|
|
34
|
-
["
|
|
43
|
+
["DIAMOND_UP", "DIAMOND_RIGHT", "DIAMOND_DOWN", "DIAMOND_LEFT", "DIAMOND_LEFT", "DIAMOND_DOWN", "DIAMOND_RIGHT", "DIAMOND_UP"],
|
|
35
44
|
["UP", "RIGHT", "DOWN", "LEFT", "LEFT", "DOWN", "RIGHT", "UP"],
|
|
36
45
|
];
|
|
37
46
|
const PERF_CODE_MAXLEN = Math.max(...PERF_CODES.map((c) => c.length));
|
|
@@ -234,6 +243,10 @@ export function connectToPlatform(options) {
|
|
|
234
243
|
// Manifest-configured smoothing wins over any ConnectOptions default.
|
|
235
244
|
if (welcome.powerSmoothing != null)
|
|
236
245
|
hardware._setSmoothingTau(welcome.powerSmoothing);
|
|
246
|
+
// Seed the glyph set BEFORE resolving, so the game's first frame already prints the right
|
|
247
|
+
// button labels instead of flashing the default and correcting on the first input.status.
|
|
248
|
+
if (welcome.glyphSet)
|
|
249
|
+
hardware._patch({ glyphSet: welcome.glyphSet });
|
|
237
250
|
resolve(session);
|
|
238
251
|
break;
|
|
239
252
|
}
|
|
@@ -286,7 +299,12 @@ export function connectToPlatform(options) {
|
|
|
286
299
|
hardware._patch({ trainerConnected: msg.connected, ergSupported: msg.ergSupported });
|
|
287
300
|
break;
|
|
288
301
|
case "rydr/input.status":
|
|
289
|
-
|
|
302
|
+
// `glyphSet` is optional on the wire (an older shell omits it), so keep the current value
|
|
303
|
+
// rather than snapping every on-screen prompt back to the default mid-session.
|
|
304
|
+
hardware._patch({
|
|
305
|
+
controllerConnected: msg.controllerConnected,
|
|
306
|
+
...(msg.glyphSet ? { glyphSet: msg.glyphSet } : {}),
|
|
307
|
+
});
|
|
290
308
|
break;
|
|
291
309
|
case "rydr/lifecycle.pause":
|
|
292
310
|
emit(pauseListeners, undefined);
|
|
@@ -395,15 +413,22 @@ export function connectToPlatform(options) {
|
|
|
395
413
|
const kbDown = new Set();
|
|
396
414
|
// Keyboard analog emulation: keep the axis values in sync with keyboard-held buttons so
|
|
397
415
|
// `session.axis()` / `session.stick()` work on the keyboard for local dev — quantized to the
|
|
398
|
-
// endpoints (a real hall controller sends continuous values over the wire instead).
|
|
399
|
-
//
|
|
400
|
-
//
|
|
401
|
-
//
|
|
416
|
+
// endpoints (a real hall controller sends continuous values over the wire instead). The arrow /
|
|
417
|
+
// d-pad keys drive the LEFT stick and the numpad 8/4/5/6 keys (RUP/RLEFT/RDOWN/RRIGHT, see
|
|
418
|
+
// `NUMPAD_CODE_TO_BUTTON`) the RIGHT one — opposite keys cancel, so LEFT+RIGHT (and RLEFT+RRIGHT)
|
|
419
|
+
// reads 0. The trigger keys (a/e → LT/RT), `o` → OPTIONS, and the numpad 1/3 stick-click keys
|
|
420
|
+
// (LSTICK_PRESS/RSTICK_PRESS) are buttons only and have no axis.
|
|
421
|
+
// Derived from `kbDown` (keyboard-only held state), so a relayed controller's own `input.axis`
|
|
422
|
+
// samples are the authority whenever one is actually in use.
|
|
423
|
+
//
|
|
424
|
+
// The right-stick keys deliberately do NOT also feed `LX`/`LY`: a twin-stick game would move while
|
|
425
|
+
// aiming, and the keyboard is the reference implementation of the documented contract ("LSTICK" reads
|
|
426
|
+
// LX/LY). A game wanting one merged scheme can OR the two sticks itself; the reverse isn't possible.
|
|
402
427
|
const syncKeyboardAxes = () => {
|
|
403
|
-
axisValues.set("LT", kbDown.has("LT") ? 1 : 0);
|
|
404
|
-
axisValues.set("RT", kbDown.has("RT") ? 1 : 0);
|
|
405
428
|
axisValues.set("LX", (kbDown.has("RIGHT") ? 1 : 0) - (kbDown.has("LEFT") ? 1 : 0));
|
|
406
429
|
axisValues.set("LY", (kbDown.has("UP") ? 1 : 0) - (kbDown.has("DOWN") ? 1 : 0));
|
|
430
|
+
axisValues.set("RX", (kbDown.has("RRIGHT") ? 1 : 0) - (kbDown.has("RLEFT") ? 1 : 0));
|
|
431
|
+
axisValues.set("RY", (kbDown.has("RUP") ? 1 : 0) - (kbDown.has("RDOWN") ? 1 : 0));
|
|
407
432
|
};
|
|
408
433
|
// A game canvas has no text fields, but admin editors mounted as guests (the song and
|
|
409
434
|
// voice-over editors) do. When a keystroke is destined for a focused editable element, it
|
|
@@ -464,7 +489,9 @@ export function connectToPlatform(options) {
|
|
|
464
489
|
}
|
|
465
490
|
if (!grantedCapabilities.includes("buttons"))
|
|
466
491
|
return;
|
|
467
|
-
|
|
492
|
+
// NUMPAD_CODE_TO_BUTTON (right stick + both stick clicks) is keyed by `.code`, not `.key` — see
|
|
493
|
+
// its doc comment for why (Num Lock changes what `.key` a numpad digit reports).
|
|
494
|
+
const name = KEY_TO_BUTTON[e.key] ?? NUMPAD_CODE_TO_BUTTON[e.code];
|
|
468
495
|
if (!name)
|
|
469
496
|
return;
|
|
470
497
|
e.preventDefault(); // stop arrow/space from scrolling the page
|
|
@@ -479,7 +506,7 @@ export function connectToPlatform(options) {
|
|
|
479
506
|
// keyup for a button that isn't held is a no-op inside applyButton.
|
|
480
507
|
if (!grantedCapabilities.includes("buttons"))
|
|
481
508
|
return;
|
|
482
|
-
const name = KEY_TO_BUTTON[e.key];
|
|
509
|
+
const name = KEY_TO_BUTTON[e.key] ?? NUMPAD_CODE_TO_BUTTON[e.code];
|
|
483
510
|
if (!name)
|
|
484
511
|
return;
|
|
485
512
|
kbDown.delete(name);
|
|
@@ -789,6 +816,9 @@ export function connectToPlatform(options) {
|
|
|
789
816
|
const scale = scaled / rawMag; // maps the raw vector to the rescaled, clamped magnitude
|
|
790
817
|
return { x: rawX * scale, y: rawY * scale, magnitude: scaled, angle: Math.atan2(rawY, rawX) };
|
|
791
818
|
},
|
|
819
|
+
// Read through to the live snapshot rather than capturing the set, so a mid-session controller
|
|
820
|
+
// swap changes every prompt on the next render with no game-side subscription.
|
|
821
|
+
buttonLabel: (name) => buttonLabel(hardware.current.glyphSet, name),
|
|
792
822
|
vibrate: (effect) => post({ rydr: true, type: "rydr/haptics.vibrate", effect }),
|
|
793
823
|
onPause: (cb) => {
|
|
794
824
|
pauseListeners.add(cb);
|
|
@@ -809,6 +839,7 @@ export function connectToPlatform(options) {
|
|
|
809
839
|
teardownKeyboard();
|
|
810
840
|
buttonListeners.clear();
|
|
811
841
|
heldButtons.clear();
|
|
842
|
+
axisValues.clear();
|
|
812
843
|
pauseListeners.clear();
|
|
813
844
|
resumeListeners.clear();
|
|
814
845
|
identityListeners.clear();
|