@helixdev/helix-manifest 0.3.0-staging.13 → 0.3.0-staging.14
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/schema/manifest-v0.3.schema.json +99 -24
- package/dist/src/index.d.ts +169 -13
- package/dist/src/index.js +821 -89
- package/dist/src/index.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/schema/manifest-v0.3.schema.json +99 -24
|
@@ -58,6 +58,12 @@
|
|
|
58
58
|
"minimum": 1,
|
|
59
59
|
"description": "Minimum players the world needs to be playable (default 1 = single-player-safe). When 1, the single-player gate warns (strict = error) if a phase transition is gated on playerCount >= 2. Set >= 2 to declare a multiplayer-only world."
|
|
60
60
|
},
|
|
61
|
+
"uploadHz": {
|
|
62
|
+
"type": "integer",
|
|
63
|
+
"enum": [10, 20],
|
|
64
|
+
"default": 10,
|
|
65
|
+
"description": "Client->server state upload rate: 10 (default) or 20 Hz, raising both the player-seat and hosted-entity channels. 20 Hz requires maxPlayers <= 12 (enforced by the validator). The platform still owns the sim/patch rate."
|
|
66
|
+
},
|
|
61
67
|
"authoritative": {
|
|
62
68
|
"type": "boolean",
|
|
63
69
|
"default": true,
|
|
@@ -185,10 +191,35 @@
|
|
|
185
191
|
{ "type": "object", "additionalProperties": false, "required": ["type", "default"], "properties": { "type": { "const": "boolean" }, "default": { "type": "boolean" } } },
|
|
186
192
|
{ "type": "object", "additionalProperties": false, "required": ["type", "default"], "properties": { "type": { "const": "vec3" }, "default": { "type": "array", "items": { "type": "number" }, "minItems": 3, "maxItems": 3 } } },
|
|
187
193
|
{ "type": "object", "additionalProperties": false, "required": ["type", "of"], "properties": { "type": { "const": "ref" }, "of": { "type": "string", "pattern": "^(player|entity:[a-z0-9][a-z0-9-]*)$" }, "default": { "type": "null" } } },
|
|
188
|
-
{ "type": "object", "additionalProperties": false, "required": ["type", "of", "maxLen"], "properties": { "type": { "const": "list" }, "of": { "
|
|
189
|
-
{ "type": "object", "additionalProperties": false, "required": ["type", "keys"], "properties": { "type": { "const": "counterMap" }, "keys": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } }, "description": "Tier 2 Phase 4.5.13: a closed-enum-keyed number map (each key 0 at start
|
|
194
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "of", "maxLen"], "properties": { "type": { "const": "list" }, "of": { "$ref": "#/$defs/listElem" }, "maxLen": { "type": "integer", "minimum": 1 } }, "description": "Tier 2 (4.5.13 + P3 Collections): a bounded ordered array whose element is a scalar, a ref, or a flat record. Starts empty; append/removeAt/removeWhere/setField/clear mutate, listLength/listAt/listCount/listIndexOf read." },
|
|
195
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "keys"], "properties": { "type": { "const": "counterMap" }, "keys": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } }, "description": "Tier 2 Phase 4.5.13: a closed-enum-keyed number map (each key 0 at start). addCount/clear mutate, count reads." }
|
|
196
|
+
]
|
|
197
|
+
},
|
|
198
|
+
"listElem": {
|
|
199
|
+
"description": "A list's element type (P3 Collections): a scalar shorthand (number/string/boolean), a ref element, or a flat record of scalar fields (no nested collections/records — spec §13 ceiling).",
|
|
200
|
+
"oneOf": [
|
|
201
|
+
{ "enum": ["number", "string", "boolean"] },
|
|
202
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "of"], "properties": { "type": { "const": "ref" }, "of": { "type": "string", "pattern": "^(player|entity:[a-z0-9][a-z0-9-]*)$" } } },
|
|
203
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "fields"], "properties": { "type": { "const": "record" }, "fields": { "type": "object", "minProperties": 1, "propertyNames": { "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "additionalProperties": { "$ref": "#/$defs/recordField" } } } }
|
|
204
|
+
]
|
|
205
|
+
},
|
|
206
|
+
"recordField": {
|
|
207
|
+
"description": "A flat scalar field of a record-list element (P3 Collections): the §5 scalar vocabulary with an OPTIONAL default; a ref field declares `of`. No nested collection/record.",
|
|
208
|
+
"oneOf": [
|
|
209
|
+
{ "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "const": "number" }, "default": { "type": "number" }, "min": { "type": "number" }, "max": { "type": "number" }, "integer": { "type": "boolean" } } },
|
|
210
|
+
{ "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "const": "string" }, "default": { "type": "string" }, "maxLen": { "type": "integer", "minimum": 1 }, "enum": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } } },
|
|
211
|
+
{ "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "const": "boolean" }, "default": { "type": "boolean" } } },
|
|
212
|
+
{ "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "const": "vec3" }, "default": { "type": "array", "items": { "type": "number" }, "minItems": 3, "maxItems": 3 } } },
|
|
213
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "of"], "properties": { "type": { "const": "ref" }, "of": { "type": "string", "pattern": "^(player|entity:[a-z0-9][a-z0-9-]*)$" } } }
|
|
190
214
|
]
|
|
191
215
|
},
|
|
216
|
+
"recordLiteral": {
|
|
217
|
+
"description": "A record-list element literal (P3 Collections): field name -> a value Expr or a Ref. Validated against the list's declared record fields by the publish validator.",
|
|
218
|
+
"type": "object",
|
|
219
|
+
"minProperties": 1,
|
|
220
|
+
"propertyNames": { "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" },
|
|
221
|
+
"additionalProperties": { "$ref": "#/$defs/payloadValue" }
|
|
222
|
+
},
|
|
192
223
|
"timerDecl": {
|
|
193
224
|
"description": "A declared timer (spec §8). Room-scoped by default ({}); `keyed:'player'` makes it per-player — startTimer/cancelTimer/timerRemaining then require a `key` (a player ref) and timerElapsed binds `self`. Keyed-ness is a static property of the declared name (every use must agree).",
|
|
194
225
|
"type": "object",
|
|
@@ -208,7 +239,9 @@
|
|
|
208
239
|
"shared": { "type": "boolean", "description": "spec §9 (Phase 4.8): the entity belongs to the game, not a player — one client is its simulation host. Requires authority:'owner' + ownerLifecycle:'hostMigrate'; the server assigns the least-loaded client at spawn and re-elects on the host's leave (involuntary host migration)." },
|
|
209
240
|
"idleTimeout": { "type": "number", "exclusiveMinimum": 0, "description": "spec §9 (Phase 4.8, optional): seconds a shared entity may stay hostless (frozen, no eligible host) before despawning." },
|
|
210
241
|
"ownerLifecycle": { "enum": ["despawnWithOwner", "persist", "migrateToServer", "hostMigrate"], "description": "spec §9. Single-owner: despawnWithOwner (default) destroys on the owner's leave; persist leaves it frozen; migrateToServer freezes during a transient grace absence + the same owner resumes. Shared: hostMigrate (requires shared:true) — re-elect the least-loaded client on the host's leave." },
|
|
211
|
-
"transferPolicy": { "enum": ["fixed", "request", "takeover"], "description": "spec §9 (Phase 4.5.12): voluntary ownership transfer policy the requestOwnership/takeover verbs honor. 'fixed' (default) = the verbs no-op. 'request' = grant only when unowned (polite pickup). 'takeover' = also steal from a live owner. Requires authority:'owner'. A transfer bumps the entity's authorityEpoch + fires ownershipChanged." }
|
|
242
|
+
"transferPolicy": { "enum": ["fixed", "request", "takeover"], "description": "spec §9 (Phase 4.5.12): voluntary ownership transfer policy the requestOwnership/takeover verbs honor. 'fixed' (default) = the verbs no-op. 'request' = grant only when unowned (polite pickup). 'takeover' = also steal from a live owner. Requires authority:'owner'. A transfer bumps the entity's authorityEpoch + fires ownershipChanged." },
|
|
243
|
+
"physics": { "$ref": "#/$defs/entityPhysics" },
|
|
244
|
+
"rejoinOnRelease": { "enum": ["nearestPoint", "timeIndex", "seekBack"], "description": "networked-physics (Phase 3.5): how a HYBRID kind (physics + server motion) re-anchors its declared path when a client RELEASES it back to the server. 'nearestPoint' (default) re-bases the path to the closest point so it doesn't snap; 'timeIndex' resumes the original time origin; 'seekBack' eases toward the path. Only meaningful with both physics and a non-static motion." }
|
|
212
245
|
}
|
|
213
246
|
},
|
|
214
247
|
"attachedZone": {
|
|
@@ -229,6 +262,33 @@
|
|
|
229
262
|
{ "type": "object", "additionalProperties": false, "required": ["type", "points", "speed"], "properties": { "type": { "const": "waypoints" }, "points": { "type": "array", "minItems": 2, "items": { "type": "array", "items": { "type": "number" }, "minItems": 3, "maxItems": 3 } }, "speed": { "type": "number", "exclusiveMinimum": 0 }, "loop": { "type": "boolean" } } }
|
|
230
263
|
]
|
|
231
264
|
},
|
|
265
|
+
"entityPhysics": {
|
|
266
|
+
"description": "networked-physics (Phase 0/2): opt-in CLIENT-simulated dynamic rigid body. Present only on authority:'owner' kinds; absent ⇒ today's kinematic/position-only path (no Rapier body, no velocity/orientation on the wire, near-zero cost). The owner simulates it locally and uploads position+velocity+orientation; remotes reproduce it (snap-into-sim + decaying render offset). Every sub-capability defaults OFF and composes per kind.",
|
|
267
|
+
"type": "object",
|
|
268
|
+
"additionalProperties": false,
|
|
269
|
+
"required": ["bodyType", "shape"],
|
|
270
|
+
"properties": {
|
|
271
|
+
"bodyType": { "const": "dynamic", "description": "Only 'dynamic' — kinematic/static bodies stay expressed by `motion`/none. 'dynamic' is the new networked-physics path." },
|
|
272
|
+
"shape": {
|
|
273
|
+
"description": "The collider shape (and dimensions, meters) the body simulates with. Mirror the visual on the client.",
|
|
274
|
+
"oneOf": [
|
|
275
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "radius"], "properties": { "type": { "const": "sphere" }, "radius": { "type": "number", "exclusiveMinimum": 0 } } },
|
|
276
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "halfExtents"], "properties": { "type": { "const": "box" }, "halfExtents": { "type": "array", "items": { "type": "number", "exclusiveMinimum": 0 }, "minItems": 3, "maxItems": 3 } } },
|
|
277
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "halfHeight", "radius"], "properties": { "type": { "const": "capsule" }, "halfHeight": { "type": "number", "exclusiveMinimum": 0 }, "radius": { "type": "number", "exclusiveMinimum": 0 } } }
|
|
278
|
+
]
|
|
279
|
+
},
|
|
280
|
+
"mass": { "type": "number", "exclusiveMinimum": 0, "description": "kg, default 1." },
|
|
281
|
+
"restitution": { "type": "number", "minimum": 0, "maximum": 1, "description": "0..1 bounciness, default 0.2." },
|
|
282
|
+
"friction": { "type": "number", "minimum": 0, "description": "default 0.5." },
|
|
283
|
+
"linearDamping": { "type": "number", "minimum": 0, "description": "default 0." },
|
|
284
|
+
"angularDamping": { "type": "number", "minimum": 0, "description": "default 0.05." },
|
|
285
|
+
"maxAngularSpeed": { "type": "number", "exclusiveMinimum": 0, "description": "rad/s gate ceiling for angular velocity; default derived from maxSpeed." },
|
|
286
|
+
"collidesWith": { "type": "array", "items": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,31}$" }, "description": "entity kinds this body collides with; default ⇒ all physics bodies + world geometry. Each entry must be a declared kind." },
|
|
287
|
+
"claimOnContact": { "type": "boolean", "description": "Phase 3.2: the server auto-hands this (simulated) body's authority to a contacting owner. Requires transferPolicy:'takeover'. Default false." },
|
|
288
|
+
"revertOnRest": { "type": "boolean", "description": "Phase 3.4: auto-release to the server (controller='') when the body comes to rest. Default off for 'fixed', on for 'shared' custody." },
|
|
289
|
+
"dualSimOnContact": { "type": "boolean", "description": "Phase 4: controlled×controlled — each client locally sims BOTH bodies + reconciles. Requires transferPolicy:'fixed'. Default false." }
|
|
290
|
+
}
|
|
291
|
+
},
|
|
232
292
|
"zone": {
|
|
233
293
|
"description": "A declared static spatial volume (spec §6), discriminated on `shape`. center is [x,y,z] in meters; a box uses full `size` [x,y,z], a sphere uses `radius`. tracks defaults to 'players'; 'entity:<kind>' tests entities of that kind, binding self = the entering entity (4.5.8). requireDwell/debounce (seconds) harden against teleport-touch and are enforced from Phase 2.4.",
|
|
234
294
|
"type": "object",
|
|
@@ -262,25 +322,32 @@
|
|
|
262
322
|
{ "type": "string" },
|
|
263
323
|
{ "type": "boolean" },
|
|
264
324
|
{ "type": "object", "additionalProperties": false, "required": ["vec3"], "properties": { "vec3": { "type": "array", "items": { "type": "number" }, "minItems": 3, "maxItems": 3 } } },
|
|
265
|
-
{ "type": "object", "additionalProperties": false, "required": ["var"], "properties": { "var": { "type": "string", "pattern": "^((room|self)\\.[A-Za-z][A-Za-z0-9_]{0,31}|action\\.args\\.[A-Za-z][A-Za-z0-9_]{0,31})$" } } },
|
|
325
|
+
{ "type": "object", "additionalProperties": false, "required": ["var"], "properties": { "var": { "type": "string", "pattern": "^((room|self)\\.[A-Za-z][A-Za-z0-9_]{0,31}|action\\.args\\.[A-Za-z][A-Za-z0-9_]{0,31}|[A-Za-z][A-Za-z0-9_]{0,31})$" } }, "description": "A var value read: \"room.<v>\" / \"self.<v>\" / \"action.args.<a>\", or a bare name = a SCALAR list element bound by forEachInList / listCount / listIndexOf (§5 P3 ext). The bare form resolves only against a list-element binding; an unbound bare name is rejected by the validator." },
|
|
266
326
|
{ "$ref": "#/$defs/refLvalue" },
|
|
267
327
|
{ "type": "object", "additionalProperties": false, "required": ["op", "a", "b"], "properties": { "op": { "enum": ["distance", "==", "!=", "<", "<=", ">", ">=", "+", "-", "*", "/"] }, "a": { "$ref": "#/$defs/expr" }, "b": { "$ref": "#/$defs/expr" } } },
|
|
268
328
|
{ "type": "object", "additionalProperties": false, "required": ["op", "of"], "properties": { "op": { "enum": ["and", "or"] }, "of": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/expr" } } } },
|
|
269
329
|
{ "type": "object", "additionalProperties": false, "required": ["op", "of"], "properties": { "op": { "const": "not" }, "of": { "$ref": "#/$defs/expr" } } },
|
|
270
330
|
{ "type": "object", "additionalProperties": false, "required": ["op", "scope", "agg"], "properties": { "op": { "const": "aggregate" }, "scope": { "type": "string", "pattern": "^(players|zone:[A-Za-z][A-Za-z0-9_]{0,31}|entities:[a-z0-9][a-z0-9-]{0,31})$" }, "agg": { "enum": ["count", "sum", "min", "max", "avg"] }, "field": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "where": { "$ref": "#/$defs/expr" }, "as": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" } } },
|
|
271
|
-
{ "type": "object", "additionalProperties": false, "required": ["op"], "properties": { "op": { "enum": ["playerCount", "random", "timeInState"] } } },
|
|
331
|
+
{ "type": "object", "additionalProperties": false, "required": ["op"], "properties": { "op": { "enum": ["playerCount", "random", "timeInState", "now"] } } },
|
|
272
332
|
{ "type": "object", "additionalProperties": false, "required": ["op", "timer"], "properties": { "op": { "const": "timerRemaining" }, "timer": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "key": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refExpr" }] } } },
|
|
273
|
-
{ "type": "object", "additionalProperties": false, "required": ["op", "list"], "properties": { "op": { "const": "listLength" }, "list": { "
|
|
274
|
-
{ "type": "object", "additionalProperties": false, "required": ["op", "list", "index"], "properties": { "op": { "const": "listAt" }, "list": { "type": "string", "pattern": "^
|
|
275
|
-
{ "type": "object", "additionalProperties": false, "required": ["op", "map", "key"], "properties": { "op": { "const": "count" }, "map": { "
|
|
276
|
-
{ "type": "object", "additionalProperties": false, "required": ["op", "
|
|
333
|
+
{ "type": "object", "additionalProperties": false, "required": ["op", "list"], "properties": { "op": { "const": "listLength" }, "list": { "$ref": "#/$defs/lvalue" } } },
|
|
334
|
+
{ "type": "object", "additionalProperties": false, "required": ["op", "list", "index"], "properties": { "op": { "const": "listAt" }, "list": { "$ref": "#/$defs/lvalue" }, "index": { "$ref": "#/$defs/expr" }, "field": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" } } },
|
|
335
|
+
{ "type": "object", "additionalProperties": false, "required": ["op", "map", "key"], "properties": { "op": { "const": "count" }, "map": { "$ref": "#/$defs/lvalue" }, "key": { "type": "string", "minLength": 1 } } },
|
|
336
|
+
{ "type": "object", "additionalProperties": false, "required": ["op", "list", "as", "where"], "properties": { "op": { "enum": ["listCount", "listIndexOf"] }, "list": { "$ref": "#/$defs/lvalue" }, "as": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "where": { "$ref": "#/$defs/expr" } }, "description": "§5 (P3 Collections ext): a bounded element scan binding `as` to the element. listCount → how many match `where`; listIndexOf → the first matching index, or -1. The `where` reads the bound element (scalar {var:as}, record field {ref:as,var:field}, ref element as a Ref) and can't itself reduce." },
|
|
337
|
+
{ "type": "object", "additionalProperties": false, "required": ["op", "min", "max"], "properties": { "op": { "const": "randomPoint" }, "min": { "type": "array", "items": { "type": "number" }, "minItems": 3, "maxItems": 3 }, "max": { "type": "array", "items": { "type": "number" }, "minItems": 3, "maxItems": 3 } }, "description": "Tier 2: a uniformly-random vec3 inside the [min,max] box (each component independent). Server-evaluated each call; vec3-typed. Use for random spawns: respawn to {op:randomPoint,min,max}." },
|
|
338
|
+
{ "type": "object", "additionalProperties": false, "required": ["op", "entity", "by"], "properties": { "op": { "const": "controlledBy" }, "entity": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refExpr" }] }, "by": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refExpr" }] } }, "description": "§9 (P3 Ownership/A3): true iff `entity`'s controller is the player `by` resolves to — boolean. O(1) field read." },
|
|
339
|
+
{ "type": "object", "additionalProperties": false, "required": ["op", "of"], "properties": { "op": { "const": "hostLoad" }, "of": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refExpr" }] } }, "description": "§9 (P3 Ownership/B8): the count of live entities the player `of` controls (its sim/host load) — number. O(entities)." },
|
|
340
|
+
{ "type": "object", "additionalProperties": false, "required": ["op", "a", "b"], "properties": { "op": { "const": "sameRef" }, "a": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refExpr" }] }, "b": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refExpr" }] } }, "description": "§7.3 (P3-B2): true iff two Refs resolve to the same live member (ref identity); false if either is unset. e.g. self is the turn-order current actor." }
|
|
277
341
|
]
|
|
278
342
|
},
|
|
279
343
|
"refOp": {
|
|
280
|
-
"description": "A ref-RETURNING op (spec §7.3): nearestPlayer (nearest to a point) / aggregate argmax|argmin (the member with the max/min field)
|
|
344
|
+
"description": "A ref-RETURNING op (spec §7.3/§9): nearestPlayer (nearest to a point) / nearestEntity / aggregate argmax|argmin (the member with the max/min field) / controllerOf (P3: the player controlling an entity). Each has a distinct `op` const, so it composes into oneOfs without overlap (argmax|argmin is also disjoint from expr's scalar aggs).",
|
|
281
345
|
"oneOf": [
|
|
282
346
|
{ "type": "object", "additionalProperties": false, "required": ["op", "from"], "properties": { "op": { "const": "nearestPlayer" }, "from": { "$ref": "#/$defs/expr" } } },
|
|
283
347
|
{ "type": "object", "additionalProperties": false, "required": ["op", "from"], "properties": { "op": { "const": "nearestEntity" }, "from": { "$ref": "#/$defs/expr" }, "kind": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,31}$" } } },
|
|
348
|
+
{ "type": "object", "additionalProperties": false, "required": ["op", "entity"], "properties": { "op": { "const": "controllerOf" }, "entity": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refExpr" }] } } },
|
|
349
|
+
{ "type": "object", "additionalProperties": false, "required": ["op"], "properties": { "op": { "const": "leastLoadedPlayer" }, "where": { "$ref": "#/$defs/expr" }, "as": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" } }, "description": "§9 (P3 Ownership/B8): the connected player controlling the fewest entities (optional where/as filter) — a player ref. O(players)." },
|
|
350
|
+
{ "type": "object", "additionalProperties": false, "required": ["op", "list", "index"], "properties": { "op": { "const": "listAt" }, "list": { "$ref": "#/$defs/lvalue" }, "index": { "$ref": "#/$defs/expr" } }, "description": "§5 (P3-B2 + P3 ext): a `list of ref` element at `index` AS a Ref (list is a room/self path OR {ref,var}) — the turn-order current actor. '' (null) out-of-range. O(1)." },
|
|
284
351
|
{ "type": "object", "additionalProperties": false, "required": ["op", "scope", "agg", "field"], "properties": { "op": { "const": "aggregate" }, "scope": { "type": "string", "pattern": "^(players|zone:[A-Za-z][A-Za-z0-9_]{0,31}|entities:[a-z0-9][a-z0-9-]{0,31})$" }, "agg": { "enum": ["argmax", "argmin"] }, "field": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "where": { "$ref": "#/$defs/expr" }, "as": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" } } }
|
|
285
352
|
]
|
|
286
353
|
},
|
|
@@ -292,15 +359,17 @@
|
|
|
292
359
|
]
|
|
293
360
|
},
|
|
294
361
|
"payloadValue": {
|
|
295
|
-
"description": "A broadcast payload field value (spec §10.3): a value Expr, or a ref-returning op (a Ref serializes to its playerKey on the wire).
|
|
296
|
-
"
|
|
362
|
+
"description": "A broadcast payload field value (spec §10.3): a value Expr, or a ref-returning op (a Ref serializes to its playerKey on the wire). anyOf (not oneOf): a field-less `listAt` is structurally valid as BOTH an expr (scalar-list read) and a refOp (ref-list read) — they're disambiguated by the list's declared element type in checkBroadcast/validateExpr, not structurally.",
|
|
363
|
+
"anyOf": [{ "$ref": "#/$defs/expr" }, { "$ref": "#/$defs/refOp" }]
|
|
297
364
|
},
|
|
298
365
|
"fieldType": {
|
|
299
|
-
"description": "A field type in an events payload (spec §10.3): the §5 type vocabulary with NO default (the value is rule-computed + sent, never stored). A ref field declares `of`.",
|
|
300
|
-
"
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
366
|
+
"description": "A field type in an events payload (spec §10.3): the §5 type vocabulary with NO default (the value is rule-computed + sent, never stored). A ref field declares `of`. P3 (B6): a `list`/`counterMap` collection-snapshot field whose value references a matching collection var.",
|
|
367
|
+
"oneOf": [
|
|
368
|
+
{ "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "enum": ["number", "string", "boolean", "vec3"] } } },
|
|
369
|
+
{ "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "const": "ref" }, "of": { "type": "string", "pattern": "^(player|entity:[a-z0-9][a-z0-9-]*)$" } } },
|
|
370
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "of"], "properties": { "type": { "const": "list" }, "of": { "$ref": "#/$defs/listElem" } } },
|
|
371
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "keys"], "properties": { "type": { "const": "counterMap" }, "keys": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } } }
|
|
372
|
+
]
|
|
304
373
|
},
|
|
305
374
|
"eventDecl": {
|
|
306
375
|
"description": "A declared server→client broadcast event (spec §10.3): an optional typed payload (field name -> fieldType).",
|
|
@@ -351,7 +420,7 @@
|
|
|
351
420
|
{ "type": "object", "additionalProperties": false, "required": ["on", "phase"], "properties": { "on": { "enum": ["stateEnter", "stateExit"] }, "phase": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" } } },
|
|
352
421
|
{ "type": "object", "additionalProperties": false, "required": ["on", "timer"], "properties": { "on": { "const": "timerElapsed" }, "timer": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" } } },
|
|
353
422
|
{ "type": "object", "additionalProperties": false, "required": ["on", "kind"], "properties": { "on": { "enum": ["entitySpawn", "entityDestroy", "ownershipChanged"] }, "kind": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,31}$" } } },
|
|
354
|
-
{ "type": "object", "additionalProperties": false, "required": ["on", "scope", "var", "cmp", "value"], "properties": { "on": { "const": "varReached" }, "scope": { "enum": ["room", "self"] }, "var": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "cmp": { "enum": ["==", "!=", "<", "<=", ">", ">="] }, "value": { "type": ["number", "string", "boolean"] } } }
|
|
423
|
+
{ "type": "object", "additionalProperties": false, "required": ["on", "scope", "var", "cmp", "value"], "properties": { "on": { "const": "varReached" }, "scope": { "enum": ["room", "self", "entity"] }, "kind": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,31}$" }, "var": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "cmp": { "enum": ["==", "!=", "<", "<=", ">", ">="] }, "value": { "oneOf": [{ "type": ["number", "string", "boolean"] }, { "type": "object", "additionalProperties": false, "required": ["var"], "properties": { "var": { "type": "string", "pattern": "^((room|self)\\.[A-Za-z][A-Za-z0-9_]{0,31}|action\\.args\\.[A-Za-z][A-Za-z0-9_]{0,31})$" } } }, { "$ref": "#/$defs/refLvalue" }] } } }
|
|
355
424
|
] },
|
|
356
425
|
{ "type": "object", "required": ["on"], "properties": { "on": { "type": "string", "not": { "enum": ["tick", "playerJoin", "playerLeave", "playerDisconnect", "playerReconnect", "zoneEnter", "zoneExit", "zoneInside", "playerContact", "action", "stateEnter", "stateExit", "timerElapsed", "entitySpawn", "entityDestroy", "ownershipChanged", "varReached"] } } } }
|
|
357
426
|
]
|
|
@@ -366,20 +435,26 @@
|
|
|
366
435
|
{ "type": "object", "additionalProperties": false, "required": ["do", "target", "to"], "properties": { "do": { "const": "set" }, "target": { "$ref": "#/$defs/lvalue" }, "to": { "$ref": "#/$defs/expr" } } },
|
|
367
436
|
{ "type": "object", "additionalProperties": false, "required": ["do", "target", "to"], "properties": { "do": { "const": "setRef" }, "target": { "$ref": "#/$defs/lvalue" }, "to": { "oneOf": [{ "type": "string" }, { "type": "null" }, { "$ref": "#/$defs/refExpr" }] } } },
|
|
368
437
|
{ "type": "object", "additionalProperties": false, "required": ["do", "player", "to"], "properties": { "do": { "enum": ["teleport", "respawn"] }, "player": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refExpr" }] }, "to": { "$ref": "#/$defs/expr" } } },
|
|
369
|
-
{ "type": "object", "additionalProperties": false, "required": ["do", "as", "then"], "properties": { "do": { "const": "forEachPlayer" }, "as": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "where": { "$ref": "#/$defs/expr" }, "then": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/ruleEffect" } } } },
|
|
438
|
+
{ "type": "object", "additionalProperties": false, "required": ["do", "as", "then"], "properties": { "do": { "const": "forEachPlayer" }, "as": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "where": { "$ref": "#/$defs/expr" }, "includeEliminated": { "type": "boolean", "description": "§7.4 (P3-B2): when true, also iterate eliminated (active:false) connected players — for a round reset / announcement. Default false (the active set only)." }, "then": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/ruleEffect" } } } },
|
|
370
439
|
{ "type": "object", "additionalProperties": false, "required": ["do", "kind", "as", "then"], "properties": { "do": { "const": "forEachEntity" }, "kind": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,31}$" }, "as": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "where": { "$ref": "#/$defs/expr" }, "then": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/ruleEffect" } } } },
|
|
440
|
+
{ "type": "object", "additionalProperties": false, "required": ["do", "list", "as", "then"], "properties": { "do": { "const": "forEachInList" }, "list": { "$ref": "#/$defs/lvalue" }, "as": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "where": { "$ref": "#/$defs/expr" }, "then": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/ruleEffect" } } }, "description": "§5 (P3 Collections ext): the bounded single-level loop over a list's elements — `as` binds the element (a scalar value via {var:as}; a Ref for a ref list; a record element whose fields read via {ref:as,var:field} / write via setField `as`). Can't nest a loop or structurally mutate the list it iterates." },
|
|
371
441
|
{ "type": "object", "additionalProperties": false, "required": ["do", "phase"], "properties": { "do": { "const": "transitionTo" }, "phase": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" } } },
|
|
372
|
-
{ "type": "object", "additionalProperties": false, "required": ["do", "timer", "seconds"], "properties": { "do": { "const": "startTimer" }, "timer": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "seconds": { "
|
|
442
|
+
{ "type": "object", "additionalProperties": false, "required": ["do", "timer", "seconds"], "properties": { "do": { "const": "startTimer" }, "timer": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "seconds": { "$ref": "#/$defs/expr", "description": "Timer duration in SECONDS — a number Expr (a literal is the degenerate case). A literal below the one-tick floor fails publish; a sub-floor/NaN/∞/negative EXPRESSION result is clamped/skipped at runtime (spec §3.2)." }, "key": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refExpr" }] } } },
|
|
373
443
|
{ "type": "object", "additionalProperties": false, "required": ["do", "timer"], "properties": { "do": { "const": "cancelTimer" }, "timer": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "key": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refExpr" }] } } },
|
|
374
444
|
{ "type": "object", "additionalProperties": false, "required": ["do", "event", "to"], "properties": { "do": { "const": "broadcast" }, "event": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "to": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refOp" }, { "type": "object", "additionalProperties": false, "required": ["team"], "properties": { "team": { "type": "string", "minLength": 3 } } }] }, "payload": { "type": "object", "propertyNames": { "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "additionalProperties": { "$ref": "#/$defs/payloadValue" } } } },
|
|
375
445
|
{ "type": "object", "additionalProperties": false, "required": ["do", "kind", "at"], "properties": { "do": { "const": "spawnEntity" }, "kind": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,31}$" }, "at": { "$ref": "#/$defs/expr" }, "vars": { "type": "object", "propertyNames": { "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "additionalProperties": { "$ref": "#/$defs/payloadValue" } }, "bind": { "const": "spawned" } } },
|
|
376
446
|
{ "type": "object", "additionalProperties": false, "required": ["do", "entity"], "properties": { "do": { "const": "destroyEntity" }, "entity": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refExpr" }] } } },
|
|
377
447
|
{ "type": "object", "additionalProperties": false, "required": ["do", "entity", "to"], "properties": { "do": { "enum": ["requestOwnership", "takeover"] }, "entity": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refExpr" }] }, "to": { "oneOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }, { "$ref": "#/$defs/refExpr" }] } } },
|
|
378
|
-
{ "type": "object", "additionalProperties": false, "required": ["do", "target", "value"], "properties": { "do": { "const": "append" }, "target": { "
|
|
379
|
-
{ "type": "object", "additionalProperties": false, "required": ["do", "target"], "properties": { "do": { "const": "clear" }, "target": { "
|
|
380
|
-
{ "type": "object", "additionalProperties": false, "required": ["do", "target", "key", "by"], "properties": { "do": { "const": "addCount" }, "target": { "
|
|
448
|
+
{ "type": "object", "additionalProperties": false, "required": ["do", "target", "value"], "properties": { "do": { "const": "append" }, "target": { "$ref": "#/$defs/lvalue" }, "value": { "anyOf": [{ "$ref": "#/$defs/payloadValue" }, { "$ref": "#/$defs/recordLiteral" }] } } },
|
|
449
|
+
{ "type": "object", "additionalProperties": false, "required": ["do", "target"], "properties": { "do": { "const": "clear" }, "target": { "$ref": "#/$defs/lvalue" } } },
|
|
450
|
+
{ "type": "object", "additionalProperties": false, "required": ["do", "target", "key", "by"], "properties": { "do": { "const": "addCount" }, "target": { "$ref": "#/$defs/lvalue" }, "key": { "type": "string", "minLength": 1 }, "by": { "$ref": "#/$defs/expr" } } },
|
|
451
|
+
{ "type": "object", "additionalProperties": false, "required": ["do", "target", "index"], "properties": { "do": { "const": "removeAt" }, "target": { "$ref": "#/$defs/lvalue" }, "index": { "$ref": "#/$defs/expr" } }, "description": "P3 Collections: remove the element at `index` from a list (shift down); out-of-range = no-op." },
|
|
452
|
+
{ "type": "object", "additionalProperties": false, "required": ["do", "target", "as", "where"], "properties": { "do": { "const": "removeWhere" }, "target": { "$ref": "#/$defs/lvalue" }, "as": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "where": { "$ref": "#/$defs/expr" } }, "description": "§5 (P3 Collections ext): remove ALL elements of a list matching `where` (a bounded scan binding `as` to the element). The scan-and-remove verb the iterated-list-mutation ban points to." },
|
|
453
|
+
{ "type": "object", "additionalProperties": false, "required": ["do", "field", "to"], "properties": { "do": { "const": "setField" }, "target": { "$ref": "#/$defs/lvalue" }, "index": { "$ref": "#/$defs/expr" }, "as": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "field": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "to": { "$ref": "#/$defs/payloadValue" } }, "description": "P3 Collections: write a record-list element's `field` — addressed by `index` into `target`, OR by a record element `as` bound by an enclosing forEachInList (§5 P3 ext)." },
|
|
454
|
+
{ "type": "object", "additionalProperties": false, "required": ["do", "order", "index"], "properties": { "do": { "const": "advanceTurn" }, "order": { "$ref": "#/$defs/lvalue" }, "index": { "$ref": "#/$defs/lvalue" } }, "description": "§7.4 (P3-B2): advance a turn `index` (a number var) over an `order` (`list of ref:player`) to the next active member — wraps + skips eliminated/disconnected. No-op if no other active member exists." },
|
|
455
|
+
{ "type": "object", "additionalProperties": false, "required": ["do", "player"], "properties": { "do": { "enum": ["eliminate", "revive"] }, "player": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refExpr" }] } }, "description": "§7.4 (P3-B2): toggle a player's reserved `active` flag. eliminate removes a still-connected player from the gameplay active set (a spectator — still rendered + reachable by broadcast); revive restores it." }
|
|
381
456
|
] },
|
|
382
|
-
{ "type": "object", "required": ["do"], "properties": { "do": { "type": "string", "not": { "enum": ["add", "set", "setRef", "teleport", "respawn", "forEachPlayer", "forEachEntity", "broadcast", "transitionTo", "startTimer", "cancelTimer", "spawnEntity", "destroyEntity", "requestOwnership", "takeover", "append", "clear", "addCount"] } } } }
|
|
457
|
+
{ "type": "object", "required": ["do"], "properties": { "do": { "type": "string", "not": { "enum": ["add", "set", "setRef", "teleport", "respawn", "forEachPlayer", "forEachEntity", "forEachInList", "broadcast", "transitionTo", "startTimer", "cancelTimer", "spawnEntity", "destroyEntity", "requestOwnership", "takeover", "append", "clear", "addCount", "removeAt", "removeWhere", "setField", "advanceTurn", "eliminate", "revive"] } } } }
|
|
383
458
|
]
|
|
384
459
|
}
|
|
385
460
|
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -51,12 +51,40 @@ export type HelixVarType = {
|
|
|
51
51
|
default?: null;
|
|
52
52
|
} | {
|
|
53
53
|
type: 'list';
|
|
54
|
-
of:
|
|
54
|
+
of: HelixListElem;
|
|
55
55
|
maxLen: number;
|
|
56
56
|
} | {
|
|
57
57
|
type: 'counterMap';
|
|
58
58
|
keys: string[];
|
|
59
59
|
};
|
|
60
|
+
export type HelixRecordField = {
|
|
61
|
+
type: 'number';
|
|
62
|
+
default?: number;
|
|
63
|
+
min?: number;
|
|
64
|
+
max?: number;
|
|
65
|
+
integer?: boolean;
|
|
66
|
+
} | {
|
|
67
|
+
type: 'string';
|
|
68
|
+
default?: string;
|
|
69
|
+
maxLen?: number;
|
|
70
|
+
enum?: string[];
|
|
71
|
+
} | {
|
|
72
|
+
type: 'boolean';
|
|
73
|
+
default?: boolean;
|
|
74
|
+
} | {
|
|
75
|
+
type: 'vec3';
|
|
76
|
+
default?: [number, number, number];
|
|
77
|
+
} | {
|
|
78
|
+
type: 'ref';
|
|
79
|
+
of: 'player' | `entity:${string}`;
|
|
80
|
+
};
|
|
81
|
+
export type HelixListElem = 'number' | 'string' | 'boolean' | {
|
|
82
|
+
type: 'ref';
|
|
83
|
+
of: 'player' | `entity:${string}`;
|
|
84
|
+
} | {
|
|
85
|
+
type: 'record';
|
|
86
|
+
fields: Record<string, HelixRecordField>;
|
|
87
|
+
};
|
|
60
88
|
/** A declared bag of custom vars: name -> type. Names are identifiers (see the v0.3 schema pattern). */
|
|
61
89
|
export type HelixVarDecls = Record<string, HelixVarType>;
|
|
62
90
|
/** v0.3 (Tier 2 Phase 1.3): the per-world DECLARED state — room-level + per-player custom vars. */
|
|
@@ -96,10 +124,17 @@ export type HelixRuleEvent = {
|
|
|
96
124
|
kind: string;
|
|
97
125
|
} | {
|
|
98
126
|
on: 'varReached';
|
|
99
|
-
scope: 'room' | 'self';
|
|
127
|
+
scope: 'room' | 'self' | 'entity';
|
|
128
|
+
kind?: string;
|
|
100
129
|
var: string;
|
|
101
130
|
cmp: '==' | '!=' | '<' | '<=' | '>' | '>=';
|
|
102
|
-
value:
|
|
131
|
+
value: VarReachedThreshold;
|
|
132
|
+
};
|
|
133
|
+
export type VarReachedThreshold = number | string | boolean | {
|
|
134
|
+
var: string;
|
|
135
|
+
} | {
|
|
136
|
+
ref: HelixRef;
|
|
137
|
+
var: string;
|
|
103
138
|
};
|
|
104
139
|
export type HelixExpr = number | string | boolean | {
|
|
105
140
|
vec3: [number, number, number];
|
|
@@ -139,19 +174,43 @@ export type HelixExpr = number | string | boolean | {
|
|
|
139
174
|
key?: HelixRef;
|
|
140
175
|
} | {
|
|
141
176
|
op: 'listLength';
|
|
142
|
-
list:
|
|
177
|
+
list: HelixLvalue;
|
|
143
178
|
} | {
|
|
144
179
|
op: 'listAt';
|
|
145
|
-
list:
|
|
180
|
+
list: HelixLvalue;
|
|
146
181
|
index: HelixExpr;
|
|
182
|
+
field?: string;
|
|
147
183
|
} | {
|
|
148
184
|
op: 'count';
|
|
149
|
-
map:
|
|
185
|
+
map: HelixLvalue;
|
|
150
186
|
key: string;
|
|
187
|
+
} | {
|
|
188
|
+
op: 'listCount';
|
|
189
|
+
list: HelixLvalue;
|
|
190
|
+
as: string;
|
|
191
|
+
where: HelixExpr;
|
|
192
|
+
} | {
|
|
193
|
+
op: 'listIndexOf';
|
|
194
|
+
list: HelixLvalue;
|
|
195
|
+
as: string;
|
|
196
|
+
where: HelixExpr;
|
|
151
197
|
} | {
|
|
152
198
|
op: 'randomPoint';
|
|
153
199
|
min: [number, number, number];
|
|
154
200
|
max: [number, number, number];
|
|
201
|
+
} | {
|
|
202
|
+
op: 'controlledBy';
|
|
203
|
+
entity: HelixRef;
|
|
204
|
+
by: HelixRef;
|
|
205
|
+
} | {
|
|
206
|
+
op: 'sameRef';
|
|
207
|
+
a: HelixRef;
|
|
208
|
+
b: HelixRef;
|
|
209
|
+
} | {
|
|
210
|
+
op: 'hostLoad';
|
|
211
|
+
of: HelixRef;
|
|
212
|
+
} | {
|
|
213
|
+
op: 'now';
|
|
155
214
|
} | {
|
|
156
215
|
op: 'playerCount' | 'random' | 'timeInState';
|
|
157
216
|
};
|
|
@@ -164,6 +223,17 @@ export type HelixRef = string | {
|
|
|
164
223
|
op: 'nearestEntity';
|
|
165
224
|
from: HelixExpr;
|
|
166
225
|
kind?: string;
|
|
226
|
+
} | {
|
|
227
|
+
op: 'controllerOf';
|
|
228
|
+
entity: HelixRef;
|
|
229
|
+
} | {
|
|
230
|
+
op: 'leastLoadedPlayer';
|
|
231
|
+
where?: HelixExpr;
|
|
232
|
+
as?: string;
|
|
233
|
+
} | {
|
|
234
|
+
op: 'listAt';
|
|
235
|
+
list: HelixLvalue;
|
|
236
|
+
index: HelixExpr;
|
|
167
237
|
} | {
|
|
168
238
|
op: 'aggregate';
|
|
169
239
|
scope: 'players' | `zone:${string}` | `entities:${string}`;
|
|
@@ -179,6 +249,7 @@ export type HelixLvalue = string | {
|
|
|
179
249
|
export type HelixBroadcastTarget = 'all' | HelixRef | {
|
|
180
250
|
team: string;
|
|
181
251
|
};
|
|
252
|
+
export type HelixRecordLiteral = Record<string, HelixExpr | HelixRef>;
|
|
182
253
|
export type HelixRuleEffect = {
|
|
183
254
|
do: 'add';
|
|
184
255
|
target: HelixLvalue;
|
|
@@ -199,6 +270,7 @@ export type HelixRuleEffect = {
|
|
|
199
270
|
do: 'forEachPlayer';
|
|
200
271
|
as: string;
|
|
201
272
|
where?: HelixExpr;
|
|
273
|
+
includeEliminated?: boolean;
|
|
202
274
|
then: HelixRuleEffect[];
|
|
203
275
|
} | {
|
|
204
276
|
do: 'forEachEntity';
|
|
@@ -206,13 +278,19 @@ export type HelixRuleEffect = {
|
|
|
206
278
|
as: string;
|
|
207
279
|
where?: HelixExpr;
|
|
208
280
|
then: HelixRuleEffect[];
|
|
281
|
+
} | {
|
|
282
|
+
do: 'forEachInList';
|
|
283
|
+
list: HelixLvalue;
|
|
284
|
+
as: string;
|
|
285
|
+
where?: HelixExpr;
|
|
286
|
+
then: HelixRuleEffect[];
|
|
209
287
|
} | {
|
|
210
288
|
do: 'transitionTo';
|
|
211
289
|
phase: string;
|
|
212
290
|
} | {
|
|
213
291
|
do: 'startTimer';
|
|
214
292
|
timer: string;
|
|
215
|
-
seconds:
|
|
293
|
+
seconds: HelixExpr;
|
|
216
294
|
key?: HelixRef;
|
|
217
295
|
} | {
|
|
218
296
|
do: 'cancelTimer';
|
|
@@ -238,16 +316,39 @@ export type HelixRuleEffect = {
|
|
|
238
316
|
to: HelixRef | null;
|
|
239
317
|
} | {
|
|
240
318
|
do: 'append';
|
|
241
|
-
target:
|
|
242
|
-
value: HelixExpr;
|
|
319
|
+
target: HelixLvalue;
|
|
320
|
+
value: HelixExpr | HelixRef | HelixRecordLiteral;
|
|
243
321
|
} | {
|
|
244
322
|
do: 'clear';
|
|
245
|
-
target:
|
|
323
|
+
target: HelixLvalue;
|
|
246
324
|
} | {
|
|
247
325
|
do: 'addCount';
|
|
248
|
-
target:
|
|
326
|
+
target: HelixLvalue;
|
|
249
327
|
key: string;
|
|
250
328
|
by: HelixExpr;
|
|
329
|
+
} | {
|
|
330
|
+
do: 'removeAt';
|
|
331
|
+
target: HelixLvalue;
|
|
332
|
+
index: HelixExpr;
|
|
333
|
+
} | {
|
|
334
|
+
do: 'removeWhere';
|
|
335
|
+
target: HelixLvalue;
|
|
336
|
+
as: string;
|
|
337
|
+
where: HelixExpr;
|
|
338
|
+
} | {
|
|
339
|
+
do: 'setField';
|
|
340
|
+
target?: HelixLvalue;
|
|
341
|
+
index?: HelixExpr;
|
|
342
|
+
as?: string;
|
|
343
|
+
field: string;
|
|
344
|
+
to: HelixExpr | HelixRef;
|
|
345
|
+
} | {
|
|
346
|
+
do: 'advanceTurn';
|
|
347
|
+
order: HelixLvalue;
|
|
348
|
+
index: HelixLvalue;
|
|
349
|
+
} | {
|
|
350
|
+
do: 'eliminate' | 'revive';
|
|
351
|
+
player: HelixRef;
|
|
251
352
|
};
|
|
252
353
|
export type HelixRule = {
|
|
253
354
|
when: HelixRuleEvent;
|
|
@@ -266,8 +367,16 @@ export type HelixTimer = {
|
|
|
266
367
|
keyed?: 'player' | `entity:${string}`;
|
|
267
368
|
};
|
|
268
369
|
export type HelixFieldType = {
|
|
269
|
-
type:
|
|
270
|
-
|
|
370
|
+
type: 'number' | 'string' | 'boolean' | 'vec3';
|
|
371
|
+
} | {
|
|
372
|
+
type: 'ref';
|
|
373
|
+
of: 'player' | `entity:${string}`;
|
|
374
|
+
} | {
|
|
375
|
+
type: 'list';
|
|
376
|
+
of: HelixListElem;
|
|
377
|
+
} | {
|
|
378
|
+
type: 'counterMap';
|
|
379
|
+
keys: string[];
|
|
271
380
|
};
|
|
272
381
|
export type HelixEventDecl = {
|
|
273
382
|
payload?: Record<string, HelixFieldType>;
|
|
@@ -326,6 +435,30 @@ export type HelixEntityMotion = {
|
|
|
326
435
|
target: string;
|
|
327
436
|
speed: number;
|
|
328
437
|
};
|
|
438
|
+
export type HelixEntityPhysics = {
|
|
439
|
+
bodyType: 'dynamic';
|
|
440
|
+
shape: {
|
|
441
|
+
type: 'sphere';
|
|
442
|
+
radius: number;
|
|
443
|
+
} | {
|
|
444
|
+
type: 'box';
|
|
445
|
+
halfExtents: [number, number, number];
|
|
446
|
+
} | {
|
|
447
|
+
type: 'capsule';
|
|
448
|
+
halfHeight: number;
|
|
449
|
+
radius: number;
|
|
450
|
+
};
|
|
451
|
+
mass?: number;
|
|
452
|
+
restitution?: number;
|
|
453
|
+
friction?: number;
|
|
454
|
+
linearDamping?: number;
|
|
455
|
+
angularDamping?: number;
|
|
456
|
+
maxAngularSpeed?: number;
|
|
457
|
+
collidesWith?: string[];
|
|
458
|
+
claimOnContact?: boolean;
|
|
459
|
+
revertOnRest?: boolean;
|
|
460
|
+
dualSimOnContact?: boolean;
|
|
461
|
+
};
|
|
329
462
|
export type HelixAttachedZone = {
|
|
330
463
|
offset?: [number, number, number];
|
|
331
464
|
tracks?: 'players' | `entity:${string}`;
|
|
@@ -348,6 +481,8 @@ export type HelixEntity = {
|
|
|
348
481
|
idleTimeout?: number;
|
|
349
482
|
ownerLifecycle?: 'despawnWithOwner' | 'persist' | 'migrateToServer' | 'hostMigrate';
|
|
350
483
|
transferPolicy?: 'fixed' | 'request' | 'takeover';
|
|
484
|
+
physics?: HelixEntityPhysics;
|
|
485
|
+
rejoinOnRelease?: 'nearestPoint' | 'timeIndex' | 'seekBack';
|
|
351
486
|
};
|
|
352
487
|
export type HelixMultiplayerConfig = {
|
|
353
488
|
/** Advisory interest radius in meters (drives later StateView interest management; may be ignored in v1). */
|
|
@@ -358,6 +493,13 @@ export type HelixMultiplayerConfig = {
|
|
|
358
493
|
* solo player would be soft-locked. Set ≥ 2 to declare a genuinely multiplayer-only world (the gate stands down).
|
|
359
494
|
*/
|
|
360
495
|
minPlayers?: number;
|
|
496
|
+
/**
|
|
497
|
+
* Client→server state upload rate tier: 10 (default) or 20 Hz. Raises BOTH the player-seat `state` channel and
|
|
498
|
+
* hosted-entity batch uploads together. 20 Hz ⇒ maxPlayers must be ≤ MULTIPLAYER_CAPS.maxPlayersAt20Hz (12) — the
|
|
499
|
+
* ~2× upstream cost is bounded by the player cap. Absent ⇒ 10 (normalized via the schema default). The platform
|
|
500
|
+
* still owns the SIM/patch rate (20 Hz); this is only the upstream cadence.
|
|
501
|
+
*/
|
|
502
|
+
uploadHz?: 10 | 20;
|
|
361
503
|
/** Server owns authoritative state + validates inputs (Tier 1). Defaulted to true; leave it true in v1. */
|
|
362
504
|
authoritative?: boolean;
|
|
363
505
|
/** Declared per-world custom state (roomVars + per-player vars); the room realizes + interprets it. */
|
|
@@ -414,6 +556,14 @@ export type ManifestValidationResult = {
|
|
|
414
556
|
};
|
|
415
557
|
export declare const PLATFORM_MAX_PLAYERS_PER_ROOM = 24;
|
|
416
558
|
export declare const MULTIPLAYER_CAPS: {
|
|
559
|
+
/** Accepted `multiplayer.uploadHz` tiers (client→server upload cadence). 10 = default, 20 = opt-in fast tier.
|
|
560
|
+
* SYNC: mirrors the contract's MESSAGE_RATE.{stateHz=10, maxUploadHz=20} (helix-sdk multiplayer-contract) — the
|
|
561
|
+
* manifest takes no SDK dep, so widen both in lockstep if a tier is ever added. */
|
|
562
|
+
readonly uploadHzAllowed: readonly [10, 20];
|
|
563
|
+
/** The 20 Hz player cap: `uploadHz:20` requires maxPlayers ≤ this (the ~2× upstream cost is bounded by capacity).
|
|
564
|
+
* SYNC: mirrors the room's ROOM_MAX_CLIENTS_CAP_20HZ (helix-colyseus-server src/tuning.ts) — publish gate here,
|
|
565
|
+
* runtime backstop there. */
|
|
566
|
+
readonly maxPlayersAt20Hz: 12;
|
|
417
567
|
/** Declared room-level vars. */
|
|
418
568
|
readonly roomVars: 64;
|
|
419
569
|
/** Declared per-player vars (these are realized once PER PLAYER at runtime, so the effective cost scales). */
|
|
@@ -426,6 +576,8 @@ export declare const MULTIPLAYER_CAPS: {
|
|
|
426
576
|
readonly listMaxLen: 256;
|
|
427
577
|
/** Keys in a `counterMap` collection var's declared key enum (4.5.13). */
|
|
428
578
|
readonly counterKeys: 64;
|
|
579
|
+
/** Fields in a `list` of `record` element (P3 Collections — bounds one synced record's flat scalar fields). */
|
|
580
|
+
readonly recordFields: 8;
|
|
429
581
|
/** Declared behavior rules (Tier 2 Phase 2). */
|
|
430
582
|
readonly rules: 128;
|
|
431
583
|
/** Effects in a single rule's `then`. */
|
|
@@ -467,6 +619,10 @@ export declare const MULTIPLAYER_CAPS: {
|
|
|
467
619
|
/** Max live entities of a single kind in a room — bounds the synced entities collection + per-tick entity work.
|
|
468
620
|
* Enforced at RUNTIME (a spawn over the cap is skipped + counted in the H3 metric, never throws). */
|
|
469
621
|
readonly entitiesPerKind: 256;
|
|
622
|
+
/** networked-physics (Phase 0): max entity kinds that may declare a `physics` block per world. Bounds the
|
|
623
|
+
* client-side dynamic-body count alongside entitiesPerKind — the worst case a client may simulate is
|
|
624
|
+
* physicsKinds × entitiesPerKind dynamic Rapier bodies, so keep this small (a casual world needs 1–2). */
|
|
625
|
+
readonly physicsKinds: 8;
|
|
470
626
|
/** Ceiling on a zone's extent — any box dimension or a sphere radius, in meters. A sanity bound (a zone
|
|
471
627
|
* test is O(1) regardless of size), generous enough for whole-level kill-planes. */
|
|
472
628
|
readonly zoneMaxExtent: 10000;
|