@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.
@@ -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": { "enum": ["number", "string", "boolean"] }, "maxLen": { "type": "integer", "minimum": 1 } }, "description": "Tier 2 Phase 4.5.13: a bounded ordered array of one scalar element type (roomVars/playerVars only). Starts empty; append/clear mutate, listLength/listAt read." },
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; roomVars/playerVars only). addCount/clear mutate, count reads." }
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": { "type": "string", "pattern": "^(room|self)\\.[A-Za-z][A-Za-z0-9_]{0,31}$" } } },
274
- { "type": "object", "additionalProperties": false, "required": ["op", "list", "index"], "properties": { "op": { "const": "listAt" }, "list": { "type": "string", "pattern": "^(room|self)\\.[A-Za-z][A-Za-z0-9_]{0,31}$" }, "index": { "$ref": "#/$defs/expr" } } },
275
- { "type": "object", "additionalProperties": false, "required": ["op", "map", "key"], "properties": { "op": { "const": "count" }, "map": { "type": "string", "pattern": "^(room|self)\\.[A-Za-z][A-Za-z0-9_]{0,31}$" }, "key": { "type": "string", "minLength": 1 } } },
276
- { "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}." }
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). Its argmax|argmin agg is disjoint from expr's scalar aggs, so it composes into oneOfs without overlap.",
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). The two sides don't overlap refOp's argmax|argmin agg is disjoint from expr's scalar aggs, and nearestPlayer isn't an expr.",
296
- "oneOf": [{ "$ref": "#/$defs/expr" }, { "$ref": "#/$defs/refOp" }]
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
- "type": "object",
301
- "additionalProperties": false,
302
- "required": ["type"],
303
- "properties": { "type": { "enum": ["number", "string", "boolean", "vec3", "ref"] }, "of": { "type": "string", "pattern": "^(player|entity:[a-z0-9][a-z0-9-]*)$" } }
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": { "type": "number", "exclusiveMinimum": 0 }, "key": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refExpr" }] } } },
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": { "type": "string", "pattern": "^(room|self)\\.[A-Za-z][A-Za-z0-9_]{0,31}$" }, "value": { "$ref": "#/$defs/expr" } } },
379
- { "type": "object", "additionalProperties": false, "required": ["do", "target"], "properties": { "do": { "const": "clear" }, "target": { "type": "string", "pattern": "^(room|self)\\.[A-Za-z][A-Za-z0-9_]{0,31}$" } } },
380
- { "type": "object", "additionalProperties": false, "required": ["do", "target", "key", "by"], "properties": { "do": { "const": "addCount" }, "target": { "type": "string", "pattern": "^(room|self)\\.[A-Za-z][A-Za-z0-9_]{0,31}$" }, "key": { "type": "string", "minLength": 1 }, "by": { "$ref": "#/$defs/expr" } } }
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
  }