@rydr/game-sdk 9.3.0 → 9.4.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.
@@ -0,0 +1,152 @@
1
+ /**
2
+ * THE SHARED CLOCK — agreement on what time it is, for everyone in a room (PLAT-1456).
3
+ *
4
+ * Lifted from Racing's `LiveClock` (RACI-131/213), which proved the design over a season of real
5
+ * races, and made transport-neutral so the shell can run one too. It is the whole of what a
6
+ * synchronised experience needs from the network: derive a timetable from a timestamp, and the only
7
+ * thing left to agree is the timestamp.
8
+ *
9
+ * offset = serverAt + rtt / 2 - now (rtt/2 ≈ the return leg)
10
+ *
11
+ * Several samples are taken and the one measured through the LOWEST latency wins — the standard
12
+ * NTP-ish trick, because a sample delayed by a slow return leg is the one whose `rtt/2` estimate is
13
+ * least trustworthy.
14
+ *
15
+ * **With no transport at all the offset stays 0 and {@link SharedClock.serverNow} is local time.**
16
+ * That is deliberate and load-bearing: a rider alone in a room, a standalone dev session and the
17
+ * SDK's loopback room must all run the same code path as a full field, because most of the time
18
+ * they will be the case.
19
+ *
20
+ * ## It re-syncs, and it reports what it measured
21
+ *
22
+ * Measurement continues for the life of the clock, in ROUNDS of {@link DEFAULT_SAMPLES} spaced
23
+ * {@link DEFAULT_RESYNC_MS} apart, and each round adopts its OWN best sample. Racing originally took
24
+ * three samples at boot and kept the lowest forever, which quietly made two things impossible: a
25
+ * connection that changed route mid-session could never correct its offset (the old "best" was
26
+ * unbeatable), and nothing downstream could know how bad the link was. Both matter once latency is a
27
+ * gameplay number rather than only a countdown's accuracy.
28
+ *
29
+ * ## A stamp is only yours if the server says it is
30
+ *
31
+ * Orchestration events are **broadcast to the whole room**, so every member sees every other
32
+ * member's probe reply. Racing's two wiring sites both fed those replies straight into
33
+ * `applyStamp` on the strength of the token alone (`RoomSession.ts`, `seriesRoom.ts`) — and tokens
34
+ * were per-client sequential (`s0`, `s1`, …), so two riders syncing at once would each apply the
35
+ * other's server stamp against their own send time and corrupt their offset. Nothing surfaced it:
36
+ * the error is a plausible-looking number.
37
+ *
38
+ * Fixed here twice over, because this is the kind of bug that comes back:
39
+ *
40
+ * 1. {@link SharedClock.applyStamp} takes `from` and **ignores any stamp not sent by `selfId`**.
41
+ * `RoomEvent.from` is stamped by the server and cannot be forged by a peer.
42
+ * 2. Tokens carry a per-instance random prefix, so even an unguarded caller cannot collide.
43
+ *
44
+ * Skew shows up as a mistimed transition, never as corrupted measurement: whatever the clock is
45
+ * used to schedule is timed locally. Worst case a badly-skewed member acts a fraction late.
46
+ */
47
+ /** How many round trips one sync ROUND measures before settling on an offset. */
48
+ export declare const DEFAULT_SAMPLES = 3;
49
+ /** Gap between round trips within a round (ms) — spread so one congested moment can't skew all. */
50
+ export declare const DEFAULT_SAMPLE_INTERVAL_MS = 400;
51
+ /**
52
+ * Gap between sync ROUNDS (ms). Measurement continues for the whole session: a member's route can
53
+ * change mid-session (wifi to cellular, a colo failover), and both the offset and the reported
54
+ * {@link SharedClock.rttMs} have to follow it.
55
+ *
56
+ * Far cheaper than it looks — three ~60-byte events a round — and it bounds how long a stale
57
+ * estimate can survive.
58
+ */
59
+ export declare const DEFAULT_RESYNC_MS = 30000;
60
+ /**
61
+ * The timer functions the clock schedules against. Injectable so a test can run rounds
62
+ * deterministically instead of waiting 30 real seconds; defaults to the host environment's.
63
+ */
64
+ export interface ClockTimers {
65
+ setTimeout(fn: () => void, ms: number): unknown;
66
+ clearTimeout(handle: unknown): void;
67
+ setInterval(fn: () => void, ms: number): unknown;
68
+ clearInterval(handle: unknown): void;
69
+ }
70
+ export interface SharedClockOptions {
71
+ /**
72
+ * This member's own id. **Stamps from anyone else are ignored** — see the note on broadcast
73
+ * replies in this file's header. Pass the same `playerId` the room knows you by.
74
+ */
75
+ selfId: string;
76
+ /** Samples per round. Defaults to {@link DEFAULT_SAMPLES}. */
77
+ samples?: number;
78
+ /** Gap between samples within a round. Defaults to {@link DEFAULT_SAMPLE_INTERVAL_MS}. */
79
+ sampleIntervalMs?: number;
80
+ /** Gap between rounds. Defaults to {@link DEFAULT_RESYNC_MS}. */
81
+ resyncIntervalMs?: number;
82
+ /** Local time source. Injectable for tests; defaults to `Date.now`. */
83
+ now?: () => number;
84
+ /** Timer source. Injectable for tests; defaults to the global timers. */
85
+ timers?: ClockTimers;
86
+ }
87
+ /**
88
+ * Tracks the offset between this machine's clock and the room server's, and reports
89
+ * {@link serverNow}.
90
+ *
91
+ * **ONE PER ROOM SESSION, not one per activity.** The offset is measured against the room server, so
92
+ * it is a property of the room; a clock rebuilt for each activity reports raw local time for its
93
+ * first round trip after every transition, which is exactly when it is most relied upon.
94
+ */
95
+ export declare class SharedClock {
96
+ private readonly selfId;
97
+ private readonly samples;
98
+ private readonly sampleIntervalMs;
99
+ private readonly resyncIntervalMs;
100
+ private readonly now;
101
+ private readonly timers;
102
+ /** ms to add to local time to land on server time. 0 until a sample lands (and when alone). */
103
+ private offset;
104
+ /**
105
+ * Best latency measured in the CURRENT round — reset when a round opens, so each round adopts its
106
+ * own fastest sample rather than being locked out by an old one.
107
+ */
108
+ private roundBestRtt;
109
+ /** Best latency of the last COMPLETED round, which is what {@link rttMs} reports. */
110
+ private lastRoundRtt;
111
+ /** Local send times of round trips still in flight, keyed by the token echoed back. */
112
+ private readonly inFlight;
113
+ private pending;
114
+ private roundTimer;
115
+ private nextToken;
116
+ /** Per-instance token prefix — belt to the `from` guard's braces (see the header). */
117
+ private readonly tokenPrefix;
118
+ private disposed;
119
+ constructor(options: SharedClockOptions);
120
+ /**
121
+ * Start measuring against the server. `probe` is handed a token to round-trip; whatever transport
122
+ * carries it must feed the reply back through {@link applyStamp} with the same token and the
123
+ * server's `from`.
124
+ *
125
+ * Safe to never call — the clock then reports local time, which is the correct answer for a member
126
+ * who is alone.
127
+ */
128
+ startSync(probe: (token: string) => void): void;
129
+ /**
130
+ * Feed back a server-stamped reply: `serverAt` is the server's own clock reading for the event it
131
+ * broadcast, and `from` is the member the server says scheduled it.
132
+ *
133
+ * Ignores anything that is not this member's own outstanding probe — another member's sync, a
134
+ * replay, or a stale token after {@link dispose}.
135
+ */
136
+ applyStamp(token: string, serverAt: number, from: string): void;
137
+ /** Epoch ms on the shared clock. Local time until a sample lands. */
138
+ serverNow(): number;
139
+ /** True once a round trip has landed — i.e. the clock is server-aligned, not merely local. */
140
+ get synced(): boolean;
141
+ /**
142
+ * Round-trip time (ms) from the best sample of the latest completed round, or null before the
143
+ * first one lands (and forever for a member with no transport, who has nothing to measure).
144
+ *
145
+ * Half of it is the one-way estimate — that is the assumption the offset is already built on, so
146
+ * nothing downstream may assume a better one.
147
+ */
148
+ get rttMs(): number | null;
149
+ /** Stop pending round trips and the repeating round. */
150
+ dispose(): void;
151
+ }
152
+ //# sourceMappingURL=SharedClock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SharedClock.d.ts","sourceRoot":"","sources":["../../src/net/SharedClock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,iFAAiF;AACjF,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC,mGAAmG;AACnG,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,QAAS,CAAC;AAExC;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAChD,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACpC,WAAW,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IACjD,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0FAA0F;IAC1F,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iEAAiE;IACjE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,yEAAyE;IACzE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AASD;;;;;;;GAOG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IAErC,+FAA+F;IAC/F,OAAO,CAAC,MAAM,CAAK;IACnB;;;OAGG;IACH,OAAO,CAAC,YAAY,CAAY;IAChC,qFAAqF;IACrF,OAAO,CAAC,YAAY,CAAY;IAChC,uFAAuF;IACvF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,SAAS,CAAK;IACtB,sFAAsF;IACtF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0C;IACtE,OAAO,CAAC,QAAQ,CAAS;gBAEb,OAAO,EAAE,kBAAkB;IASvC;;;;;;;OAOG;IACH,SAAS,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAyB/C;;;;;;OAMG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAgB/D,qEAAqE;IACrE,SAAS,IAAI,MAAM;IAInB,8FAA8F;IAC9F,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;;;;;OAMG;IACH,IAAI,KAAK,IAAI,MAAM,GAAG,IAAI,CAEzB;IAED,wDAAwD;IACxD,OAAO,IAAI,IAAI;CAQhB"}
@@ -0,0 +1,195 @@
1
+ /**
2
+ * THE SHARED CLOCK — agreement on what time it is, for everyone in a room (PLAT-1456).
3
+ *
4
+ * Lifted from Racing's `LiveClock` (RACI-131/213), which proved the design over a season of real
5
+ * races, and made transport-neutral so the shell can run one too. It is the whole of what a
6
+ * synchronised experience needs from the network: derive a timetable from a timestamp, and the only
7
+ * thing left to agree is the timestamp.
8
+ *
9
+ * offset = serverAt + rtt / 2 - now (rtt/2 ≈ the return leg)
10
+ *
11
+ * Several samples are taken and the one measured through the LOWEST latency wins — the standard
12
+ * NTP-ish trick, because a sample delayed by a slow return leg is the one whose `rtt/2` estimate is
13
+ * least trustworthy.
14
+ *
15
+ * **With no transport at all the offset stays 0 and {@link SharedClock.serverNow} is local time.**
16
+ * That is deliberate and load-bearing: a rider alone in a room, a standalone dev session and the
17
+ * SDK's loopback room must all run the same code path as a full field, because most of the time
18
+ * they will be the case.
19
+ *
20
+ * ## It re-syncs, and it reports what it measured
21
+ *
22
+ * Measurement continues for the life of the clock, in ROUNDS of {@link DEFAULT_SAMPLES} spaced
23
+ * {@link DEFAULT_RESYNC_MS} apart, and each round adopts its OWN best sample. Racing originally took
24
+ * three samples at boot and kept the lowest forever, which quietly made two things impossible: a
25
+ * connection that changed route mid-session could never correct its offset (the old "best" was
26
+ * unbeatable), and nothing downstream could know how bad the link was. Both matter once latency is a
27
+ * gameplay number rather than only a countdown's accuracy.
28
+ *
29
+ * ## A stamp is only yours if the server says it is
30
+ *
31
+ * Orchestration events are **broadcast to the whole room**, so every member sees every other
32
+ * member's probe reply. Racing's two wiring sites both fed those replies straight into
33
+ * `applyStamp` on the strength of the token alone (`RoomSession.ts`, `seriesRoom.ts`) — and tokens
34
+ * were per-client sequential (`s0`, `s1`, …), so two riders syncing at once would each apply the
35
+ * other's server stamp against their own send time and corrupt their offset. Nothing surfaced it:
36
+ * the error is a plausible-looking number.
37
+ *
38
+ * Fixed here twice over, because this is the kind of bug that comes back:
39
+ *
40
+ * 1. {@link SharedClock.applyStamp} takes `from` and **ignores any stamp not sent by `selfId`**.
41
+ * `RoomEvent.from` is stamped by the server and cannot be forged by a peer.
42
+ * 2. Tokens carry a per-instance random prefix, so even an unguarded caller cannot collide.
43
+ *
44
+ * Skew shows up as a mistimed transition, never as corrupted measurement: whatever the clock is
45
+ * used to schedule is timed locally. Worst case a badly-skewed member acts a fraction late.
46
+ */
47
+ /** How many round trips one sync ROUND measures before settling on an offset. */
48
+ export const DEFAULT_SAMPLES = 3;
49
+ /** Gap between round trips within a round (ms) — spread so one congested moment can't skew all. */
50
+ export const DEFAULT_SAMPLE_INTERVAL_MS = 400;
51
+ /**
52
+ * Gap between sync ROUNDS (ms). Measurement continues for the whole session: a member's route can
53
+ * change mid-session (wifi to cellular, a colo failover), and both the offset and the reported
54
+ * {@link SharedClock.rttMs} have to follow it.
55
+ *
56
+ * Far cheaper than it looks — three ~60-byte events a round — and it bounds how long a stale
57
+ * estimate can survive.
58
+ */
59
+ export const DEFAULT_RESYNC_MS = 30_000;
60
+ const globalTimers = {
61
+ setTimeout: (fn, ms) => setTimeout(fn, ms),
62
+ clearTimeout: (h) => clearTimeout(h),
63
+ setInterval: (fn, ms) => setInterval(fn, ms),
64
+ clearInterval: (h) => clearInterval(h),
65
+ };
66
+ /**
67
+ * Tracks the offset between this machine's clock and the room server's, and reports
68
+ * {@link serverNow}.
69
+ *
70
+ * **ONE PER ROOM SESSION, not one per activity.** The offset is measured against the room server, so
71
+ * it is a property of the room; a clock rebuilt for each activity reports raw local time for its
72
+ * first round trip after every transition, which is exactly when it is most relied upon.
73
+ */
74
+ export class SharedClock {
75
+ selfId;
76
+ samples;
77
+ sampleIntervalMs;
78
+ resyncIntervalMs;
79
+ now;
80
+ timers;
81
+ /** ms to add to local time to land on server time. 0 until a sample lands (and when alone). */
82
+ offset = 0;
83
+ /**
84
+ * Best latency measured in the CURRENT round — reset when a round opens, so each round adopts its
85
+ * own fastest sample rather than being locked out by an old one.
86
+ */
87
+ roundBestRtt = Infinity;
88
+ /** Best latency of the last COMPLETED round, which is what {@link rttMs} reports. */
89
+ lastRoundRtt = Infinity;
90
+ /** Local send times of round trips still in flight, keyed by the token echoed back. */
91
+ inFlight = new Map();
92
+ pending = [];
93
+ roundTimer = null;
94
+ nextToken = 0;
95
+ /** Per-instance token prefix — belt to the `from` guard's braces (see the header). */
96
+ tokenPrefix = Math.random().toString(36).slice(2, 8);
97
+ disposed = false;
98
+ constructor(options) {
99
+ this.selfId = options.selfId;
100
+ this.samples = options.samples ?? DEFAULT_SAMPLES;
101
+ this.sampleIntervalMs = options.sampleIntervalMs ?? DEFAULT_SAMPLE_INTERVAL_MS;
102
+ this.resyncIntervalMs = options.resyncIntervalMs ?? DEFAULT_RESYNC_MS;
103
+ this.now = options.now ?? Date.now;
104
+ this.timers = options.timers ?? globalTimers;
105
+ }
106
+ /**
107
+ * Start measuring against the server. `probe` is handed a token to round-trip; whatever transport
108
+ * carries it must feed the reply back through {@link applyStamp} with the same token and the
109
+ * server's `from`.
110
+ *
111
+ * Safe to never call — the clock then reports local time, which is the correct answer for a member
112
+ * who is alone.
113
+ */
114
+ startSync(probe) {
115
+ const round = () => {
116
+ if (this.disposed)
117
+ return;
118
+ // Close the round that just ran before opening the next, so `rttMs` always reports a COMPLETE
119
+ // round rather than the first sample of the one in progress.
120
+ if (this.roundBestRtt < Infinity)
121
+ this.lastRoundRtt = this.roundBestRtt;
122
+ this.roundBestRtt = Infinity;
123
+ // Last round's timers have all fired (a round is ~1.2s, the gap 30s), so the array is dead
124
+ // weight — dropped here rather than grown for the length of the session.
125
+ this.pending = [];
126
+ for (let i = 0; i < this.samples; i++) {
127
+ this.pending.push(this.timers.setTimeout(() => {
128
+ if (this.disposed)
129
+ return;
130
+ const token = `${this.tokenPrefix}-${this.nextToken++}`;
131
+ this.inFlight.set(token, this.now());
132
+ probe(token);
133
+ }, i * this.sampleIntervalMs));
134
+ }
135
+ };
136
+ round();
137
+ this.roundTimer = this.timers.setInterval(round, this.resyncIntervalMs);
138
+ }
139
+ /**
140
+ * Feed back a server-stamped reply: `serverAt` is the server's own clock reading for the event it
141
+ * broadcast, and `from` is the member the server says scheduled it.
142
+ *
143
+ * Ignores anything that is not this member's own outstanding probe — another member's sync, a
144
+ * replay, or a stale token after {@link dispose}.
145
+ */
146
+ applyStamp(token, serverAt, from) {
147
+ if (from !== this.selfId)
148
+ return; // somebody else's round trip, broadcast to us
149
+ const sentAt = this.inFlight.get(token);
150
+ if (sentAt === undefined)
151
+ return;
152
+ this.inFlight.delete(token);
153
+ const now = this.now();
154
+ const rtt = now - sentAt;
155
+ // A slower sample tells us less than the one we already have THIS ROUND. Across rounds it wins
156
+ // outright, which is what lets a changed route correct an offset that used to be unbeatable.
157
+ if (rtt >= this.roundBestRtt)
158
+ return;
159
+ this.roundBestRtt = rtt;
160
+ // Seed the report from the first round too, so a readout says something before a round closes.
161
+ if (this.lastRoundRtt === Infinity)
162
+ this.lastRoundRtt = rtt;
163
+ this.offset = serverAt + rtt / 2 - now;
164
+ }
165
+ /** Epoch ms on the shared clock. Local time until a sample lands. */
166
+ serverNow() {
167
+ return this.now() + this.offset;
168
+ }
169
+ /** True once a round trip has landed — i.e. the clock is server-aligned, not merely local. */
170
+ get synced() {
171
+ return this.lastRoundRtt < Infinity;
172
+ }
173
+ /**
174
+ * Round-trip time (ms) from the best sample of the latest completed round, or null before the
175
+ * first one lands (and forever for a member with no transport, who has nothing to measure).
176
+ *
177
+ * Half of it is the one-way estimate — that is the assumption the offset is already built on, so
178
+ * nothing downstream may assume a better one.
179
+ */
180
+ get rttMs() {
181
+ return this.lastRoundRtt < Infinity ? this.lastRoundRtt : null;
182
+ }
183
+ /** Stop pending round trips and the repeating round. */
184
+ dispose() {
185
+ this.disposed = true;
186
+ for (const t of this.pending)
187
+ this.timers.clearTimeout(t);
188
+ this.pending = [];
189
+ if (this.roundTimer !== null)
190
+ this.timers.clearInterval(this.roundTimer);
191
+ this.roundTimer = null;
192
+ this.inFlight.clear();
193
+ }
194
+ }
195
+ //# sourceMappingURL=SharedClock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SharedClock.js","sourceRoot":"","sources":["../../src/net/SharedClock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * `@rydr/game-sdk/net` — the shared pieces of talking to a room, owned once (PLAT-1456).
3
+ *
4
+ * Racing and Party each built these independently, and the shell now needs them too, which is the
5
+ * third time and the reason they moved here. What lives in this module is only what is genuinely
6
+ * generic: **agreement on the time**. Anything that knows what a lap, a round or a workout block is
7
+ * belongs to its own consumer.
8
+ *
9
+ * The binder below matters as much as the clock does. Both of Racing's wiring sites got the same
10
+ * detail wrong in the same way — applying any member's broadcast probe reply as if it were their own
11
+ * — so the wiring is published here rather than left as an example to copy.
12
+ */
13
+ export { SharedClock, DEFAULT_SAMPLES, DEFAULT_SAMPLE_INTERVAL_MS, DEFAULT_RESYNC_MS, type ClockTimers, type SharedClockOptions, } from "./SharedClock.js";
14
+ import { SharedClock, type SharedClockOptions } from "./SharedClock.js";
15
+ /**
16
+ * The event name the clock's probes ride on. Exported so a consumer that fans room events out to its
17
+ * own handlers can recognise and swallow them — a sync probe is plumbing, not an orchestration event
18
+ * anybody else should see.
19
+ */
20
+ export declare const CLOCK_EVENT = "rydr/clock";
21
+ /**
22
+ * The slice of a room the clock needs: schedule a server-stamped event, and hear them back.
23
+ *
24
+ * Deliberately structural rather than `RoomHandle`, so the SHELL's own room object satisfies it too.
25
+ * The shell holds a socket and a guest holds a relay handle; neither difference reaches this far.
26
+ */
27
+ export interface ClockRoom {
28
+ scheduleEvent(name: string, payload?: unknown, at?: number): void;
29
+ on(event: "event", cb: (e: {
30
+ name: string;
31
+ payload: unknown;
32
+ at: number;
33
+ from: string;
34
+ }) => void): () => void;
35
+ }
36
+ /** A bound clock, plus the unsubscribe that detaches it from the room. */
37
+ export interface BoundClock {
38
+ clock: SharedClock;
39
+ /** Detach from the room and stop measuring. Idempotent. */
40
+ dispose(): void;
41
+ }
42
+ /**
43
+ * Bind a {@link SharedClock} to a room and start it measuring.
44
+ *
45
+ * One per room session — see the note on {@link SharedClock}. `selfId` must be the id the room knows
46
+ * this member by, because it is what distinguishes this member's probe replies from the broadcast
47
+ * copies of everybody else's.
48
+ */
49
+ export declare function bindRoomClock(room: ClockRoom, options: SharedClockOptions): BoundClock;
50
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/net/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,WAAW,EACX,eAAe,EACf,0BAA0B,EAC1B,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,kBAAkB,GACxB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAExE;;;;GAIG;AACH,eAAO,MAAM,WAAW,eAAe,CAAC;AAExC;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClE,EAAE,CACA,KAAK,EAAE,OAAO,EACd,EAAE,EAAE,CAAC,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAC5E,MAAM,IAAI,CAAC;CACf;AAED,0EAA0E;AAC1E,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,WAAW,CAAC;IACnB,2DAA2D;IAC3D,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,kBAAkB,GAC1B,UAAU,CAkBZ"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * `@rydr/game-sdk/net` — the shared pieces of talking to a room, owned once (PLAT-1456).
3
+ *
4
+ * Racing and Party each built these independently, and the shell now needs them too, which is the
5
+ * third time and the reason they moved here. What lives in this module is only what is genuinely
6
+ * generic: **agreement on the time**. Anything that knows what a lap, a round or a workout block is
7
+ * belongs to its own consumer.
8
+ *
9
+ * The binder below matters as much as the clock does. Both of Racing's wiring sites got the same
10
+ * detail wrong in the same way — applying any member's broadcast probe reply as if it were their own
11
+ * — so the wiring is published here rather than left as an example to copy.
12
+ */
13
+ export { SharedClock, DEFAULT_SAMPLES, DEFAULT_SAMPLE_INTERVAL_MS, DEFAULT_RESYNC_MS, } from "./SharedClock.js";
14
+ import { SharedClock } from "./SharedClock.js";
15
+ /**
16
+ * The event name the clock's probes ride on. Exported so a consumer that fans room events out to its
17
+ * own handlers can recognise and swallow them — a sync probe is plumbing, not an orchestration event
18
+ * anybody else should see.
19
+ */
20
+ export const CLOCK_EVENT = "rydr/clock";
21
+ /**
22
+ * Bind a {@link SharedClock} to a room and start it measuring.
23
+ *
24
+ * One per room session — see the note on {@link SharedClock}. `selfId` must be the id the room knows
25
+ * this member by, because it is what distinguishes this member's probe replies from the broadcast
26
+ * copies of everybody else's.
27
+ */
28
+ export function bindRoomClock(room, options) {
29
+ const clock = new SharedClock(options);
30
+ const off = room.on("event", (e) => {
31
+ if (e.name !== CLOCK_EVENT)
32
+ return;
33
+ if (typeof e.payload !== "string")
34
+ return;
35
+ clock.applyStamp(e.payload, e.at, e.from);
36
+ });
37
+ clock.startSync((token) => room.scheduleEvent(CLOCK_EVENT, token));
38
+ let disposed = false;
39
+ return {
40
+ clock,
41
+ dispose() {
42
+ if (disposed)
43
+ return;
44
+ disposed = true;
45
+ off();
46
+ clock.dispose();
47
+ },
48
+ };
49
+ }
50
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/net/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,WAAW,EACX,eAAe,EACf,0BAA0B,EAC1B,iBAAiB,GAGlB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,WAAW,EAA2B,MAAM,kBAAkB,CAAC;AAExE;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC;AAuBxC;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAC3B,IAAe,EACf,OAA2B;IAE3B,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;QACjC,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW;YAAE,OAAO;QACnC,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ;YAAE,OAAO;QAC1C,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IACH,KAAK,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC;IACnE,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,OAAO;QACL,KAAK;QACL,OAAO;YACL,IAAI,QAAQ;gBAAE,OAAO;YACrB,QAAQ,GAAG,IAAI,CAAC;YAChB,GAAG,EAAE,CAAC;YACN,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -9,5 +9,5 @@
9
9
  export declare const RYDR_PROTOCOL_VERSION: 37;
10
10
  /** Semver of this SDK build. Sent in the handshake for telemetry/debugging.
11
11
  * (Bumped to 2.0.0 by `npm version major` on release — see CHANGELOG [Unreleased].) */
12
- export declare const RYDR_SDK_VERSION = "9.3.0";
12
+ export declare const RYDR_SDK_VERSION = "9.4.0";
13
13
  //# sourceMappingURL=version.d.ts.map
@@ -334,5 +334,5 @@
334
334
  export const RYDR_PROTOCOL_VERSION = 37;
335
335
  /** Semver of this SDK build. Sent in the handshake for telemetry/debugging.
336
336
  * (Bumped to 2.0.0 by `npm version major` on release — see CHANGELOG [Unreleased].) */
337
- export const RYDR_SDK_VERSION = "9.3.0";
337
+ export const RYDR_SDK_VERSION = "9.4.0";
338
338
  //# sourceMappingURL=version.js.map
@@ -0,0 +1,291 @@
1
+ # Multiplayer workouts — what your game has to do
2
+
3
+ > **For game authors.** You are adapting a game so it can be played inside a workout that a room of
4
+ > riders is doing together. This is the guest-facing half; the shell-side design lives in the
5
+ > platform repo and you never need to read it.
6
+
7
+ ## The shape of it
8
+
9
+ A room of riders rides **one workout, together, on one clock**. Alongside it the room holds an
10
+ ordered list of games, and the **host cycles through them** from the platform's OPTIONS menu. Your
11
+ game is one entry in that rotation: when the host reaches it, you are the thing on screen, until they
12
+ move on.
13
+
14
+ ```
15
+ workout 20' warm-up │ 3 × 13 × 30/15 … 39 efforts … │ 20' cool-down
16
+ │ │
17
+ games ── GAME A ───┴──── GAME B ──────┴─── GAME C ───┴──── (cycles) ──▶
18
+ ▲ ▲ ▲
19
+ host presses NEXT GAME whenever they like
20
+ ```
21
+
22
+ **The two are independent.** A game change can land anywhere — between sets, mid-effort, halfway
23
+ through the warm-up. The workout does not pause for it and the rotation does not wait for a boundary.
24
+
25
+ The platform owns the workout, the clock, the trainer and the room. **You own what is on screen.**
26
+
27
+ ## Three rules, and everything else follows from them
28
+
29
+ ### 1. You do not own the clock
30
+
31
+ Position comes from `session.training`, which is authoritative and identical for every rider in the
32
+ room. Never run your own timer, never derive elapsed time from when your iframe
33
+ mounted, and never assume your `requestAnimationFrame` count means anything.
34
+
35
+ ```ts
36
+ session.training.subscribe((snapshot) => {
37
+ const w = snapshot.workout;
38
+ if (!w) return; // no workout attached — no group session is running
39
+ const { position, targetPower, segment } = w.progress;
40
+ });
41
+ ```
42
+
43
+ A game that keeps its own clock desynchronises from the rest of the room within a minute, and the
44
+ riders see it long before you do.
45
+
46
+ > **Two different things are called "phase", and mixing them up is the first bug everyone writes.**
47
+ > `progress.phase` is the ENGINE's state — `"active"`, paused, complete — i.e. whether the workout is
48
+ > running at all. The work/recovery rhythm is `progress.segment.type`. You almost always want the
49
+ > second one.
50
+
51
+ ### 2. You do not own power
52
+
53
+ The workout prescribes watts and the platform holds the trainer there in ERG. **Do not call
54
+ `setDemand`** — a workout target outranks it, so the call does nothing except make your code lie
55
+ about what is happening.
56
+
57
+ This is the part that decides whether your game is a good fit at all. If your game's controller *is*
58
+ the pedals — go faster by pushing harder — then the workout has already taken your controller
59
+ away: everyone is held at the same target, so everyone performs identically. **A game suited to a
60
+ workout takes its input from the gamepad**: aim, steer, time a press, hit a note. Power is
61
+ spoken for.
62
+
63
+ You still *read* power (`session.hardware`) — actual measured watts, which is what the rider really
64
+ did and how well they held the target. Read it to score adherence, not to move the player.
65
+
66
+ ### 3. You do not decide when your turn ends
67
+
68
+ The **host** does, from the OPTIONS menu, and you get no warning. If your song finishes or your wave
69
+ is cleared while you are still on screen, **show something until they move on** — results, a summary,
70
+ an idle loop. Reporting a stage as finished advances nothing in a group workout.
71
+
72
+ Equally, you can be taken off mid-anything. Stop cleanly, and score only what the rider actually did
73
+ while you were up — never invent a result for the part that did not happen.
74
+
75
+ ## The new thing: position is not monotonic
76
+
77
+ **The host has admin powers over the running workout** — skip a segment, restart a segment, pause,
78
+ resume — from the platform's OPTIONS menu. These are shell-owned controls; your game neither draws
79
+ them nor receives them as commands.
80
+
81
+ What reaches you is simply the next `session.training` snapshot, **with `position` somewhere you did
82
+ not expect**:
83
+
84
+ | Host does | What you see |
85
+ |---|---|
86
+ | Skip segment | `position` jumps **forward**, `segment` changes |
87
+ | Restart segment | `position` jumps **backward** to the segment start |
88
+ | Pause / resume | `position` stops advancing, then resumes |
89
+
90
+ So the one real requirement this contract adds: **handle a discontinuous position, in both
91
+ directions, at any moment.** Concretely —
92
+
93
+ - Derive your state *from* `position` each tick rather than accumulating it. Anything you integrate
94
+ (a score, a distance, a spawn schedule) needs an explicit answer for "the clock went backwards".
95
+ - Do not replay cues you already fired just because time moved back over them, and do not skip the
96
+ ones you jumped past as though the rider earned them.
97
+ - Re-read `segment` on every snapshot. A skip changes which effort is running, and the workout may
98
+ simply be over.
99
+
100
+ A game that assumes time only moves forward looks fine for a whole session and then breaks the first
101
+ time a host restarts a set — which is exactly when a room full of people is watching.
102
+
103
+ ## The v1 session, hard-coded
104
+
105
+ v1 ships **one** workout. It is not picked from a library and not editable; it is this, so that
106
+ everybody adapting a game is aiming at the same session.
107
+
108
+ **3 × 13 × 30/15, with a 20-minute warm-up and a 20-minute cool-down.** A Rønnestad session: three
109
+ sets of thirteen 30-second efforts, 15 seconds easy between them, 3 minutes between sets.
110
+
111
+ ```ts
112
+ import type { Workout } from "@rydr/game-sdk";
113
+
114
+ const SESSION: Workout = {
115
+ id: "group-ronnestad-3x13",
116
+ name: "3 × 13 × 30/15",
117
+ description: "Rønnestad intervals with a 20' warm-up and 20' cool-down.",
118
+ category: "aerobicPower",
119
+ createdAt: 0,
120
+ updatedAt: 0,
121
+ segments: [
122
+ { id: "warmup", type: "ramp", duration: 1200, startPower: 50, endPower: 75 },
123
+ {
124
+ id: "main", type: "intervals",
125
+ sets: 3, reps: 13,
126
+ workDuration: 30, workPower: 120,
127
+ recoveryDuration: 15, recoveryPower: 50,
128
+ restBetweenSets: 180, restPower: 45,
129
+ },
130
+ { id: "cooldown", type: "ramp", duration: 1200, startPower: 60, endPower: 40 },
131
+ ],
132
+ };
133
+ ```
134
+
135
+ Which compiles to **75:15** total — 20:00 + (3 × 13 × 45s = 29:15) + (2 × 3:00 rest) + 20:00. Rest
136
+ goes *between* sets and never after the last one, so three sets means two rests.
137
+
138
+ **Games are assigned to none of this.** The segments above shape the *effort* — what the trainer
139
+ holds and what `segment.type` reports — and nothing else. Which game is on screen while a given
140
+ segment plays is the host's live choice, not a property of the workout.
141
+
142
+ ### Games are a rotation, not a block assignment
143
+
144
+ **Games are not tied to the workout's structure at all.** The room holds an ordered list of games and
145
+ a current index; the **host advances it** from the platform's OPTIONS menu, whenever they like, and
146
+ the list cycles. Three games and a 75-minute session means the host decides when each gets its turn.
147
+
148
+ So your game does **not** own a block, a set or a segment. It owns *the screen, until the host moves
149
+ on.* It might be up for one effort or for forty minutes.
150
+
151
+ Two things follow, and the second is the one that will bite you:
152
+
153
+ **You can be mounted at ANY workout position.** Not at a block boundary — at second 2347, fourteen
154
+ seconds into a 30-second effort, with 16 seconds left on it. Render correctly from a cold start at an
155
+ arbitrary position: read `position`, `segment`, `segmentRemaining` and `targetPower` on your very
156
+ first snapshot and draw the truth immediately. Never animate in from zero, never assume you saw the
157
+ effort begin, never wait for the next boundary to start being useful.
158
+
159
+ **You can be unmounted at any position too**, mid-effort included. Stop cleanly and score only what
160
+ the rider actually did while you were on screen.
161
+
162
+ ```ts
163
+ const p = snapshot.workout?.progress;
164
+ // What the rider is doing right now. NOT `p.phase` — that is the engine's running/paused state.
165
+ const effort = p?.segment?.type; // "work" | "recovery" | "rest" | "steady"
166
+ const set = p?.set; // 1-based, 1..3 — and 0 outside an interval set
167
+ const rep = p?.rep; // 1-based, 1..13 — and 0 outside an interval set
168
+ const watts = p?.targetPower; // what THIS rider should be pushing, watts
169
+ const left = p?.segmentRemaining; // seconds left in this effort — your countdown
170
+ ```
171
+
172
+ If your game wants a beat, `phase` is it. Thirty seconds hard, fifteen easy, thirteen times — that
173
+ rhythm is the session, and a game that ignores it is just wallpaper over a workout.
174
+
175
+ ## Per-rider watts, shared everything else
176
+
177
+ Every rider rides the same effort at the same %FTP, which is a **different number of watts** for each
178
+ of them. That is the point, and it is already handled: the compiler scales against each rider's own
179
+ FTP, so `targetPower` in your snapshot is *this* rider's target, in watts.
180
+
181
+ **So never send watts to other riders as though they were comparable**, and never rank a room by raw
182
+ power. What is shared is the workout and the position. What is personal is the wattage and how well
183
+ they held it — and the comparable number is how far ABOVE their own target each one is, in watts.
184
+ See "Compare the watts a rider is OVER their own target" below.
185
+
186
+ ## Who else is riding — and why you must not ask which are real
187
+
188
+ > **Not shipped yet (PLAT-1667).** The shape below is agreed; the host does not populate it in the
189
+ > current release. Design your rendering against it, but check the field exists before you read it.
190
+
191
+ A workout is ridden by a **room** — and a room is never empty, because a rider alone is given a
192
+ **bot pacer** doing the same session. Solo and group are the same path with a different number of
193
+ participants, so your game must never have a "nobody else is here" branch.
194
+
195
+ **`participants` are the REAL riders. The pacer is yours to make** (Ben's call, 2026-09-22.) An
196
+ earlier draft had the host derive bots and deliver them in this list so that no game had to. That
197
+ seam was wrong: drawing a bot is not a detail of a bot, it *is* the game — a pacer is a rigged
198
+ cyclist on a road in one game and an opponent in a duel in the next, and once the game-specific part
199
+ is removed there is no rendering left for anyone else to own.
200
+
201
+ So the platform gives you the workout, the shared position and the humans. **Filling an empty seat is
202
+ your job**, and it is not optional: a room of one must still be a race. Two rules survive from when
203
+ this was the host's problem, because they were never about rendering —
204
+
205
+ - **A bot is derived, never a member.** Compute it from the frozen workout, the shared position and a
206
+ seed; do not synthesise one into the roster, give it a `playerId`, or put it on the wire.
207
+ - **A bot is not a person in any record.** Never write one into a saved result as an opponent, and
208
+ never let one enter a leaderboard.
209
+
210
+ ```ts
211
+ const riders = snapshot.workout?.participants ?? [];
212
+
213
+ for (const r of riders) {
214
+ // Compare the watts they are OVER their own target — not compliance, not raw watts.
215
+ draw(r.name, r.power - r.targetPower); // r.isBot changes NOTHING about how you draw them
216
+ }
217
+ ```
218
+
219
+ **Draw your bots exactly as you draw humans.** Whatever you use to mark one — the `isBot` flag on a
220
+ participant, or simply knowing you made it yourself — must change nothing about the geometry. A game
221
+ that renders its pacer differently shows a solo rider something that looks broken, which is precisely
222
+ the case bots exist to fix. Marking a bot is for wording ("you beat Astrid" vs "you beat the pacer")
223
+ and for keeping it out of leaderboards, never for how it is drawn.
224
+
225
+ ### There is one position, and it belongs to everybody
226
+
227
+ Participants do **not** each carry a position. The workout's `progress.position` is shared — that is
228
+ what makes it a group workout. Everyone is in the same effort, at the same second, always.
229
+
230
+ So the thing that differs between riders, and the only thing worth drawing a gap from, is **how well
231
+ each is holding the target**.
232
+
233
+ ### Compare the watts a rider is OVER their own target
234
+
235
+ **Ben's call, 2026-09-22 (PLAT-1668).** This reverses what this section used to say. If you find a
236
+ comment, a plan or a game arguing for compliance, this paragraph wins.
237
+
238
+ There are three different numbers here and only two of them used to be written down:
239
+
240
+ | Field | Comparable across riders? |
241
+ |---|---|
242
+ | `power - targetPower` | **Yes — this is your comparison.** The watts a rider is above or below their own prescription. |
243
+ | `compliance` (`power / targetPower`) | Yes, but it is not what we use. See below. |
244
+ | `power` | **No.** Their raw watts, meaningful only against their own FTP. |
245
+ | `targetPower` | No. Scaled to their FTP. |
246
+
247
+ Raw `power` is still wrong and for the reason it always was: rank by it and you have built a
248
+ heaviest-rider-wins leaderboard, which is the thing structured training exists to avoid. **That
249
+ warning does not apply to the delta.** `power - targetPower` is already personal to each rider —
250
+ each is measured against their own scaled target — it simply normalises by *subtracting* the target
251
+ rather than by *dividing* by it.
252
+
253
+ So a rider at 200 W FTP pushing +30 W and a rider at 400 W FTP pushing +30 W are a **dead heat**.
254
+ Compliance would have called that 115 % against 107.5 % and given it to the first rider.
255
+
256
+ **Why additive and not multiplicative.** Two reasons, and the second is the one that decided it:
257
+
258
+ - **Watts are the thing being contested.** In a game where riders push against each other, 30 W of
259
+ extra work is 30 W whoever produced it. A percentage is a ratio of prescriptions, not an amount of
260
+ work.
261
+ - **A rider can learn one number.** "I need +15 W" is true in every set, at every target, all
262
+ session — so it can be learned once and acted on at threshold, where nobody is doing arithmetic.
263
+ Under compliance the watts you must find change with every block, and the rider is left computing
264
+ their own target all session.
265
+
266
+ **The tradeoff this accepts, stated plainly:** +30 W is a harder ask of a 150 W rider than of a
267
+ 400 W rider, so the smaller rider is doing more relative work for the same result. That is known and
268
+ accepted — it is the same tradeoff a flat safe-zone band already makes, and it is the price of a
269
+ number a rider can hold in their head.
270
+
271
+ ### A bot is derived, not connected
272
+
273
+ Bots are computed from the workout, the shared clock and a seed — identical on every client, with no
274
+ network traffic. Practically, for you: a bot never disconnects, never lags, never needs
275
+ interpolating, and its numbers are as trustworthy as your own. You do not need the netcode you would
276
+ need for a human rival.
277
+
278
+ ## Checklist
279
+
280
+ - [ ] Reads position from `session.training`, never from a local timer
281
+ - [ ] Survives `position` jumping forward *and* backward, at any moment
282
+ - [ ] Re-reads `segment` every snapshot; notices when the effort changed
283
+ - [ ] Never calls `setDemand`
284
+ - [ ] Renders correctly when mounted at an arbitrary position, mid-effort included
285
+ - [ ] Fills its turn for as long as the host leaves it up, and stops cleanly when taken off
286
+ - [ ] Scores from actual measured watts, not from the prescribed target
287
+ - [ ] Takes its gameplay input from the gamepad, not the pedals
288
+ - [ ] Draws bots and humans identically — however you mark one, it must not reach the geometry
289
+ - [ ] **Makes its own pacer**, so a room of one is still a race — `participants` carries only humans
290
+ - [ ] Keeps its bots off the wire and out of results and leaderboards
291
+ - [ ] Ranks and compares riders by `power - targetPower`, never by raw watts (PLAT-1668)
@@ -1 +1 @@
1
- {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/training/compile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAGrE;;;;GAIG;AACH,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAM5C;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEtE;AA6CD,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,SAAS,CAAC;IAChB,eAAe;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,YAAY,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wCAAwC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sCAAsC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAExC,MAAM,WAAW,cAAc;IAC7B,gFAAgF;IAChF,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,cAAc,GACvB,WAAW,EAAE,CAoHf;AAoDD,4CAA4C;AAC5C,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CAEzE;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,OAAO,EAAE,MAAM,GACd,WAAW,GAAG,IAAI,CAWpB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAIvF;AAED,wFAAwF;AACxF,wBAAgB,aAAa,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAgBvE;AAMD,MAAM,WAAW,cAAc;IAC7B,eAAe;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,gBAAgB,EAAE,MAAM,CAAC;IACzB,2CAA2C;IAC3C,aAAa,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,eAAe,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,eAAe,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,EAClC,GAAG,EAAE,MAAM,GACV,cAAc,CAwEhB"}
1
+ {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/training/compile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAGrE;;;;GAIG;AACH,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAM5C;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEtE;AA6CD,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,SAAS,CAAC;IAChB,eAAe;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,YAAY,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wCAAwC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sCAAsC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAExC,MAAM,WAAW,cAAc;IAC7B,gFAAgF;IAChF,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,cAAc,GACvB,WAAW,EAAE,CAoHf;AA+DD,4CAA4C;AAC5C,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CAEzE;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,OAAO,EAAE,MAAM,GACd,WAAW,GAAG,IAAI,CAWpB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAIvF;AAED,wFAAwF;AACxF,wBAAgB,aAAa,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAgBvE;AAMD,MAAM,WAAW,cAAc;IAC7B,eAAe;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,gBAAgB,EAAE,MAAM,CAAC;IACzB,2CAA2C;IAC3C,aAAa,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,eAAe,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,eAAe,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,EAClC,GAAG,EAAE,MAAM,GACV,cAAc,CAwEhB"}
@@ -199,10 +199,20 @@ export function compileWorkout(segments, ftp, options) {
199
199
  function numberReps(flat) {
200
200
  let set = 1;
201
201
  let rep = 0;
202
+ /**
203
+ * Sets already closed by a rest that no interval set emitted.
204
+ *
205
+ * An interval set numbers from 1 because it only knows about itself. Author a session as several
206
+ * one-set segments with a rest between them — which is how you give each set its own game on the
207
+ * timeline — and adopting those numbers verbatim labels every set "set 1". Carrying the offset is
208
+ * what makes that authoring compile identically to the single three-set segment it replaces.
209
+ */
210
+ let closedBefore = 0;
202
211
  for (const entry of flat) {
203
212
  if (entry.set !== undefined) {
204
- // Authored by an interval set: adopt its numbering rather than imposing one.
205
- set = entry.set;
213
+ // Authored by an interval set: adopt its numbering, shifted past any set already closed.
214
+ set = entry.set + closedBefore;
215
+ entry.set = set;
206
216
  rep = entry.rep ?? rep;
207
217
  // A rest emitted by a set closes it, exactly as it does below.
208
218
  if (entry.type === "rest") {
@@ -227,6 +237,7 @@ function numberReps(flat) {
227
237
  entry.set = set;
228
238
  set += 1;
229
239
  rep = 0;
240
+ closedBefore += 1;
230
241
  }
231
242
  }
232
243
  }
@@ -1 +1 @@
1
- {"version":3,"file":"compile.js","sourceRoot":"","sources":["../../src/training/compile.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compile.js","sourceRoot":"","sources":["../../src/training/compile.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rydr/game-sdk",
3
- "version": "9.3.0",
3
+ "version": "9.4.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/bdefrenne/rydr-game-sdk.git"
@@ -63,6 +63,10 @@
63
63
  "./ui/showcase": {
64
64
  "types": "./dist/ui/showcase/index.d.ts",
65
65
  "import": "./dist/ui/showcase/index.js"
66
+ },
67
+ "./net": {
68
+ "types": "./dist/net/index.d.ts",
69
+ "import": "./dist/net/index.js"
66
70
  }
67
71
  },
68
72
  "main": "./dist/index.js",
@@ -73,7 +77,7 @@
73
77
  "scripts": {
74
78
  "build": "npm run check:css && tsc -p tsconfig.json && npm run copy:docs",
75
79
  "prepare": "npm run check:css && tsc -p tsconfig.json && npm run copy:docs",
76
- "copy:docs": "cp src/ui/README.md dist/ui/README.md && cp src/three/README.md dist/three/README.md && cp src/characters/README.md dist/characters/README.md && cp src/nav/README.md dist/nav/README.md",
80
+ "copy:docs": "cp src/ui/README.md dist/ui/README.md && cp src/three/README.md dist/three/README.md && cp src/characters/README.md dist/characters/README.md && cp src/nav/README.md dist/nav/README.md && cp src/training/README.md dist/training/README.md",
77
81
  "dev": "tsc -w -p tsconfig.json",
78
82
  "clean": "rm -rf dist",
79
83
  "typecheck": "tsc --noEmit -p tsconfig.json",
@@ -88,7 +92,8 @@
88
92
  "check:css": "node scripts/check-css-literals.mjs",
89
93
  "rig3d": "npm run build && vite examples/ui --open /rig3d.html",
90
94
  "pad-hud": "npm run build && vite examples/ui --open /pad-hud.html",
91
- "rig-hud": "npm run build && vite examples/ui --port 5180 --strictPort --open /rig-hud.html"
95
+ "rig-hud": "npm run build && vite examples/ui --port 5180 --strictPort --open /rig-hud.html",
96
+ "test": "node test/run.mjs"
92
97
  },
93
98
  "peerDependencies": {
94
99
  "three": ">=0.160.0"
@@ -102,7 +107,8 @@
102
107
  "@types/three": "^0.184.0",
103
108
  "three": "^0.184.0",
104
109
  "typescript": "^5.5.0",
105
- "vite": "^6.0.0"
110
+ "vite": "^6.0.0",
111
+ "esbuild": "^0.28.2"
106
112
  },
107
113
  "dependencies": {
108
114
  "@breezystack/lamejs": "^1.2.7"