@rydr/game-sdk 3.27.0 → 5.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 +26 -21
- 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 +51 -28
- package/dist/client/PlatformClient.d.ts.map +1 -1
- package/dist/client/PlatformClient.js +33 -9
- package/dist/client/PlatformClient.js.map +1 -1
- package/dist/host/PlatformHost.d.ts +30 -13
- package/dist/host/PlatformHost.d.ts.map +1 -1
- package/dist/host/PlatformHost.js +42 -8
- package/dist/host/PlatformHost.js.map +1 -1
- package/dist/nav/README.md +26 -6
- 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 +27 -0
- package/dist/nav/spatial-nav.d.ts.map +1 -1
- package/dist/nav/spatial-nav.js +173 -8
- package/dist/nav/spatial-nav.js.map +1 -1
- package/dist/protocol/boards.d.ts +0 -8
- package/dist/protocol/boards.d.ts.map +1 -1
- package/dist/protocol/boards.js.map +1 -1
- package/dist/protocol/buttons.d.ts +49 -15
- package/dist/protocol/buttons.d.ts.map +1 -1
- package/dist/protocol/buttons.js +39 -17
- package/dist/protocol/buttons.js.map +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 +25 -19
- package/dist/protocol/messages.d.ts.map +1 -1
- package/dist/protocol/version.d.ts +2 -2
- package/dist/protocol/version.d.ts.map +1 -1
- package/dist/protocol/version.js +47 -2
- package/dist/protocol/version.js.map +1 -1
- package/dist/three/README.md +1 -1
- package/dist/ui/README.md +6 -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/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/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
|
@@ -131,8 +131,8 @@ exists but defaults to ALL; you don't set it.)
|
|
|
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)` — trainer control.
|
|
134
|
-
- `setRoute(path)` · `createRouter({ onRoute })` · `setPowerBar(visible)` · `
|
|
135
|
-
- `
|
|
134
|
+
- `setRoute(path)` · `createRouter({ onRoute })` · `setPowerBar(visible)` · `requestExit()` · `requestHardwareModal()`.
|
|
135
|
+
- `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
136
|
|
|
137
137
|
> **Routing — use `createRouter`, not `setRoute` by hand.** `setRoute(path)` only *mirrors* your current route into the shell's top URL (shareable/refreshable). It does **nothing** for browser Back/Forward — those are driven by the history stack, and an iframe's own `history.pushState` entries join that stack, so a guest gets Back/Forward *for free* the moment it pushes one entry per navigation. `session.createRouter({ onRoute })` owns that wiring: `pushState` on `go()`, a `popstate` listener, dispatching the boot path from the real URL / `initialPath`, and re-mirroring via `setRoute` on every dispatch (including Back/Forward). You keep your own route *table* — parse the path in `onRoute` and mount your screen:
|
|
138
138
|
>
|
|
@@ -191,6 +191,11 @@ const anyHeld = session.buttonsDown().length > 0;
|
|
|
191
191
|
const throttle = session.axis("RT"); // 0..1 (0 on a plain, non-hall trigger until pressed)
|
|
192
192
|
const move = session.stick("LSTICK"); // { x, y, magnitude, angle }, radially deadzoned
|
|
193
193
|
player.x += move.x * speed; // smooth on hall sticks, clean 8-way on a d-pad
|
|
194
|
+
const aim = session.stick("RSTICK"); // the SECOND stick: aim / camera (RX/RY)
|
|
195
|
+
|
|
196
|
+
// The right stick is also readable digitally, and clicking a stick is a plain button:
|
|
197
|
+
const aimX = (session.isDown("RRIGHT") ? 1 : 0) - (session.isDown("RLEFT") ? 1 : 0);
|
|
198
|
+
session.onButton(({ name, edge }) => { if (name === "RSTICK_PRESS" && edge === "down") recentreCamera(); });
|
|
194
199
|
```
|
|
195
200
|
|
|
196
201
|
- **Do NOT** add `window.addEventListener("keydown"/"keyup")`, read `e.key`/`e.code`/`keyCode`,
|
|
@@ -201,11 +206,12 @@ player.x += move.x * speed; // smooth on hall sticks, clean
|
|
|
201
206
|
delivered per-frame and per-focus. `onButton`/`isDown`/`buttonsDown` are immune to all of that:
|
|
202
207
|
the SDK normalises **keyboard, phone, and Zwift Play** into one stream and delivers it whether
|
|
203
208
|
the shell or the game canvas holds focus.
|
|
204
|
-
- **Canonical names:** `UP`/`DOWN`/`LEFT`/`RIGHT`
|
|
205
|
-
|
|
206
|
-
`
|
|
207
|
-
|
|
208
|
-
|
|
209
|
+
- **Canonical names:** `UP`/`DOWN`/`LEFT`/`RIGHT` (D-pad **and** left stick) + right-stick directions
|
|
210
|
+
`RUP`/`RDOWN`/`RLEFT`/`RRIGHT` + face diamond `DIAMOND_UP`/`DIAMOND_DOWN`/`DIAMOND_LEFT`/`DIAMOND_RIGHT` + shoulder triggers `LT`/`RT` + joystick
|
|
211
|
+
clicks `LSTICK_PRESS`/`RSTICK_PRESS`. 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
|
|
212
|
+
other name is contextual (no built-in meaning). Show the actual glyph in prompts; never hard-code a
|
|
213
|
+
device. (Phones expose at least the `DIAMOND_DOWN`/`DIAMOND_RIGHT` pair, and most controllers have no `LT`/`RT`, no second
|
|
214
|
+
stick and no stick click — don't require them for a flow that must work everywhere.)
|
|
209
215
|
- **Edges are real holds:** a held button emits exactly one `down` and one `up`. For "is it held
|
|
210
216
|
right now?" use `isDown`/`buttonsDown` rather than tracking edges yourself.
|
|
211
217
|
- **Analog is opt-in and additive:** when a controller has **hall-effect** sensors, read the
|
|
@@ -217,8 +223,10 @@ player.x += move.x * speed; // smooth on hall sticks, clean
|
|
|
217
223
|
game uses either or both. And `axis()`/`stick()` are **always readable**: on a plain, non-hall
|
|
218
224
|
controller the value is quantized to the endpoints (a trigger snaps `0`/`1`, a d-pad snaps
|
|
219
225
|
`-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 trigger keys drive `LT`/`RT
|
|
221
|
-
|
|
226
|
+
sample arrives. The **keyboard emulates axes too** (for local dev): the trigger keys drive `LT`/`RT`, the
|
|
227
|
+
arrow keys the left stick and `i`/`j`/`k`/`l` the right stick, so `axis()` and both `stick("LSTICK")` /
|
|
228
|
+
`stick("RSTICK")` work without a controller. The right stick is also readable digitally
|
|
229
|
+
(`RUP`/`RDOWN`/`RLEFT`/`RRIGHT`) and each stick's click is a plain button (`f`/`o` on the keyboard).
|
|
222
230
|
- **The only legitimate exception is a non-gameplay authoring tool** — an admin-gated **world /
|
|
223
231
|
level editor** or a dev-only showcase page that runs outside the rider flow. There, raw keyboard
|
|
224
232
|
(Ctrl+Z, arrows to nudge, Delete) is the correct desktop UX. *Gameplay* never uses it.
|
|
@@ -292,7 +300,7 @@ if (r) spawnGhost(r.body.frames); // r = { body: ReplayBody, meta: ReplayMeta |
|
|
|
292
300
|
> 2. **The URL carries only the `runId` — fetch everything else.** Do **not** gate replay entry on a level/track/difficulty/song param: you won't have one. `getReplay(runId)` returns the frames, and whatever the run was (which level, the chart, the player) must come from *inside* that replay (put it in the first frame's `customData`, or store it under the run via `saveRun`/`getRun`). This is why the route is the same for every game.
|
|
293
301
|
> 3. **Handle "no replay" gracefully.** `getReplay` returns `null` when nothing was stored for that id (e.g. a pre-feature or non-PB run). Fall back to a menu — never hang on a blank screen.
|
|
294
302
|
> 4. **Play it back read-only.** It's a viewer, not a run: **no** hardware input, **no** `startRun`, **no** `saveReplay`/recording. Drive your scene purely from `r.body.frames`.
|
|
295
|
-
> 5. **
|
|
303
|
+
> 5. **Keep the URL stable.** A replay is read-only, so hide the power bar (`setPowerBar(false)`) and call `setRoute('replay/' + runId)` so a refresh stays on the replay. Derive the exit/back target from the fetched replay (e.g. that level's leaderboard), not from the URL.
|
|
296
304
|
> 6. **Serve the path from your origin.** Like every projected route, a hard refresh of `…/replay/<id>` hits your origin as `/replay/<id>` — add the [SPA rewrite](#platformsession) so it falls back to `index.html` instead of 404ing.
|
|
297
305
|
>
|
|
298
306
|
> ```ts
|
|
@@ -303,8 +311,7 @@ if (r) spawnGhost(r.body.frames); // r = { body: ReplayBody, meta: ReplayMeta |
|
|
|
303
311
|
> const runId = m[1];
|
|
304
312
|
> const r = await session.getReplay(runId); // null ⇒ nothing stored for this run
|
|
305
313
|
> if (!r) return goToMenu(); // graceful fallback, no blank screen
|
|
306
|
-
> session.
|
|
307
|
-
> session.setPowerBar(false); // …no live power bar (this isn't a run)
|
|
314
|
+
> session.setPowerBar(false); // read-only viewer — no live power bar
|
|
308
315
|
> session.setRoute('replay/' + runId); // keep the URL stable across refresh
|
|
309
316
|
> playReplayReadOnly(r.body.frames); // no input, no startRun, no recording
|
|
310
317
|
> return; // skip your normal "start a run" boot path
|
|
@@ -347,15 +354,14 @@ Any game can ship an **editor** for its own `shared` content (levels, tracks, ch
|
|
|
347
354
|
|
|
348
355
|
1. **Gate your editor UI** on `session.identity.isAdmin` — show the "Edit" button / editor route only when it's `true`.
|
|
349
356
|
2. **Call the normal session methods** — `saveContent` / `deleteContent` / `getUploadUrl`. The shell performs the write as the signed-in admin (Supabase RLS on their admin role; your iframe never authenticates). For a non-admin player these reject; for an admin they succeed.
|
|
350
|
-
3. **Hide the
|
|
357
|
+
3. **Hide the power bar** — an editor is a full-screen authoring tool with nothing to pedal, so on boot call **`session.setPowerBar(false)`** to drop the trainerless power slider. (The shell draws no other per-game chrome — its platform menu is summoned on demand — so there's nothing else to hide.) This is the one place a guest should hide the power bar; gameplay and game menus always leave it visible.
|
|
351
358
|
|
|
352
359
|
```ts
|
|
353
360
|
// in-game editor (embedded in the shell — the normal case)
|
|
354
361
|
if (session.identity.isAdmin) {
|
|
355
362
|
showEditorButton();
|
|
356
363
|
}
|
|
357
|
-
// …on editor boot,
|
|
358
|
-
session.setMenu("hidden"); // remove the hamburger + pills (NOT false, which keeps them for gameplay)
|
|
364
|
+
// …on editor boot, drop the power slider — nothing to pedal in an editor:
|
|
359
365
|
session.setPowerBar(false);
|
|
360
366
|
// …when the author saves:
|
|
361
367
|
const { uploadUrl, url } = await session.getUploadUrl({ collection: "songs", filename: "track.mp3" });
|
|
@@ -385,8 +391,7 @@ if (!session.identity.isAdmin) {
|
|
|
385
391
|
// Not an admin — show "sign in to the platform as an admin", author nothing.
|
|
386
392
|
return;
|
|
387
393
|
}
|
|
388
|
-
//
|
|
389
|
-
session.setMenu("hidden");
|
|
394
|
+
// Drop the power slider — an editor has nothing to pedal.
|
|
390
395
|
session.setPowerBar(false);
|
|
391
396
|
// Admin: author through the session. The shell writes as the signed-in admin; this iframe never authenticates.
|
|
392
397
|
const { uploadUrl, url } = await session.getUploadUrl({ collection: "levels", filename: "bg.png" });
|
|
@@ -489,16 +494,16 @@ A self-contained DOM/CSS kit for the **controller-button UI** — showing "which
|
|
|
489
494
|
import { createKeycap, mountActionDiamond, mountDialogueCard } from "@rydr/game-sdk/ui";
|
|
490
495
|
|
|
491
496
|
// a single A keycap that reflects real presses — pass the session (it satisfies `ButtonSource`)
|
|
492
|
-
host.appendChild(createKeycap("
|
|
497
|
+
host.appendChild(createKeycap("DIAMOND_DOWN", { press: session }).el);
|
|
493
498
|
|
|
494
|
-
// the full
|
|
499
|
+
// the full face-diamond move diamond, wired to input
|
|
495
500
|
const dia = mountActionDiamond(host, { press: session, cards: { A: { label: "Séisme" } } });
|
|
496
501
|
dia.cards.A.setActive(true);
|
|
497
502
|
dia.cards.Z.keycap.setCooldown(0.4, 3); // depleting ring + countdown
|
|
498
503
|
```
|
|
499
504
|
|
|
500
|
-
- **`createKeycap(button, opts)`** / **`createDpadKeycap(dir, opts)`** — a keycap of the
|
|
501
|
-
- **`mountCard`** — the dark glass container with a content slot. **`mountActionCard`** — that card + a keycap straddling its bottom edge. **`mountActionDiamond`** — the full
|
|
505
|
+
- **`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.
|
|
506
|
+
- **`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.
|
|
502
507
|
- **`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.
|
|
503
508
|
|
|
504
509
|
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":"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"}
|
|
@@ -174,13 +174,15 @@ export interface PlatformSession {
|
|
|
174
174
|
}): Promise<RankResult>;
|
|
175
175
|
/**
|
|
176
176
|
* Begin a new run/attempt. The shell mints a fresh {@link runId} (rotating the id used by the
|
|
177
|
-
* run's `saveRun`/`saveReplay`) AND opens a named effort window on its ride recording
|
|
178
|
-
* `name` (e.g. `
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
177
|
+
* run's `saveRun`/`saveReplay`) AND opens a named effort window on its ride recording. Pass the
|
|
178
|
+
* run's `name` (e.g. `"Through the Fire and Flames"`) and optional `tags` (e.g. `["Expert", "No Fail"]`)
|
|
179
|
+
* — these label the run in the activity Breakdown AND on the leaderboard board it submits to; the
|
|
180
|
+
* platform stores + shows them verbatim and never parses them. Call at the start of each attempt so
|
|
181
|
+
* its score/breakdown/replay are stored under their own id. A restart (calling `startRun` again)
|
|
182
|
+
* discards the previous still-open *unsaved* window. Resolves with the new id + a platform-clock
|
|
183
|
+
* start time. Throws against an older shell that doesn't support it.
|
|
182
184
|
*/
|
|
183
|
-
startRun(name?: string): Promise<{
|
|
185
|
+
startRun(name?: string, tags?: string[]): Promise<{
|
|
184
186
|
runId: string;
|
|
185
187
|
startedAt: number;
|
|
186
188
|
}>;
|
|
@@ -409,35 +411,32 @@ export interface PlatformSession {
|
|
|
409
411
|
* {@link setRoute}); the router does not change that requirement.
|
|
410
412
|
*/
|
|
411
413
|
createRouter(options: CreateRouterOptions): GuestRouter;
|
|
412
|
-
/** Ask the shell to show/hide the trainerless power bar. Default is visible
|
|
413
|
-
*
|
|
414
|
+
/** Ask the shell to show/hide the trainerless power bar. Default is visible — leave it alone
|
|
415
|
+
* during gameplay and menus (a keyboard rider must always be able to feed power). Editors are
|
|
416
|
+
* the one exception: a full-screen authoring tool has nothing to pedal, so it hides the bar
|
|
417
|
+
* with `setPowerBar(false)`. */
|
|
414
418
|
setPowerBar(visible: boolean): void;
|
|
415
|
-
/**
|
|
416
|
-
* Control the in-game platform menu — the hamburger that opens Exit + hardware/settings/profile.
|
|
417
|
-
* Three states:
|
|
418
|
-
* - `true` (default) — chrome shown; use on menu/browsing screens.
|
|
419
|
-
* - `false` — immersive play; the overlay collapses but the hamburger **stays** so the rider can
|
|
420
|
-
* always exit and reach hardware mid-ride. Use during gameplay.
|
|
421
|
-
* - `"hidden"` — full takeover; the hamburger and the floating resistance/FTP pills are removed
|
|
422
|
-
* **entirely**. Use in any authoring tool / editor (chart, album, world, voice-over): an editor
|
|
423
|
-
* is a full-screen tool with its own navigation, so the shell menu must not overlay it. There is
|
|
424
|
-
* no rider to strand, so hiding the exit is safe.
|
|
425
|
-
*
|
|
426
|
-
* Rule of thumb: **every game editor must call `setMenu("hidden")`** (alongside
|
|
427
|
-
* `setPowerBar(false)`). `false` is for gameplay, never for editors.
|
|
428
|
-
*/
|
|
429
|
-
setMenu(state: boolean | "hidden"): void;
|
|
430
419
|
/**
|
|
431
420
|
* Report whether the rider is in **active gameplay** or navigating a **menu** (picker,
|
|
432
421
|
* results, leaderboard, pause screen, etc.). The shell uses this to ease trainer resistance
|
|
433
|
-
* whenever the rider isn't racing — so you keep spinning comfortably while browsing — and
|
|
422
|
+
* (~35%) whenever the rider isn't racing — so you keep spinning comfortably while browsing — and
|
|
434
423
|
* restores full resistance the instant you go back to `"playing"`. The game never sets a
|
|
435
424
|
* resistance value; it only declares state.
|
|
436
425
|
*
|
|
437
|
-
* - `"
|
|
438
|
-
*
|
|
426
|
+
* - `"menu"` — any non-racing screen; the shell eases resistance. **This is the default** — a
|
|
427
|
+
* freshly-launched game is treated as being in its own menus until it says otherwise.
|
|
428
|
+
* - `"playing"` — active gameplay; the shell holds FULL rider resistance.
|
|
439
429
|
*
|
|
440
|
-
*
|
|
430
|
+
* **The one rule: call `setActivity("playing")` when your gameplay loop is live, and `"menu"`
|
|
431
|
+
* on every other screen — including your title/menu screen at boot.** If you never call it, the
|
|
432
|
+
* rider stays eased (~35% lighter) the whole time — safe, but your race won't feel full. You
|
|
433
|
+
* only ever declare menu↔play; the shell owns all the *cleanup* — opening the platform menu,
|
|
434
|
+
* pausing, exiting, or a crash all reset you to the eased rest state automatically, so you can
|
|
435
|
+
* never leave the trainer stuck at full resistance.
|
|
436
|
+
*
|
|
437
|
+
* A DOM-menu game with a screen router should map every screen → state and call this on each
|
|
438
|
+
* transition (see the guitar-hero `ACTIVITY_STATE` map). A game with a phase enum should call
|
|
439
|
+
* `"playing"` on the play phase and `"menu"` on results/pause/etc.
|
|
441
440
|
*/
|
|
442
441
|
setActivity(state: "playing" | "menu"): void;
|
|
443
442
|
/** Ask the shell to exit the game (back to the launcher). */
|
|
@@ -457,9 +456,15 @@ export interface PlatformSession {
|
|
|
457
456
|
* - **Held button** (hold-to-brake, digital steer) → `isDown(name)` / `buttonsDown()` — poll each frame.
|
|
458
457
|
* - **Analog trigger** (throttle, brake pressure) → `axis("LT"|"RT")` — `0..1`, poll each frame.
|
|
459
458
|
* - **Joystick** (2D move/aim) → `stick("LSTICK"|"RSTICK")` — deadzoned `{x, y, magnitude, angle}`.
|
|
459
|
+
* - **Second stick, digitally** (8-way aim) → `RUP`/`RDOWN`/`RLEFT`/`RRIGHT` via `onButton`/`isDown` —
|
|
460
|
+
* the right stick's own edges, so they never collide with the D-pad's `UP`/`DOWN`/`LEFT`/`RIGHT`
|
|
461
|
+
* (which the left stick shares).
|
|
462
|
+
* - **Stick click** (crouch, recentre camera) → `LSTICK_PRESS`/`RSTICK_PRESS` via `onButton`/`isDown` —
|
|
463
|
+
* an ordinary button edge, not an axis. Rare hardware: always leave a second way to do it.
|
|
460
464
|
*
|
|
461
465
|
* The digital (`onButton`/`isDown`) and analog (`axis`/`stick`) streams run in parallel — the same
|
|
462
|
-
* trigger is readable both ways (`isDown("LT")`
|
|
466
|
+
* trigger is readable both ways (`isDown("LT")` / `axis("LT")`) and so is the same stick
|
|
467
|
+
* (`isDown("RLEFT")` / `stick("RSTICK")`), so use whichever fits, or both.
|
|
463
468
|
* The SDK normalises keyboard, phone, and Zwift Play into these streams and delivers them whether
|
|
464
469
|
* the shell or the game canvas holds focus (raw DOM key events silently break across iframe focus).
|
|
465
470
|
*
|
|
@@ -494,8 +499,26 @@ export interface PlatformSession {
|
|
|
494
499
|
* the stick's two axes (`"LSTICK"` → `LX`/`LY`, `"RSTICK"` → `RX`/`RY`) into `{ x, y, magnitude,
|
|
495
500
|
* angle }`. Because axes are always populated, this returns a smooth vector on hall hardware and a
|
|
496
501
|
* clean 8-way vector on a d-pad — one code path. Default deadzone `0.1`; override via `opts`.
|
|
502
|
+
*
|
|
503
|
+
* Clicking the stick is a separate button edge (`LSTICK_PRESS`/`RSTICK_PRESS`), not part of this vector,
|
|
504
|
+
* and the right stick's deflection also arrives digitally as `RUP`/`RDOWN`/`RLEFT`/`RRIGHT`.
|
|
497
505
|
*/
|
|
498
506
|
stick(name: StickName, opts?: StickOptions): StickVector;
|
|
507
|
+
/**
|
|
508
|
+
* The label to PRINT for a button on the controller the rider is actually holding —
|
|
509
|
+
* `buttonLabel("DIAMOND_DOWN")` gives `"A"` on an Xbox pad, `"✕"` on a DualSense, `"B"` on a
|
|
510
|
+
* Switch Pro.
|
|
511
|
+
*
|
|
512
|
+
* **Use this everywhere you would otherwise type a button letter.** The wire vocabulary is
|
|
513
|
+
* positional ({@link ButtonName}) precisely because no letter is correct on every pad, so a
|
|
514
|
+
* hardcoded "Press A" is wrong for most riders. For a graphical prompt prefer a keycap from
|
|
515
|
+
* `@rydr/game-sdk/ui`, which calls this for you and draws the diamond.
|
|
516
|
+
*
|
|
517
|
+
* Reactive: the value changes when the rider swaps controllers mid-session. If you cache the
|
|
518
|
+
* string, re-read it in `onHardware` (the glyph set lives on the hardware snapshot) — or just call
|
|
519
|
+
* this at render time, it's a table lookup.
|
|
520
|
+
*/
|
|
521
|
+
buttonLabel(name: ButtonName): string;
|
|
499
522
|
/**
|
|
500
523
|
* Rumble the connected controller. Pass a named effect (`vibrate("hit")`) or a custom
|
|
501
524
|
* `{ 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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"}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { RYDR_PROTOCOL_VERSION, RYDR_SDK_VERSION } from "../protocol/version.js";
|
|
10
10
|
import { ALL_CAPABILITIES } from "../protocol/capabilities.js";
|
|
11
11
|
import { KEY_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";
|
|
@@ -27,11 +28,14 @@ function emit(set, value) {
|
|
|
27
28
|
* it stays renderer-agnostic — only a boolean flag + a DOM CustomEvent, no Three.js dependency.
|
|
28
29
|
*
|
|
29
30
|
* 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.
|
|
31
|
+
* code can't be entered — `DIAMOND_RIGHT` is the house back/cancel button, so it pops menus/exits
|
|
32
|
+
* before the code completes; the D-pad only navigates *within* a game, never out of it.
|
|
33
|
+
*
|
|
34
|
+
* The face code is a clockwise sweep of the diamond and back (top → right → bottom → left, reversed),
|
|
35
|
+
* so it is the same physical gesture on every pad regardless of the letters printed on it.
|
|
32
36
|
*/
|
|
33
37
|
const PERF_CODES = [
|
|
34
|
-
["
|
|
38
|
+
["DIAMOND_UP", "DIAMOND_RIGHT", "DIAMOND_DOWN", "DIAMOND_LEFT", "DIAMOND_LEFT", "DIAMOND_DOWN", "DIAMOND_RIGHT", "DIAMOND_UP"],
|
|
35
39
|
["UP", "RIGHT", "DOWN", "LEFT", "LEFT", "DOWN", "RIGHT", "UP"],
|
|
36
40
|
];
|
|
37
41
|
const PERF_CODE_MAXLEN = Math.max(...PERF_CODES.map((c) => c.length));
|
|
@@ -234,6 +238,10 @@ export function connectToPlatform(options) {
|
|
|
234
238
|
// Manifest-configured smoothing wins over any ConnectOptions default.
|
|
235
239
|
if (welcome.powerSmoothing != null)
|
|
236
240
|
hardware._setSmoothingTau(welcome.powerSmoothing);
|
|
241
|
+
// Seed the glyph set BEFORE resolving, so the game's first frame already prints the right
|
|
242
|
+
// button labels instead of flashing the default and correcting on the first input.status.
|
|
243
|
+
if (welcome.glyphSet)
|
|
244
|
+
hardware._patch({ glyphSet: welcome.glyphSet });
|
|
237
245
|
resolve(session);
|
|
238
246
|
break;
|
|
239
247
|
}
|
|
@@ -286,7 +294,12 @@ export function connectToPlatform(options) {
|
|
|
286
294
|
hardware._patch({ trainerConnected: msg.connected, ergSupported: msg.ergSupported });
|
|
287
295
|
break;
|
|
288
296
|
case "rydr/input.status":
|
|
289
|
-
|
|
297
|
+
// `glyphSet` is optional on the wire (an older shell omits it), so keep the current value
|
|
298
|
+
// rather than snapping every on-screen prompt back to the default mid-session.
|
|
299
|
+
hardware._patch({
|
|
300
|
+
controllerConnected: msg.controllerConnected,
|
|
301
|
+
...(msg.glyphSet ? { glyphSet: msg.glyphSet } : {}),
|
|
302
|
+
});
|
|
290
303
|
break;
|
|
291
304
|
case "rydr/lifecycle.pause":
|
|
292
305
|
emit(pauseListeners, undefined);
|
|
@@ -396,14 +409,22 @@ export function connectToPlatform(options) {
|
|
|
396
409
|
// Keyboard analog emulation: keep the axis values in sync with keyboard-held buttons so
|
|
397
410
|
// `session.axis()` / `session.stick()` work on the keyboard for local dev — quantized to the
|
|
398
411
|
// endpoints (a real hall controller sends continuous values over the wire instead). Triggers map
|
|
399
|
-
// to their own axis; the arrow / d-pad keys drive the LEFT stick
|
|
400
|
-
//
|
|
401
|
-
//
|
|
412
|
+
// to their own axis; the arrow / d-pad keys drive the LEFT stick and the i/j/k/l keys
|
|
413
|
+
// (RUP/RLEFT/RDOWN/RRIGHT) the RIGHT one — opposite keys cancel, so LEFT+RIGHT (and RLEFT+RRIGHT)
|
|
414
|
+
// reads 0. The stick-click keys (f/o → LSTICK_PRESS/RSTICK_PRESS) are buttons only and have no axis.
|
|
415
|
+
// Derived from `kbDown` (keyboard-only held state), so a relayed controller's own `input.axis`
|
|
416
|
+
// samples are the authority whenever one is actually in use.
|
|
417
|
+
//
|
|
418
|
+
// The right-stick keys deliberately do NOT also feed `LX`/`LY`: a twin-stick game would move while
|
|
419
|
+
// aiming, and the keyboard is the reference implementation of the documented contract ("LSTICK" reads
|
|
420
|
+
// LX/LY). A game wanting one merged scheme can OR the two sticks itself; the reverse isn't possible.
|
|
402
421
|
const syncKeyboardAxes = () => {
|
|
403
422
|
axisValues.set("LT", kbDown.has("LT") ? 1 : 0);
|
|
404
423
|
axisValues.set("RT", kbDown.has("RT") ? 1 : 0);
|
|
405
424
|
axisValues.set("LX", (kbDown.has("RIGHT") ? 1 : 0) - (kbDown.has("LEFT") ? 1 : 0));
|
|
406
425
|
axisValues.set("LY", (kbDown.has("UP") ? 1 : 0) - (kbDown.has("DOWN") ? 1 : 0));
|
|
426
|
+
axisValues.set("RX", (kbDown.has("RRIGHT") ? 1 : 0) - (kbDown.has("RLEFT") ? 1 : 0));
|
|
427
|
+
axisValues.set("RY", (kbDown.has("RUP") ? 1 : 0) - (kbDown.has("RDOWN") ? 1 : 0));
|
|
407
428
|
};
|
|
408
429
|
// A game canvas has no text fields, but admin editors mounted as guests (the song and
|
|
409
430
|
// voice-over editors) do. When a keystroke is destined for a focused editable element, it
|
|
@@ -568,7 +589,7 @@ export function connectToPlatform(options) {
|
|
|
568
589
|
knownBoard(boardId);
|
|
569
590
|
return request((nonce) => post({ rydr: true, type: "rydr/leaderboard.rankQuery", nonce, boardId, key: opts.key, value: opts.value })).then((r) => ({ boardId, rank: r.rank, total: r.total }));
|
|
570
591
|
},
|
|
571
|
-
startRun: (name) => request((nonce) => post({ rydr: true, type: "rydr/run.start", nonce, name })).then((r) => {
|
|
592
|
+
startRun: (name, tags) => request((nonce) => post({ rydr: true, type: "rydr/run.start", nonce, name, tags })).then((r) => {
|
|
572
593
|
if (r.error)
|
|
573
594
|
throw new Error(`[rydr-sdk] startRun: ${r.error}`);
|
|
574
595
|
runId = r.runId; // rotate the closure-captured id so `session.runId` + shell-stamped writes follow
|
|
@@ -763,7 +784,6 @@ export function connectToPlatform(options) {
|
|
|
763
784
|
return router;
|
|
764
785
|
},
|
|
765
786
|
setPowerBar: (visible) => post({ rydr: true, type: "rydr/ui.setPowerBar", visible }),
|
|
766
|
-
setMenu: (state) => post({ rydr: true, type: "rydr/ui.setMenu", visible: state === true, hidden: state === "hidden" }),
|
|
767
787
|
setActivity: (state) => post({ rydr: true, type: "rydr/activity.set", state }),
|
|
768
788
|
requestExit: () => post({ rydr: true, type: "rydr/exitRequest" }),
|
|
769
789
|
requestHardwareModal: () => post({ rydr: true, type: "rydr/ui.requestHardwareModal" }),
|
|
@@ -790,6 +810,9 @@ export function connectToPlatform(options) {
|
|
|
790
810
|
const scale = scaled / rawMag; // maps the raw vector to the rescaled, clamped magnitude
|
|
791
811
|
return { x: rawX * scale, y: rawY * scale, magnitude: scaled, angle: Math.atan2(rawY, rawX) };
|
|
792
812
|
},
|
|
813
|
+
// Read through to the live snapshot rather than capturing the set, so a mid-session controller
|
|
814
|
+
// swap changes every prompt on the next render with no game-side subscription.
|
|
815
|
+
buttonLabel: (name) => buttonLabel(hardware.current.glyphSet, name),
|
|
793
816
|
vibrate: (effect) => post({ rydr: true, type: "rydr/haptics.vibrate", effect }),
|
|
794
817
|
onPause: (cb) => {
|
|
795
818
|
pauseListeners.add(cb);
|
|
@@ -810,6 +833,7 @@ export function connectToPlatform(options) {
|
|
|
810
833
|
teardownKeyboard();
|
|
811
834
|
buttonListeners.clear();
|
|
812
835
|
heldButtons.clear();
|
|
836
|
+
axisValues.clear();
|
|
813
837
|
pauseListeners.clear();
|
|
814
838
|
resumeListeners.clear();
|
|
815
839
|
identityListeners.clear();
|