@helixdev/helix-sdk 0.1.1-staging.8 → 0.1.1-staging.9
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/dist/index.d.ts +15 -2
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -1
- package/dist/multiplayer-contract/credential.d.ts +9 -0
- package/dist/multiplayer-contract/index.d.ts +4 -3
- package/dist/multiplayer-contract/index.js +4 -3
- package/dist/multiplayer-contract/index.js.map +1 -1
- package/dist/multiplayer-contract/messages.d.ts +113 -3
- package/dist/multiplayer-contract/messages.js +32 -2
- package/dist/multiplayer-contract/messages.js.map +1 -1
- package/dist/multiplayer-contract/room.d.ts +61 -2
- package/dist/multiplayer-contract/state.d.ts +1 -1
- package/dist/multiplayer.d.ts +30 -3
- package/dist/multiplayer.js +101 -15
- package/dist/multiplayer.js.map +1 -1
- package/dist/protocol.d.ts +11 -1
- package/dist/protocol.js +3 -2
- package/dist/protocol.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { HelixWorldContext, HelixUser } from './protocol';
|
|
1
|
+
import { HelixWorldContext, HelixUser, DebugLogEntry } from './protocol';
|
|
2
2
|
import { HelixMultiplayer } from './multiplayer';
|
|
3
|
-
export type { HelixWorldContext, HelixSession, HelixUser } from './protocol';
|
|
3
|
+
export type { HelixWorldContext, HelixSession, HelixUser, DebugLogEntry, DebugLogLevel } from './protocol';
|
|
4
4
|
export type { HelixRoom, JoinRoomOptions, ReplicaInput } from './multiplayer';
|
|
5
5
|
export * from './multiplayer-contract';
|
|
6
6
|
export type HelixInitResult = {
|
|
@@ -15,9 +15,19 @@ declare class HelixSdk {
|
|
|
15
15
|
private world;
|
|
16
16
|
private initialized;
|
|
17
17
|
private listeners;
|
|
18
|
+
private debugEnabled;
|
|
19
|
+
private debugRing;
|
|
20
|
+
private debugListeners;
|
|
21
|
+
private consoleHooked;
|
|
18
22
|
private pendingLogins;
|
|
19
23
|
init(): Promise<HelixInitResult>;
|
|
20
24
|
readonly multiplayer: HelixMultiplayer;
|
|
25
|
+
readonly debug: {
|
|
26
|
+
enabled: () => boolean;
|
|
27
|
+
log: (...args: unknown[]) => void;
|
|
28
|
+
onLog: (cb: (entry: DebugLogEntry) => void) => (() => void);
|
|
29
|
+
recent: () => DebugLogEntry[];
|
|
30
|
+
};
|
|
21
31
|
readonly auth: {
|
|
22
32
|
getUser: () => Promise<HelixUser | null>;
|
|
23
33
|
isAuthenticated: () => boolean;
|
|
@@ -30,6 +40,9 @@ declare class HelixSdk {
|
|
|
30
40
|
private waitForInit;
|
|
31
41
|
private onMessage;
|
|
32
42
|
private setSession;
|
|
43
|
+
private enableDebug;
|
|
44
|
+
private hookConsole;
|
|
45
|
+
private captureLog;
|
|
33
46
|
private post;
|
|
34
47
|
}
|
|
35
48
|
export declare const Helix: HelixSdk;
|
package/dist/index.js
CHANGED
|
@@ -6,12 +6,17 @@ import { HelixMultiplayer } from './multiplayer';
|
|
|
6
6
|
export * from './multiplayer-contract';
|
|
7
7
|
const INIT_TIMEOUT_MS = 3000;
|
|
8
8
|
const LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
9
|
+
const DEBUG_LOG_MAX = 200; // bounded ring of recent log entries (overlay backfill + pre-init forward flush)
|
|
9
10
|
class HelixSdk {
|
|
10
11
|
shellOrigin = null;
|
|
11
12
|
session = null;
|
|
12
13
|
world = null;
|
|
13
14
|
initialized = false;
|
|
14
15
|
listeners = new Set();
|
|
16
|
+
debugEnabled = false;
|
|
17
|
+
debugRing = [];
|
|
18
|
+
debugListeners = new Set();
|
|
19
|
+
consoleHooked = false;
|
|
15
20
|
pendingLogins = new Map();
|
|
16
21
|
// Performs the shell handshake. Call once at world start, before any other
|
|
17
22
|
// Helix API. Resolves with embedded=false if no shell answers (local dev).
|
|
@@ -33,6 +38,21 @@ class HelixSdk {
|
|
|
33
38
|
getToken: () => this.session?.token ?? null,
|
|
34
39
|
getWorldId: () => this.world?.id ?? null,
|
|
35
40
|
});
|
|
41
|
+
// Debug surface. A world's own console lives in its cross-origin iframe — invisible to DevTools / an
|
|
42
|
+
// automation harness attached to the shell, and to teammates without that frame selected. debug.log feeds
|
|
43
|
+
// a bounded ring an in-world overlay can render (onLog/recent), and — when the shell enabled debug
|
|
44
|
+
// (?debug) — is mirrored to the shell frame's console via helix:log. enableDebug() also hooks console.*
|
|
45
|
+
// so ALL world output is captured, not just explicit debug.log calls. enabled() is a fn (the flag flips
|
|
46
|
+
// at init, after the field initializer runs).
|
|
47
|
+
debug = {
|
|
48
|
+
enabled: () => this.debugEnabled,
|
|
49
|
+
log: (...args) => this.captureLog('info', args),
|
|
50
|
+
onLog: (cb) => {
|
|
51
|
+
this.debugListeners.add(cb);
|
|
52
|
+
return () => this.debugListeners.delete(cb);
|
|
53
|
+
},
|
|
54
|
+
recent: () => this.debugRing.slice(),
|
|
55
|
+
};
|
|
36
56
|
auth = {
|
|
37
57
|
getUser: async () => {
|
|
38
58
|
this.assertInitialized();
|
|
@@ -123,6 +143,8 @@ class HelixSdk {
|
|
|
123
143
|
this.shellOrigin = event.origin;
|
|
124
144
|
this.world = msg.world;
|
|
125
145
|
this.setSession(msg.session);
|
|
146
|
+
if (msg.debug)
|
|
147
|
+
this.enableDebug();
|
|
126
148
|
break;
|
|
127
149
|
case 'helix:session':
|
|
128
150
|
this.setSession(msg.session);
|
|
@@ -149,10 +171,68 @@ class HelixSdk {
|
|
|
149
171
|
listener(session?.user ?? null);
|
|
150
172
|
}
|
|
151
173
|
}
|
|
174
|
+
enableDebug() {
|
|
175
|
+
if (this.debugEnabled)
|
|
176
|
+
return;
|
|
177
|
+
this.debugEnabled = true;
|
|
178
|
+
this.hookConsole();
|
|
179
|
+
// Flush anything buffered before the shell turned debug on (e.g. early lifecycle logs).
|
|
180
|
+
if (this.shellOrigin)
|
|
181
|
+
for (const entry of this.debugRing)
|
|
182
|
+
this.post({ type: 'helix:log', entry }, this.shellOrigin);
|
|
183
|
+
}
|
|
184
|
+
// Capture ALL console output once debug is on (three.js warnings, SDK errors, uncaught logs) — not just
|
|
185
|
+
// explicit debug.log calls. Pass-through preserves normal console behaviour in the iframe.
|
|
186
|
+
hookConsole() {
|
|
187
|
+
if (this.consoleHooked || typeof console === 'undefined')
|
|
188
|
+
return;
|
|
189
|
+
this.consoleHooked = true;
|
|
190
|
+
const levels = ['log', 'info', 'warn', 'error', 'debug'];
|
|
191
|
+
const c = console;
|
|
192
|
+
for (const level of levels) {
|
|
193
|
+
const orig = c[level]?.bind(console);
|
|
194
|
+
if (!orig)
|
|
195
|
+
continue;
|
|
196
|
+
c[level] = (...args) => {
|
|
197
|
+
this.captureLog(level, args);
|
|
198
|
+
orig(...args);
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
captureLog(level, args) {
|
|
203
|
+
const entry = { level, args: args.map(stringifyArg), t: Date.now() };
|
|
204
|
+
this.debugRing.push(entry);
|
|
205
|
+
if (this.debugRing.length > DEBUG_LOG_MAX)
|
|
206
|
+
this.debugRing.shift();
|
|
207
|
+
for (const cb of this.debugListeners) {
|
|
208
|
+
try {
|
|
209
|
+
cb(entry);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
/* a listener must never break logging */
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (this.debugEnabled && this.shellOrigin)
|
|
216
|
+
this.post({ type: 'helix:log', entry }, this.shellOrigin);
|
|
217
|
+
}
|
|
152
218
|
post(message, targetOrigin) {
|
|
153
219
|
window.parent.postMessage(message, targetOrigin);
|
|
154
220
|
}
|
|
155
221
|
}
|
|
222
|
+
// Serialize a console arg to a string the shell can print: strings as-is, Errors to their stack, other
|
|
223
|
+
// objects to compact JSON (falling back to String() on cyclic/unserializable values).
|
|
224
|
+
function stringifyArg(a) {
|
|
225
|
+
if (typeof a === 'string')
|
|
226
|
+
return a;
|
|
227
|
+
if (a instanceof Error)
|
|
228
|
+
return a.stack ?? `${a.name}: ${a.message}`;
|
|
229
|
+
try {
|
|
230
|
+
return typeof a === 'object' && a !== null ? JSON.stringify(a) : String(a);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return String(a);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
156
236
|
// Worlds import this singleton: `import { Helix } from '@hypersoniclabs/helix-sdk'`.
|
|
157
237
|
export const Helix = new HelixSdk();
|
|
158
238
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,6 +16,15 @@ export interface RoomCredentialClaims {
|
|
|
16
16
|
scopes: string[];
|
|
17
17
|
/** Manifest maxPlayers (authoritative) → the room's maxClients cap, so it auto-locks + spills when full. */
|
|
18
18
|
maxPlayers: number;
|
|
19
|
+
/** Monotonic per-world build counter (1, 2, 3 …) of the active build — telemetry + config cache key. */
|
|
20
|
+
buildNumber: number;
|
|
21
|
+
/**
|
|
22
|
+
* Signed URL of this build's `multiplayer.json` — the validated declarative config baked into the
|
|
23
|
+
* published bundle. Derived server-side from the build, NEVER client-supplied, so the room fetches its
|
|
24
|
+
* config from an authentic, immutable location and caches the parsed result by buildId. The room
|
|
25
|
+
* tolerates its absence (older credentials / a build with no declared config) by running the fixed schema.
|
|
26
|
+
*/
|
|
27
|
+
configUrl: string;
|
|
19
28
|
/** Always ROOM_CREDENTIAL_AUDIENCE. */
|
|
20
29
|
aud: typeof ROOM_CREDENTIAL_AUDIENCE;
|
|
21
30
|
iss: string;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Wire format version. Bump on any incompatible change to PlayerReplicaState, the message table,
|
|
3
|
-
*
|
|
2
|
+
* Wire format version. Bump on any incompatible change to PlayerReplicaState, the message table, the
|
|
3
|
+
* credential claims, or the realized declared-state shape (roomVars / per-player vars / entities), so the
|
|
4
|
+
* room/SDK/driver can detect a mismatch instead of misreading bytes.
|
|
4
5
|
*/
|
|
5
|
-
export declare const CONTRACT_VERSION:
|
|
6
|
+
export declare const CONTRACT_VERSION: 11;
|
|
6
7
|
export * from './state';
|
|
7
8
|
export * from './room';
|
|
8
9
|
export * from './messages';
|
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
// per-player vars, and entities are DECLARED per-world — that's how an agent syncs custom game state
|
|
15
15
|
// without writing server code. The generic 'action' message (messages.ts) is the channel for it.
|
|
16
16
|
/**
|
|
17
|
-
* Wire format version. Bump on any incompatible change to PlayerReplicaState, the message table,
|
|
18
|
-
*
|
|
17
|
+
* Wire format version. Bump on any incompatible change to PlayerReplicaState, the message table, the
|
|
18
|
+
* credential claims, or the realized declared-state shape (roomVars / per-player vars / entities), so the
|
|
19
|
+
* room/SDK/driver can detect a mismatch instead of misreading bytes.
|
|
19
20
|
*/
|
|
20
|
-
export const CONTRACT_VERSION = 2
|
|
21
|
+
export const CONTRACT_VERSION = 11; // v11: EntityStateBatch — many owner-entity uploads in one message (Tier 2 Phase 4.10, beats the per-connection message-count cap)
|
|
21
22
|
export * from './state';
|
|
22
23
|
export * from './room';
|
|
23
24
|
export * from './messages';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/multiplayer-contract/index.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,EAAE;AACF,iGAAiG;AACjG,0FAA0F;AAC1F,oEAAoE;AACpE,wFAAwF;AACxF,8EAA8E;AAC9E,EAAE;AACF,mGAAmG;AACnG,+FAA+F;AAC/F,gDAAgD;AAChD,EAAE;AACF,uGAAuG;AACvG,qGAAqG;AACrG,iGAAiG;AAEjG
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/multiplayer-contract/index.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,EAAE;AACF,iGAAiG;AACjG,0FAA0F;AAC1F,oEAAoE;AACpE,wFAAwF;AACxF,8EAA8E;AAC9E,EAAE;AACF,mGAAmG;AACnG,+FAA+F;AAC/F,gDAAgD;AAChD,EAAE;AACF,uGAAuG;AACvG,qGAAqG;AACrG,iGAAiG;AAEjG;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAW,CAAC,CAAC,mIAAmI;AAEhL,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
|
|
@@ -7,8 +7,25 @@ export declare const ClientMessageType: {
|
|
|
7
7
|
readonly Ability: "ability";
|
|
8
8
|
/** A world-authored declared action (the generic extensibility channel — see ActionMessage). */
|
|
9
9
|
readonly Action: "action";
|
|
10
|
+
/** An owner-authoritative entity's client-simulated state for this tick (Tier 2 Phase 4.6c — see EntityStateMessage). */
|
|
11
|
+
readonly EntityState: "entityState";
|
|
12
|
+
/**
|
|
13
|
+
* A BATCH of owner-authoritative entity uploads for this tick — ALL the entities this connection hosts in one
|
|
14
|
+
* message (Tier 2 Phase 4.10 — see EntityStateBatchMessage). The per-connection rate cap counts MESSAGES, so a
|
|
15
|
+
* host of N entities must batch (one message/tick) instead of N single EntityState messages, or it starves its
|
|
16
|
+
* own upload budget. EntityScene uses this; the single EntityState stays for one-off uploads (e.g. a flag).
|
|
17
|
+
*/
|
|
18
|
+
readonly EntityStateBatch: "entityStateBatch";
|
|
19
|
+
/** Clock-sync probe: the client's local timestamp, which the room echoes in a Pong (RTT + offset). */
|
|
20
|
+
readonly Ping: "ping";
|
|
10
21
|
};
|
|
11
22
|
export type ClientMessageType = (typeof ClientMessageType)[keyof typeof ClientMessageType];
|
|
23
|
+
/** Server → client messages (the room sends these; SDK exposes them via room.onMessage(type, cb)). */
|
|
24
|
+
export declare const ServerMessageType: {
|
|
25
|
+
/** Reply to a Ping: the client's echoed timestamp + the server's current time, for offset estimation. */
|
|
26
|
+
readonly Pong: "pong";
|
|
27
|
+
};
|
|
28
|
+
export type ServerMessageType = (typeof ServerMessageType)[keyof typeof ServerMessageType];
|
|
12
29
|
/** Per-tick state upload. Units match PlayerReplicaState (position m; all angles degrees, see *Deg). */
|
|
13
30
|
export interface StateMessage {
|
|
14
31
|
/** Monotonic per-client sequence; the room echoes the last applied seq for client reconciliation. */
|
|
@@ -23,6 +40,58 @@ export interface StateMessage {
|
|
|
23
40
|
aimYawDeg: number;
|
|
24
41
|
aimPitchDeg: number;
|
|
25
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* An owner-authoritative entity's client-simulated state for this tick (Tier 2 Phase 4.6c, spec §9/§10.4). The
|
|
45
|
+
* client that the server marked as the entity's `controller` (see EntityState.controller) simulates the entity
|
|
46
|
+
* and uploads its transform + declared vars; the room sanity-validates (it owns this entity? movement within
|
|
47
|
+
* its declared `maxSpeed`? vars within their declared bounds?) and relays. Cross-player consequences still go
|
|
48
|
+
* through server-validated rules/actions — never this channel (the publish-time firewall enforces that).
|
|
49
|
+
*/
|
|
50
|
+
export interface EntityStateMessage {
|
|
51
|
+
/** The entity id this upload is for (the state.entities map key). Rejected unless the sender is its controller. */
|
|
52
|
+
entity: string;
|
|
53
|
+
/** Monotonic per-entity sequence; the room ignores a stale/out-of-order frame. */
|
|
54
|
+
seq: number;
|
|
55
|
+
/** The entity's world position, METERS — gated against the kind's declared maxSpeed. */
|
|
56
|
+
position: Vec3;
|
|
57
|
+
/** Declared per-kind vars to set (same vocabulary as roomVars); each is clamped to its declared type/bounds on ingest. */
|
|
58
|
+
vars?: Record<string, RoomVarValue>;
|
|
59
|
+
/**
|
|
60
|
+
* The authority EPOCH the client is simulating under (Tier 2 Phase 4.5.12, spec §9). Mirror the entity's synced
|
|
61
|
+
* `authorityEpoch`; the room bumps it on every controller handoff (host migration AND voluntary ownership
|
|
62
|
+
* transfer) and rejects an upload whose epoch ≠ the entity's current one — fencing a revoked/stale authority
|
|
63
|
+
* era even from a still-connected prior owner whose sessionId would otherwise pass. Omit only for the very
|
|
64
|
+
* first frame before the field is observed; the room tolerates absence (sessionId ownership still gates).
|
|
65
|
+
*/
|
|
66
|
+
epoch?: number;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* One entity's slot in an EntityStateBatchMessage. Compact, short keys (sent verbatim in msgpack), with position
|
|
70
|
+
* as a 3-tuple instead of a {x,y,z} object — the same authoritative meaning as EntityStateMessage's per-entity
|
|
71
|
+
* fields. `v`/`ep` are omitted when unchanged/unobserved to keep the batch small.
|
|
72
|
+
*/
|
|
73
|
+
export interface EntityStateBatchEntry {
|
|
74
|
+
/** Entity id (the state.entities map key). Rejected unless the sender is its controller. */
|
|
75
|
+
e: string;
|
|
76
|
+
/** World position as [x, y, z], METERS — gated against the kind's declared maxSpeed. */
|
|
77
|
+
p: [number, number, number];
|
|
78
|
+
/** Declared per-kind vars to set (clamped to declared type/bounds on ingest). Omit when unchanged this tick. */
|
|
79
|
+
v?: Record<string, RoomVarValue>;
|
|
80
|
+
/** Authority epoch this entity is simulated under (mirror its synced authorityEpoch). Omit before observed. */
|
|
81
|
+
ep?: number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A batch of owner-authoritative entity uploads (Tier 2 Phase 4.10, spec §10.4) — every entity this connection
|
|
85
|
+
* hosts, in ONE message. The per-connection rate cap (MESSAGE_RATE.entityStateHz) counts messages, so batching
|
|
86
|
+
* lets a host refresh many entities per tick within one token instead of spending one token per entity. The room
|
|
87
|
+
* applies each entry exactly like a single EntityStateMessage (ownership + epoch + maxSpeed gate + var clamp).
|
|
88
|
+
*/
|
|
89
|
+
export interface EntityStateBatchMessage {
|
|
90
|
+
/** Monotonic per-connection batch sequence; the room ignores a stale/out-of-order batch per entity (gate seq). */
|
|
91
|
+
seq: number;
|
|
92
|
+
/** One entry per hosted entity changed this tick (bounded by the room's per-owner entity cap). */
|
|
93
|
+
states: EntityStateBatchEntry[];
|
|
94
|
+
}
|
|
26
95
|
/** Discrete ability activation intent. */
|
|
27
96
|
export interface AbilityMessage {
|
|
28
97
|
/** Ability id (must be one the world's build declares). */
|
|
@@ -42,11 +111,45 @@ export interface ActionMessage {
|
|
|
42
111
|
/** Primitive/Vec3 args, validated against the declared action's schema by the room. */
|
|
43
112
|
args?: Record<string, RoomVarValue>;
|
|
44
113
|
}
|
|
45
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Clock-sync probe. The client sends its local send time; the room replies (Pong) with this value echoed
|
|
116
|
+
* plus its own clock, so the client estimates RTT (recv − send) and offset (serverTimeMs − send − RTT/2).
|
|
117
|
+
*/
|
|
118
|
+
export interface PingMessage {
|
|
119
|
+
/** The client's local clock (epoch ms) at send — echoed verbatim in the Pong. */
|
|
120
|
+
clientTimeMs: number;
|
|
121
|
+
}
|
|
122
|
+
/** Reply to a Ping — the echoed client time + the server's clock at reply, for offset/RTT estimation. */
|
|
123
|
+
export interface PongMessage {
|
|
124
|
+
clientTimeMs: number;
|
|
125
|
+
serverTimeMs: number;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* A server→client broadcast event (Tier 2 Phase 2.5, spec §10.3). A world declares named events with a
|
|
129
|
+
* payload schema; a `broadcast` rule-effect sends one on the event's NAME (not a fixed ServerMessageType),
|
|
130
|
+
* received via `room.onMessage(name, cb)`. Payload values are the declared fields; a `ref` field serializes
|
|
131
|
+
* to its `playerKey` string on the wire (so `who:"self"` and `who:{aggregate argmax}` are the same type).
|
|
132
|
+
*/
|
|
133
|
+
export type BroadcastPayload = Record<string, RoomVarValue>;
|
|
134
|
+
/**
|
|
135
|
+
* Wire message-type names a world's declared event/action names MUST NOT shadow — the fixed client→server
|
|
136
|
+
* and server→client channels. The manifest validator rejects a declared name in this set so a `broadcast`
|
|
137
|
+
* can't collide with `pong` (or confuse the fixed channels). Inlined in @hypersoniclabs/helix-manifest with
|
|
138
|
+
* a cross-ref (the manifest takes no SDK dependency); keep the two in sync.
|
|
139
|
+
*/
|
|
140
|
+
export declare const RESERVED_MESSAGE_TYPES: readonly ["state", "ability", "action", "entityState", "entityStateBatch", "ping", "pong"];
|
|
141
|
+
/** Maps each client→server message type to its payload, so senders/handlers stay in lockstep. */
|
|
46
142
|
export interface ClientMessagePayloads {
|
|
47
143
|
[ClientMessageType.State]: StateMessage;
|
|
48
144
|
[ClientMessageType.Ability]: AbilityMessage;
|
|
49
145
|
[ClientMessageType.Action]: ActionMessage;
|
|
146
|
+
[ClientMessageType.EntityState]: EntityStateMessage;
|
|
147
|
+
[ClientMessageType.EntityStateBatch]: EntityStateBatchMessage;
|
|
148
|
+
[ClientMessageType.Ping]: PingMessage;
|
|
149
|
+
}
|
|
150
|
+
/** Maps each server→client message type to its payload. */
|
|
151
|
+
export interface ServerMessagePayloads {
|
|
152
|
+
[ServerMessageType.Pong]: PongMessage;
|
|
50
153
|
}
|
|
51
154
|
/**
|
|
52
155
|
* Per-connection rate ceilings (messages/second) the room enforces; over-rate clients are dropped.
|
|
@@ -56,6 +159,13 @@ export declare const MESSAGE_RATE: {
|
|
|
56
159
|
readonly stateHz: 10;
|
|
57
160
|
readonly abilityHz: 20;
|
|
58
161
|
readonly actionHz: 20;
|
|
162
|
+
readonly entityStateHz: 10;
|
|
163
|
+
readonly pingHz: 4;
|
|
59
164
|
};
|
|
60
|
-
/**
|
|
61
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Hard payload bound the room rejects above (defense against oversized frames), measured on the JSON form (a
|
|
167
|
+
* cheap proxy; the msgpack wire is smaller). Sized to admit a full EntityStateBatch — up to MAX_ENTITIES_PER_OWNER
|
|
168
|
+
* entities in one message. MUST stay below the Colyseus transport `maxPayload` (set in helix-colyseus-server
|
|
169
|
+
* src/index.ts) in WIRE terms, so our clean reject fires before the transport drops the frame / closes the socket.
|
|
170
|
+
*/
|
|
171
|
+
export declare const MAX_MESSAGE_BYTES = 8192;
|
|
@@ -12,7 +12,30 @@ export const ClientMessageType = {
|
|
|
12
12
|
Ability: 'ability',
|
|
13
13
|
/** A world-authored declared action (the generic extensibility channel — see ActionMessage). */
|
|
14
14
|
Action: 'action',
|
|
15
|
+
/** An owner-authoritative entity's client-simulated state for this tick (Tier 2 Phase 4.6c — see EntityStateMessage). */
|
|
16
|
+
EntityState: 'entityState',
|
|
17
|
+
/**
|
|
18
|
+
* A BATCH of owner-authoritative entity uploads for this tick — ALL the entities this connection hosts in one
|
|
19
|
+
* message (Tier 2 Phase 4.10 — see EntityStateBatchMessage). The per-connection rate cap counts MESSAGES, so a
|
|
20
|
+
* host of N entities must batch (one message/tick) instead of N single EntityState messages, or it starves its
|
|
21
|
+
* own upload budget. EntityScene uses this; the single EntityState stays for one-off uploads (e.g. a flag).
|
|
22
|
+
*/
|
|
23
|
+
EntityStateBatch: 'entityStateBatch',
|
|
24
|
+
/** Clock-sync probe: the client's local timestamp, which the room echoes in a Pong (RTT + offset). */
|
|
25
|
+
Ping: 'ping',
|
|
15
26
|
};
|
|
27
|
+
/** Server → client messages (the room sends these; SDK exposes them via room.onMessage(type, cb)). */
|
|
28
|
+
export const ServerMessageType = {
|
|
29
|
+
/** Reply to a Ping: the client's echoed timestamp + the server's current time, for offset estimation. */
|
|
30
|
+
Pong: 'pong',
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Wire message-type names a world's declared event/action names MUST NOT shadow — the fixed client→server
|
|
34
|
+
* and server→client channels. The manifest validator rejects a declared name in this set so a `broadcast`
|
|
35
|
+
* can't collide with `pong` (or confuse the fixed channels). Inlined in @hypersoniclabs/helix-manifest with
|
|
36
|
+
* a cross-ref (the manifest takes no SDK dependency); keep the two in sync.
|
|
37
|
+
*/
|
|
38
|
+
export const RESERVED_MESSAGE_TYPES = ['state', 'ability', 'action', 'entityState', 'entityStateBatch', 'ping', 'pong'];
|
|
16
39
|
/**
|
|
17
40
|
* Per-connection rate ceilings (messages/second) the room enforces; over-rate clients are dropped.
|
|
18
41
|
* State is the throttled input channel (≤~10Hz, plan D3); ability is bursty but bounded.
|
|
@@ -21,7 +44,14 @@ export const MESSAGE_RATE = {
|
|
|
21
44
|
stateHz: 10,
|
|
22
45
|
abilityHz: 20,
|
|
23
46
|
actionHz: 20,
|
|
47
|
+
entityStateHz: 10, // owner-entity upload — same cadence as the player state channel (one bucket per connection)
|
|
48
|
+
pingHz: 4, // clock-sync probe — a few per second is ample to track offset/RTT
|
|
24
49
|
};
|
|
25
|
-
/**
|
|
26
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Hard payload bound the room rejects above (defense against oversized frames), measured on the JSON form (a
|
|
52
|
+
* cheap proxy; the msgpack wire is smaller). Sized to admit a full EntityStateBatch — up to MAX_ENTITIES_PER_OWNER
|
|
53
|
+
* entities in one message. MUST stay below the Colyseus transport `maxPayload` (set in helix-colyseus-server
|
|
54
|
+
* src/index.ts) in WIRE terms, so our clean reject fires before the transport drops the frame / closes the socket.
|
|
55
|
+
*/
|
|
56
|
+
export const MAX_MESSAGE_BYTES = 8192;
|
|
27
57
|
//# sourceMappingURL=messages.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/multiplayer-contract/messages.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,mGAAmG;AACnG,kGAAkG;AAClG,EAAE;AACF,kGAAkG;AAClG,qGAAqG;AACrG,0FAA0F;AAK1F,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,4FAA4F;IAC5F,KAAK,EAAE,OAAO;IACd,4FAA4F;IAC5F,OAAO,EAAE,SAAS;IAClB,gGAAgG;IAChG,MAAM,EAAE,QAAQ;
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/multiplayer-contract/messages.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,mGAAmG;AACnG,kGAAkG;AAClG,EAAE;AACF,kGAAkG;AAClG,qGAAqG;AACrG,0FAA0F;AAK1F,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,4FAA4F;IAC5F,KAAK,EAAE,OAAO;IACd,4FAA4F;IAC5F,OAAO,EAAE,SAAS;IAClB,gGAAgG;IAChG,MAAM,EAAE,QAAQ;IAChB,yHAAyH;IACzH,WAAW,EAAE,aAAa;IAC1B;;;;;OAKG;IACH,gBAAgB,EAAE,kBAAkB;IACpC,sGAAsG;IACtG,IAAI,EAAE,MAAM;CACJ,CAAC;AAGX,sGAAsG;AACtG,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,yGAAyG;IACzG,IAAI,EAAE,MAAM;CACJ,CAAC;AAqHX;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,CAAU,CAAC;AAiBjI;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,OAAO,EAAE,EAAE;IACX,SAAS,EAAE,EAAE;IACb,QAAQ,EAAE,EAAE;IACZ,aAAa,EAAE,EAAE,EAAE,6FAA6F;IAChH,MAAM,EAAE,CAAC,EAAE,mEAAmE;CACtE,CAAC;AAEX;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC"}
|
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
import type { Vec3 } from './state';
|
|
2
2
|
import type { PlayerIdentity, PlayerReplicaState } from './state';
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* The runtime value vocabulary for declared custom state. A `ref`-typed var's value is the **id** of a live
|
|
5
|
+
* member (a player's `playerKey` or an entity id) as a string, or `null` when unset/dangling — hence `null`
|
|
6
|
+
* is included. The DECLARATION vocabulary (`VarType`: number/string/boolean/vec3/ref + bounds) lives in
|
|
7
|
+
* `@hypersoniclabs/helix-manifest` (authored config, validated at publish); this is the wire value side.
|
|
8
|
+
*/
|
|
9
|
+
export type RoomVarValue = number | string | boolean | Vec3 | null | RoomVarCollection;
|
|
10
|
+
/**
|
|
11
|
+
* Per-player/room COLLECTION values (Tier 2 Phase 4.5.13, spec §5). A `list` var reads as an ordered array of
|
|
12
|
+
* its declared scalar element type (realized server-side as a colyseus ArraySchema); a `counterMap` var reads as
|
|
13
|
+
* a string→number record over its declared key enum (a MapSchema<number>). Mutated only via the dedicated
|
|
14
|
+
* append/clear/addCount effects + read via listLength/listAt/count ops — never a normal scalar expr/payload/arg.
|
|
15
|
+
*/
|
|
16
|
+
export type RoomVarCollection = number[] | string[] | boolean[] | Record<string, number>;
|
|
5
17
|
/** A declared bag of custom state — names + types are declared per-world; used for both room-level and per-player vars. */
|
|
6
18
|
export type RoomVars = Record<string, RoomVarValue>;
|
|
7
19
|
/** Full authoritative per-player state: the FIXED character contract + identity + DECLARED game vars. */
|
|
8
20
|
export interface PlayerState extends PlayerIdentity, PlayerReplicaState {
|
|
9
21
|
/** Declared per-player game state (e.g. team, health, score). Same vocabulary as roomVars; declared per-world. */
|
|
10
22
|
vars: RoomVars;
|
|
23
|
+
/**
|
|
24
|
+
* Presence (Tier 2 Phase 3, spec §8): `false` while this seat sits in the reconnection grace window after an
|
|
25
|
+
* unexpected drop — the seat (and its refs) persists, but the server excludes it from reductions. The seat is
|
|
26
|
+
* removed (an `onRemove('players')`) only at grace expiry; until then the engine should render the replica idle.
|
|
27
|
+
* This layers on Colyseus's own reconnection — it annotates the seat, it does not drive the reconnect handshake.
|
|
28
|
+
*/
|
|
29
|
+
connected: boolean;
|
|
11
30
|
}
|
|
12
31
|
/**
|
|
13
32
|
* RESERVED (Tier 2): a server-spawned object (pickup, projectile, NPC). Empty in v1 — the shape is
|
|
@@ -19,6 +38,19 @@ export interface EntityState {
|
|
|
19
38
|
/** Author-defined kind (e.g. 'flag', 'pickup'). */
|
|
20
39
|
kind: string;
|
|
21
40
|
position?: Vec3;
|
|
41
|
+
/**
|
|
42
|
+
* Who simulates this entity (Tier 2 Phase 4.6c, spec §9). '' = server-authoritative (deterministic kinematics).
|
|
43
|
+
* A player id (a state.players key) = an owner-authoritative entity that client simulates + uploads (the others
|
|
44
|
+
* render it); a client reads this to know which entities it should be uploading via uploadEntity.
|
|
45
|
+
*/
|
|
46
|
+
controller: string;
|
|
47
|
+
/**
|
|
48
|
+
* Monotonic authority epoch (Tier 2 Phase 4.5.12, spec §9), bumped on every controller handoff — host
|
|
49
|
+
* migration AND voluntary ownership transfer. A controller client mirrors this into each EntityStateMessage;
|
|
50
|
+
* the room rejects an upload whose epoch ≠ the current one, fencing a stale authority era (a still-connected
|
|
51
|
+
* prior owner's in-flight frames after ownership moved). 0 at spawn; clients only read it.
|
|
52
|
+
*/
|
|
53
|
+
authorityEpoch: number;
|
|
22
54
|
/** Declared custom vars (same vocabulary as roomVars). */
|
|
23
55
|
vars: RoomVars;
|
|
24
56
|
}
|
|
@@ -30,4 +62,31 @@ export interface RoomState {
|
|
|
30
62
|
roomVars: RoomVars;
|
|
31
63
|
/** RESERVED (Tier 2): server-spawned entities. Empty in v1. */
|
|
32
64
|
entities: Record<string, EntityState>;
|
|
65
|
+
/** Authoritative server clock: a monotonic counter advanced once per fixed-rate sim tick. */
|
|
66
|
+
serverTick: number;
|
|
67
|
+
/**
|
|
68
|
+
* Authoritative server wall-clock (epoch ms) sampled at the last tick. Clients align to it (offset via the
|
|
69
|
+
* ping/pong echo, see messages.ts) and interpolate between patches, so timer/phase deadlines expressed
|
|
70
|
+
* against server time agree across clients without a per-tick countdown var.
|
|
71
|
+
*/
|
|
72
|
+
serverTimeMs: number;
|
|
73
|
+
/**
|
|
74
|
+
* The current phase of the world's room-scoped state machine (Tier 2 Phase 3, spec §8), or `''` when the
|
|
75
|
+
* world declares no `states`. Authored phase names; changed only by the server's `transitionTo`. A
|
|
76
|
+
* late-joiner reads it off synced state to know whether to spawn in or spectate (the join policy).
|
|
77
|
+
*/
|
|
78
|
+
phase: string;
|
|
79
|
+
/**
|
|
80
|
+
* The `serverTick` at which the room entered its current `phase`. With `serverTick`/`serverTimeMs` a client
|
|
81
|
+
* computes time-in-phase locally (e.g. a round countdown) without a per-tick countdown var.
|
|
82
|
+
*/
|
|
83
|
+
phaseStartTick: number;
|
|
84
|
+
/**
|
|
85
|
+
* Active timers (Tier 2 Phase 3, spec §8) → absolute deadline as `serverTimeMs` (epoch ms). The map key is
|
|
86
|
+
* the timer name for a room-scoped timer, or `"<timer>|<playerKey>"` for a per-player **keyed** timer (e.g.
|
|
87
|
+
* read your own cooldown at `"cooldown|" + room.sessionId`). A client computes the seconds remaining as
|
|
88
|
+
* `(deadline − estimatedServerTimeMs) / 1000` using its ping/pong offset, so a countdown interpolates locally
|
|
89
|
+
* without a per-tick countdown var. An entry is removed when the timer fires or is cancelled.
|
|
90
|
+
*/
|
|
91
|
+
timerDeadlines: Record<string, number>;
|
|
33
92
|
}
|
|
@@ -11,7 +11,7 @@ export declare const UNITS: {
|
|
|
11
11
|
};
|
|
12
12
|
/** Stable per-player identity (set at join from the room credential; not part of the per-tick churn). */
|
|
13
13
|
export interface PlayerIdentity {
|
|
14
|
-
/**
|
|
14
|
+
/** Opaque per-connection room playerKey (= the state.players map key). NOT the credential `sub` — that stays server-side (§4). */
|
|
15
15
|
id: string;
|
|
16
16
|
/** Sanitized display name for nameplates (sanitized before it enters room state — see plan H4). */
|
|
17
17
|
displayName: string;
|
package/dist/multiplayer.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type StateMessage, type RoomState, type PlayerState, type EntityState, type RoomVarValue } from './multiplayer-contract';
|
|
1
|
+
import { type StateMessage, type RoomState, type PlayerState, type EntityState, type RoomVarValue, type Vec3 } from './multiplayer-contract';
|
|
2
2
|
export type ReplicaInput = Omit<StateMessage, 'seq'>;
|
|
3
3
|
/** Options for joinRoom — only needed where the API base can't be derived from the token (local dev/tests). */
|
|
4
4
|
export interface JoinRoomOptions {
|
|
@@ -26,6 +26,27 @@ export interface HelixRoom {
|
|
|
26
26
|
sendAbility(ability: string, active: boolean): void;
|
|
27
27
|
/** A world-authored declared action (the generic extensibility channel). */
|
|
28
28
|
sendAction(name: string, args?: Record<string, RoomVarValue>): void;
|
|
29
|
+
/**
|
|
30
|
+
* Upload an owner-authoritative entity's client-simulated state (Tier 2 Phase 4.6c). Call only for an entity
|
|
31
|
+
* whose `controller` is this client (state.entities[id].controller === sessionId); the room rejects others.
|
|
32
|
+
* Sent immediately, per-entity seq-tagged; the room gates the position against the kind's maxSpeed + clamps vars.
|
|
33
|
+
*/
|
|
34
|
+
uploadEntity(entityId: string, input: {
|
|
35
|
+
position: Vec3;
|
|
36
|
+
vars?: Record<string, RoomVarValue>;
|
|
37
|
+
}): void;
|
|
38
|
+
/**
|
|
39
|
+
* Upload MANY owner-authoritative entities in ONE message (Tier 2 Phase 4.10). Prefer this over per-entity
|
|
40
|
+
* uploadEntity when hosting multiple entities: the per-connection rate cap counts messages, so N single uploads
|
|
41
|
+
* spend N tokens/tick and starve, while one batch spends one. Position is sent as a compact [x,y,z] tuple; each
|
|
42
|
+
* entry is gated/clamped server-side exactly like uploadEntity. Pass only entities this client controls.
|
|
43
|
+
*/
|
|
44
|
+
uploadEntities(entities: ReadonlyArray<{
|
|
45
|
+
id: string;
|
|
46
|
+
position: Vec3;
|
|
47
|
+
vars?: Record<string, RoomVarValue>;
|
|
48
|
+
epoch?: number;
|
|
49
|
+
}>): void;
|
|
29
50
|
/** Sent-but-unacknowledged states — the reconciliation tail the engine NetworkDriver replays (D4). */
|
|
30
51
|
pendingInputs(): readonly StateMessage[];
|
|
31
52
|
/** Prune the reconciliation buffer once the server confirms it processed up to `seq` (D4). */
|
|
@@ -50,10 +71,12 @@ export declare class HelixMultiplayer {
|
|
|
50
71
|
private room;
|
|
51
72
|
private callbacks;
|
|
52
73
|
private seq;
|
|
74
|
+
private entitySeq;
|
|
75
|
+
private entityBatchSeq;
|
|
53
76
|
private pendingInput;
|
|
54
77
|
private flushTimer;
|
|
55
78
|
private inputBuffer;
|
|
56
|
-
private
|
|
79
|
+
private worldId;
|
|
57
80
|
constructor(host: MultiplayerHost);
|
|
58
81
|
configure(options: {
|
|
59
82
|
apiBaseUrl: string;
|
|
@@ -63,7 +86,11 @@ export declare class HelixMultiplayer {
|
|
|
63
86
|
private makeHandle;
|
|
64
87
|
private startFlush;
|
|
65
88
|
private flush;
|
|
66
|
-
private
|
|
89
|
+
private persistReconnect;
|
|
90
|
+
private readReconnect;
|
|
91
|
+
private reconnectWsUrl;
|
|
92
|
+
private clearReconnect;
|
|
93
|
+
private tryReconnect;
|
|
67
94
|
private leave;
|
|
68
95
|
private teardown;
|
|
69
96
|
}
|