@helixdev/helix-manifest 0.2.1-staging.8 → 0.3.0-staging.11

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.
@@ -1,8 +1,9 @@
1
1
  export * from './bundle-rules';
2
2
  export declare const MANIFEST_FILENAME = "helix.json";
3
- export declare const MANIFEST_VERSIONS: readonly ["0.1", "0.2"];
3
+ export declare const MANIFEST_VERSIONS: readonly ["0.1", "0.2", "0.3"];
4
4
  export declare const PERMISSIONS_V01: readonly ["auth.profile"];
5
- export type HelixPermission = (typeof PERMISSIONS_V01)[number];
5
+ export declare const PERMISSIONS_V03: readonly ["auth.profile", "multiplayer", "voice.room", "voice.proximity"];
6
+ export type HelixPermission = (typeof PERMISSIONS_V03)[number];
6
7
  /**
7
8
  * Bundle vocabulary, pinned for main-backend compatibility. A **system** is a
8
9
  * versioned runtime framework that hosts abilities (the humanoid character is
@@ -21,18 +22,375 @@ export type HelixBundleKind = (typeof BUNDLE_KINDS)[number];
21
22
  */
22
23
  export declare const MAIN_BACKEND_PACKAGE_TYPE: Record<HelixBundleKind, string>;
23
24
  export type HelixContentRating = 'unrated' | 'everyone' | 'teen' | 'mature';
25
+ /**
26
+ * A declared custom-state variable's type + constraints (Tier 2 declared state — spec §5). The author
27
+ * declares names -> HelixVarType under `multiplayer.state`; the room realizes them as @colyseus/schema and
28
+ * interprets them. Value vocabulary: number | string | boolean | vec3 | ref (the id of a live player/entity).
29
+ * This is the DECLARATION home; the runtime value union (RoomVarValue) lives in the SDK multiplayer-contract.
30
+ */
31
+ export type HelixVarType = {
32
+ type: 'number';
33
+ default: number;
34
+ min?: number;
35
+ max?: number;
36
+ integer?: boolean;
37
+ } | {
38
+ type: 'string';
39
+ default: string;
40
+ maxLen?: number;
41
+ enum?: string[];
42
+ } | {
43
+ type: 'boolean';
44
+ default: boolean;
45
+ } | {
46
+ type: 'vec3';
47
+ default: [number, number, number];
48
+ } | {
49
+ type: 'ref';
50
+ of: 'player' | `entity:${string}`;
51
+ default?: null;
52
+ } | {
53
+ type: 'list';
54
+ of: 'number' | 'string' | 'boolean';
55
+ maxLen: number;
56
+ } | {
57
+ type: 'counterMap';
58
+ keys: string[];
59
+ };
60
+ /** A declared bag of custom vars: name -> type. Names are identifiers (see the v0.3 schema pattern). */
61
+ export type HelixVarDecls = Record<string, HelixVarType>;
62
+ /** v0.3 (Tier 2 Phase 1.3): the per-world DECLARED state — room-level + per-player custom vars. */
63
+ export type HelixMultiplayerState = {
64
+ roomVars?: HelixVarDecls;
65
+ playerVars?: HelixVarDecls;
66
+ };
67
+ export type HelixRuleEvent = {
68
+ on: 'tick';
69
+ everyN?: number;
70
+ } | {
71
+ on: 'playerJoin';
72
+ } | {
73
+ on: 'playerLeave';
74
+ } | {
75
+ on: 'playerDisconnect' | 'playerReconnect';
76
+ } | {
77
+ on: 'zoneEnter' | 'zoneExit' | 'zoneInside';
78
+ zone: string;
79
+ } | {
80
+ on: 'playerContact';
81
+ radius: number;
82
+ } | {
83
+ on: 'action';
84
+ name: string;
85
+ } | {
86
+ on: 'stateEnter' | 'stateExit';
87
+ phase: string;
88
+ } | {
89
+ on: 'timerElapsed';
90
+ timer: string;
91
+ } | {
92
+ on: 'entitySpawn' | 'entityDestroy';
93
+ kind: string;
94
+ } | {
95
+ on: 'ownershipChanged';
96
+ kind: string;
97
+ } | {
98
+ on: 'varReached';
99
+ scope: 'room' | 'self';
100
+ var: string;
101
+ cmp: '==' | '!=' | '<' | '<=' | '>' | '>=';
102
+ value: number | string | boolean;
103
+ };
104
+ export type HelixExpr = number | string | boolean | {
105
+ vec3: [number, number, number];
106
+ } | {
107
+ var: string;
108
+ } | {
109
+ ref: HelixRef;
110
+ var: string;
111
+ } | {
112
+ op: 'distance';
113
+ a: HelixExpr;
114
+ b: HelixExpr;
115
+ } | {
116
+ op: '==' | '!=' | '<' | '<=' | '>' | '>=';
117
+ a: HelixExpr;
118
+ b: HelixExpr;
119
+ } | {
120
+ op: '+' | '-' | '*' | '/';
121
+ a: HelixExpr;
122
+ b: HelixExpr;
123
+ } | {
124
+ op: 'and' | 'or';
125
+ of: HelixExpr[];
126
+ } | {
127
+ op: 'not';
128
+ of: HelixExpr;
129
+ } | {
130
+ op: 'aggregate';
131
+ scope: 'players' | `zone:${string}` | `entities:${string}`;
132
+ agg: 'count' | 'sum' | 'min' | 'max' | 'avg';
133
+ field?: string;
134
+ where?: HelixExpr;
135
+ as?: string;
136
+ } | {
137
+ op: 'timerRemaining';
138
+ timer: string;
139
+ key?: HelixRef;
140
+ } | {
141
+ op: 'listLength';
142
+ list: string;
143
+ } | {
144
+ op: 'listAt';
145
+ list: string;
146
+ index: HelixExpr;
147
+ } | {
148
+ op: 'count';
149
+ map: string;
150
+ key: string;
151
+ } | {
152
+ op: 'randomPoint';
153
+ min: [number, number, number];
154
+ max: [number, number, number];
155
+ } | {
156
+ op: 'playerCount' | 'random' | 'timeInState';
157
+ };
158
+ export type HelixRef = string | {
159
+ var: string;
160
+ } | {
161
+ op: 'nearestPlayer';
162
+ from: HelixExpr;
163
+ } | {
164
+ op: 'nearestEntity';
165
+ from: HelixExpr;
166
+ kind?: string;
167
+ } | {
168
+ op: 'aggregate';
169
+ scope: 'players' | `zone:${string}` | `entities:${string}`;
170
+ agg: 'argmax' | 'argmin';
171
+ field: string;
172
+ where?: HelixExpr;
173
+ as?: string;
174
+ };
175
+ export type HelixLvalue = string | {
176
+ ref: HelixRef;
177
+ var: string;
178
+ };
179
+ export type HelixBroadcastTarget = 'all' | HelixRef | {
180
+ team: string;
181
+ };
182
+ export type HelixRuleEffect = {
183
+ do: 'add';
184
+ target: HelixLvalue;
185
+ by: HelixExpr;
186
+ } | {
187
+ do: 'set';
188
+ target: HelixLvalue;
189
+ to: HelixExpr;
190
+ } | {
191
+ do: 'setRef';
192
+ target: HelixLvalue;
193
+ to: HelixRef | null;
194
+ } | {
195
+ do: 'teleport' | 'respawn';
196
+ player: HelixRef;
197
+ to: HelixExpr;
198
+ } | {
199
+ do: 'forEachPlayer';
200
+ as: string;
201
+ where?: HelixExpr;
202
+ then: HelixRuleEffect[];
203
+ } | {
204
+ do: 'forEachEntity';
205
+ kind: string;
206
+ as: string;
207
+ where?: HelixExpr;
208
+ then: HelixRuleEffect[];
209
+ } | {
210
+ do: 'transitionTo';
211
+ phase: string;
212
+ } | {
213
+ do: 'startTimer';
214
+ timer: string;
215
+ seconds: number;
216
+ key?: HelixRef;
217
+ } | {
218
+ do: 'cancelTimer';
219
+ timer: string;
220
+ key?: HelixRef;
221
+ } | {
222
+ do: 'broadcast';
223
+ event: string;
224
+ to: HelixBroadcastTarget;
225
+ payload?: Record<string, HelixExpr | HelixRef>;
226
+ } | {
227
+ do: 'spawnEntity';
228
+ kind: string;
229
+ at: HelixExpr;
230
+ vars?: Record<string, HelixExpr | HelixRef>;
231
+ bind?: 'spawned';
232
+ } | {
233
+ do: 'destroyEntity';
234
+ entity: HelixRef;
235
+ } | {
236
+ do: 'requestOwnership' | 'takeover';
237
+ entity: HelixRef;
238
+ to: HelixRef | null;
239
+ } | {
240
+ do: 'append';
241
+ target: string;
242
+ value: HelixExpr;
243
+ } | {
244
+ do: 'clear';
245
+ target: string;
246
+ } | {
247
+ do: 'addCount';
248
+ target: string;
249
+ key: string;
250
+ by: HelixExpr;
251
+ };
252
+ export type HelixRule = {
253
+ when: HelixRuleEvent;
254
+ if?: HelixExpr;
255
+ then: HelixRuleEffect[];
256
+ };
257
+ export type HelixStateMachine = {
258
+ initial: string;
259
+ phases: string[];
260
+ joinPolicy?: Record<string, {
261
+ joinable?: boolean;
262
+ onLateJoin?: HelixRuleEffect[];
263
+ }>;
264
+ };
265
+ export type HelixTimer = {
266
+ keyed?: 'player' | `entity:${string}`;
267
+ };
268
+ export type HelixFieldType = {
269
+ type: HelixVarType['type'];
270
+ of?: 'player' | `entity:${string}`;
271
+ };
272
+ export type HelixEventDecl = {
273
+ payload?: Record<string, HelixFieldType>;
274
+ };
275
+ export type HelixActionArgType = {
276
+ type: 'number';
277
+ min?: number;
278
+ max?: number;
279
+ integer?: boolean;
280
+ } | {
281
+ type: 'string';
282
+ maxLen?: number;
283
+ enum?: string[];
284
+ } | {
285
+ type: 'boolean';
286
+ } | {
287
+ type: 'vec3';
288
+ } | {
289
+ type: 'ref';
290
+ of: 'player' | `entity:${string}`;
291
+ };
292
+ export type HelixActionDecl = {
293
+ args?: Record<string, HelixActionArgType>;
294
+ };
295
+ export type HelixZone = {
296
+ id: string;
297
+ tracks?: 'players' | `entity:${string}`;
298
+ requireDwell?: number;
299
+ debounce?: number;
300
+ } & ({
301
+ shape: 'box';
302
+ center: [number, number, number];
303
+ size: [number, number, number];
304
+ } | {
305
+ shape: 'sphere';
306
+ center: [number, number, number];
307
+ radius: number;
308
+ });
309
+ export type HelixEntityMotion = {
310
+ type: 'static';
311
+ } | {
312
+ type: 'linear';
313
+ velocity: [number, number, number];
314
+ } | {
315
+ type: 'orbit';
316
+ center: [number, number, number];
317
+ radius: number;
318
+ speed: number;
319
+ } | {
320
+ type: 'waypoints';
321
+ points: [number, number, number][];
322
+ speed: number;
323
+ loop?: boolean;
324
+ } | {
325
+ type: 'seek';
326
+ target: string;
327
+ speed: number;
328
+ };
329
+ export type HelixAttachedZone = {
330
+ offset?: [number, number, number];
331
+ tracks?: 'players' | `entity:${string}`;
332
+ requireDwell?: number;
333
+ debounce?: number;
334
+ } & ({
335
+ shape: 'box';
336
+ size: [number, number, number];
337
+ } | {
338
+ shape: 'sphere';
339
+ radius: number;
340
+ });
341
+ export type HelixEntity = {
342
+ vars?: HelixVarDecls;
343
+ motion?: HelixEntityMotion;
344
+ zone?: HelixAttachedZone;
345
+ authority?: 'server' | 'owner';
346
+ maxSpeed?: number;
347
+ shared?: boolean;
348
+ idleTimeout?: number;
349
+ ownerLifecycle?: 'despawnWithOwner' | 'persist' | 'migrateToServer' | 'hostMigrate';
350
+ transferPolicy?: 'fixed' | 'request' | 'takeover';
351
+ };
352
+ export type HelixMultiplayerConfig = {
353
+ /** Advisory interest radius in meters (drives later StateView interest management; may be ignored in v1). */
354
+ interestRadius?: number;
355
+ /**
356
+ * Minimum players the world needs to be playable (Tier 2 Phase 4.5, spec §14). Default 1 = single-player-safe:
357
+ * the single-player gate then warns (strict → error) if a phase transition is gated on playerCount ≥ 2, since a
358
+ * solo player would be soft-locked. Set ≥ 2 to declare a genuinely multiplayer-only world (the gate stands down).
359
+ */
360
+ minPlayers?: number;
361
+ /** Server owns authoritative state + validates inputs (Tier 1). Defaulted to true; leave it true in v1. */
362
+ authoritative?: boolean;
363
+ /** Declared per-world custom state (roomVars + per-player vars); the room realizes + interprets it. */
364
+ state?: HelixMultiplayerState;
365
+ /** Declared entity archetypes (Tier 2 Phase 4, spec §9): kind name -> { vars }. spawnEntity/destroyEntity reference them; the room realizes per-kind schema + syncs the entities collection. */
366
+ entities?: Record<string, HelixEntity>;
367
+ /** Declared room-scoped state machine (Tier 2 Phase 3, spec §8): the phase set + the initial phase. */
368
+ states?: HelixStateMachine;
369
+ /** Declared timers (Tier 2 Phase 3, spec §8): name -> {}. startTimer/cancelTimer/timerElapsed/timerRemaining reference them. */
370
+ timers?: Record<string, HelixTimer>;
371
+ /** Declared static spatial zones (Tier 2 Phase 2.3, spec §6) — the room tests membership each tick. */
372
+ zones?: HelixZone[];
373
+ /** Declared server→client broadcast events (Tier 2 Phase 2.5, spec §10.3): name -> payload schema. */
374
+ events?: Record<string, HelixEventDecl>;
375
+ /** Declared client→server actions (Tier 2 Phase 2.5b, spec §11.5): name -> { typed arg schema }. */
376
+ actions?: Record<string, HelixActionDecl>;
377
+ /** Declared behavior rules (Tier 2 Phase 2, spec §7) — the room evaluates them in declared order per tick. */
378
+ rules?: HelixRule[];
379
+ };
24
380
  /**
25
381
  * contentRating → main-backend ContentRating. 'unrated' has no counterpart there (their enum is
26
382
  * Everyone/Teen/Mature) — an unrated bundle cannot federate into the catalog until it is rated.
27
383
  */
28
384
  export declare const MAIN_BACKEND_CONTENT_RATING: Record<HelixContentRating, 'Everyone' | 'Teen' | 'Mature' | null>;
29
385
  export type HelixManifest = {
30
- helixVersion: '0.1' | '0.2';
386
+ helixVersion: '0.1' | '0.2' | '0.3';
31
387
  title: string;
32
388
  slug: string;
33
389
  entry: string;
34
390
  maxPlayers?: number;
35
391
  permissions?: HelixPermission[];
392
+ /** v0.3: present only on multiplayer worlds. */
393
+ multiplayer?: HelixMultiplayerConfig;
36
394
  supportsMobile?: boolean;
37
395
  requiresAuth?: boolean;
38
396
  contentRating?: HelixContentRating;
@@ -45,15 +403,84 @@ export type HelixManifest = {
45
403
  /** v0.2: pinned ability dependencies (slug -> semver range), resolved + baked at build. */
46
404
  abilities?: Record<string, string>;
47
405
  };
48
- export type NormalizedHelixManifest = Required<Omit<HelixManifest, 'systems' | 'abilities'>> & Pick<HelixManifest, 'systems' | 'abilities'>;
406
+ export type NormalizedHelixManifest = Required<Omit<HelixManifest, 'systems' | 'abilities' | 'multiplayer'>> & Pick<HelixManifest, 'systems' | 'abilities' | 'multiplayer'>;
49
407
  export type ManifestValidationResult = {
50
408
  valid: true;
51
409
  manifest: NormalizedHelixManifest;
410
+ warnings: string[];
52
411
  } | {
53
412
  valid: false;
54
413
  errors: string[];
55
414
  };
56
- export declare function validateManifest(data: unknown): ManifestValidationResult;
415
+ export declare const PLATFORM_MAX_PLAYERS_PER_ROOM = 24;
416
+ export declare const MULTIPLAYER_CAPS: {
417
+ /** Declared room-level vars. */
418
+ readonly roomVars: 64;
419
+ /** Declared per-player vars (these are realized once PER PLAYER at runtime, so the effective cost scales). */
420
+ readonly playerVars: 64;
421
+ /** Ceiling on a string var's declared `maxLen` (bounds a single synced string). */
422
+ readonly stringMaxLen: 1024;
423
+ /** Values in a string var's `enum`. */
424
+ readonly enumValues: 64;
425
+ /** Ceiling on a `list` collection var's declared `maxLen` (4.5.13 — bounds a single synced array). */
426
+ readonly listMaxLen: 256;
427
+ /** Keys in a `counterMap` collection var's declared key enum (4.5.13). */
428
+ readonly counterKeys: 64;
429
+ /** Declared behavior rules (Tier 2 Phase 2). */
430
+ readonly rules: 128;
431
+ /** Effects in a single rule's `then`. */
432
+ readonly ruleThen: 16;
433
+ /** Max nesting depth of a condition expression (`if`). */
434
+ readonly exprDepth: 8;
435
+ /** Max total nodes in a single condition expression. */
436
+ readonly exprNodes: 64;
437
+ /** Declared state-machine phases (Tier 2 Phase 3, spec §8). One room-scoped machine; this caps its phase set. */
438
+ readonly phases: 32;
439
+ /** Declared timers (Tier 2 Phase 3, spec §8). Each active timer (room-scoped, or × live members when keyed in
440
+ * 3.3) reifies a deadline into synced state — this caps the declared name count. */
441
+ readonly timers: 32;
442
+ /** Floor on a `startTimer` duration (seconds) = one sim tick (1/20s). A shorter timer can't resolve below the
443
+ * tick rate; rejecting it stops a 0s timer from same-tick start→elapse churn. MUST track the room's SIM_HZ. */
444
+ readonly timerMinSeconds: 0.05;
445
+ /** Effects in a single phase's `joinPolicy.onLateJoin` (Tier 2 Phase 3.5). Runs once per late join (≤ player
446
+ * cap/tick), so it's bounded like a playerJoin rule's `then`. */
447
+ readonly joinPolicyEffects: 16;
448
+ /** §3.6/§11.3 cascade-drain depth — the longest within-tick chain of stateEnter/stateExit → transitionTo →
449
+ * stateEnter… Computed statically at publish (a cycle is rejected outright); a config whose chain exceeds
450
+ * this fails publish. Bounds the ×(1+maxCascadeDepth) budget term + MUST stay ≤ the room's MAX_CASCADE_DEPTH
451
+ * runtime backstop (helix-colyseus-server src/tuning.ts). */
452
+ readonly maxCascadeDepth: 16;
453
+ /** Declared zones (Tier 2 Phase 2.3). Membership is O(members × zones)/tick, both capped. */
454
+ readonly zones: 64;
455
+ /** Declared broadcast events (Tier 2 Phase 2.5). */
456
+ readonly events: 64;
457
+ /** Fields in a single broadcast event's payload. */
458
+ readonly broadcastFields: 16;
459
+ /** Declared client→server actions (Tier 2 Phase 2.5b). */
460
+ readonly actions: 64;
461
+ /** Args in a single action's schema. */
462
+ readonly actionArgs: 16;
463
+ /** Declared entity archetypes (Tier 2 Phase 4, spec §9). */
464
+ readonly entityKinds: 32;
465
+ /** Max declared vars per entity kind (realized once PER live entity at runtime, so the effective cost scales). */
466
+ readonly entityVars: 64;
467
+ /** Max live entities of a single kind in a room — bounds the synced entities collection + per-tick entity work.
468
+ * Enforced at RUNTIME (a spawn over the cap is skipped + counted in the H3 metric, never throws). */
469
+ readonly entitiesPerKind: 256;
470
+ /** Ceiling on a zone's extent — any box dimension or a sphere radius, in meters. A sanity bound (a zone
471
+ * test is O(1) regardless of size), generous enough for whole-level kill-planes. */
472
+ readonly zoneMaxExtent: 10000;
473
+ /** §11.2 per-tick budget — the worst-case node-evaluations across ALL rules in one tick: Σ over rules of
474
+ * fireCount(event) × ruleCost, where reductions (aggregate/nearest*) + forEach fanout are weighted by the
475
+ * player cap and contact events by playerCap², all ×(1 + maxCascadeDepth) for the Phase-3 cascade drain
476
+ * (analyzeCascade computes the cycle-free longest chain). Over this → publish fails. PROVISIONAL: a generous
477
+ * bound that catches pathological configs; the precise number wants the S6 load test. */
478
+ readonly tickNodeBudget: 100000;
479
+ };
480
+ export declare const VAR_TYPES: readonly ["number", "string", "boolean", "vec3", "ref", "list", "counterMap"];
481
+ export declare function validateManifest(data: unknown, opts?: {
482
+ strict?: boolean;
483
+ }): ManifestValidationResult;
57
484
  export declare function parseManifest(json: string): ManifestValidationResult;
58
485
  export declare const PACKAGE_ENVELOPE_VERSION: "1";
59
486
  export type HelixPackageEnvelope = {