@hypersoniclabs/helix-manifest 0.3.0 → 0.3.1
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 +359 -0
- package/dist/src/index.d.ts +573 -1
- package/dist/src/index.js +2896 -5
- 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 +359 -0
|
@@ -53,10 +53,87 @@
|
|
|
53
53
|
"exclusiveMinimum": 0,
|
|
54
54
|
"description": "Advisory: meters within which a player is relevant to another (drives later StateView interest management). Advisory in v1 — the room may ignore it until interest management ships."
|
|
55
55
|
},
|
|
56
|
+
"minPlayers": {
|
|
57
|
+
"type": "integer",
|
|
58
|
+
"minimum": 1,
|
|
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
|
+
},
|
|
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
|
+
},
|
|
56
67
|
"authoritative": {
|
|
57
68
|
"type": "boolean",
|
|
58
69
|
"default": true,
|
|
59
70
|
"description": "Whether the room server owns authoritative state and validates inputs (Tier 1). Reserved as a seam; v1 only ships the authoritative model, so leave true."
|
|
71
|
+
},
|
|
72
|
+
"state": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"additionalProperties": false,
|
|
75
|
+
"description": "Declared per-world custom state (Tier 2 Phase 1.3): room-level + per-player vars the room realizes as @colyseus/schema and interprets. Each entry is a var name -> VarType.",
|
|
76
|
+
"properties": {
|
|
77
|
+
"roomVars": { "$ref": "#/$defs/varDecls" },
|
|
78
|
+
"playerVars": { "$ref": "#/$defs/varDecls" }
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"entities": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"propertyNames": { "pattern": "^[a-z0-9][a-z0-9-]{0,31}$" },
|
|
84
|
+
"additionalProperties": { "$ref": "#/$defs/entityDecl" },
|
|
85
|
+
"description": "Declared entity archetypes (Tier 2 Phase 4, spec §9): kind name -> { vars }. The room realizes per-kind @colyseus/schema + syncs the entities collection; spawnEntity/destroyEntity + entitySpawn/entityDestroy reference a kind. Kind names are lowercase (matching a ref's `of: entity:<kind>`)."
|
|
86
|
+
},
|
|
87
|
+
"states": {
|
|
88
|
+
"type": "object",
|
|
89
|
+
"additionalProperties": false,
|
|
90
|
+
"required": ["initial", "phases"],
|
|
91
|
+
"description": "Declared room-scoped state machine (Tier 2 Phase 3, spec §8): `initial` is the phase the room starts in; `phases` is the closed set transitionTo may move between. stateEnter/stateExit rule events fire on a transition; timeInState reads seconds spent in the current phase. `initial` must be one of `phases` (checked at publish).",
|
|
92
|
+
"properties": {
|
|
93
|
+
"initial": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" },
|
|
94
|
+
"phases": { "type": "array", "minItems": 1, "items": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "uniqueItems": true },
|
|
95
|
+
"joinPolicy": {
|
|
96
|
+
"type": "object",
|
|
97
|
+
"propertyNames": { "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" },
|
|
98
|
+
"additionalProperties": {
|
|
99
|
+
"type": "object",
|
|
100
|
+
"additionalProperties": false,
|
|
101
|
+
"properties": {
|
|
102
|
+
"joinable": { "type": "boolean" },
|
|
103
|
+
"onLateJoin": { "type": "array", "items": { "$ref": "#/$defs/ruleEffect" } }
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"description": "Per-phase late-join policy (spec §8): phase name -> { joinable, onLateJoin }. When the current phase is joinable:false, a joiner runs onLateJoin (bind self) instead of the normal playerJoin rules (spectate-until-next-round). Absent / joinable:true → spawn in."
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"timers": {
|
|
111
|
+
"type": "object",
|
|
112
|
+
"propertyNames": { "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" },
|
|
113
|
+
"additionalProperties": { "$ref": "#/$defs/timerDecl" },
|
|
114
|
+
"description": "Declared timers (Tier 2 Phase 3, spec §8): name -> {}. startTimer arms one (deadline reified into synced state), timerElapsed fires once when it passes, cancelTimer clears it, timerRemaining reads the seconds left. Room-scoped in Phase 3.2; the per-player keyed form arrives in 3.3."
|
|
115
|
+
},
|
|
116
|
+
"zones": {
|
|
117
|
+
"type": "array",
|
|
118
|
+
"items": { "$ref": "#/$defs/zone" },
|
|
119
|
+
"description": "Declared static spatial zones (Tier 2 Phase 2.3, spec §6): box/sphere volumes the room tests members against each tick, edge-firing zoneEnter/zoneExit/zoneInside. The no-physics primitive behind checkpoints, kill-planes, finish lines, pickup radii."
|
|
120
|
+
},
|
|
121
|
+
"events": {
|
|
122
|
+
"type": "object",
|
|
123
|
+
"propertyNames": { "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" },
|
|
124
|
+
"additionalProperties": { "$ref": "#/$defs/eventDecl" },
|
|
125
|
+
"description": "Declared server→client broadcast events (Tier 2 Phase 2.5, spec §10.3): name -> { payload schema }. A `broadcast` rule-effect sends one; clients receive it via room.onMessage(name)."
|
|
126
|
+
},
|
|
127
|
+
"actions": {
|
|
128
|
+
"type": "object",
|
|
129
|
+
"propertyNames": { "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" },
|
|
130
|
+
"additionalProperties": { "$ref": "#/$defs/actionDecl" },
|
|
131
|
+
"description": "Declared client→server actions (Tier 2 Phase 2.5b, spec §11.5): name -> { typed arg schema }. A client sends ActionMessage{name,args}; the room validates args against the schema before firing {on:action,name} rules."
|
|
132
|
+
},
|
|
133
|
+
"rules": {
|
|
134
|
+
"type": "array",
|
|
135
|
+
"items": { "$ref": "#/$defs/rule" },
|
|
136
|
+
"description": "Declared behavior rules (Tier 2 Phase 2, spec §7): when/if/then the room evaluates each tick in declared order. Phase-2.1 subset = the tick event + the add effect; later slices widen the grammar."
|
|
60
137
|
}
|
|
61
138
|
}
|
|
62
139
|
},
|
|
@@ -98,5 +175,287 @@
|
|
|
98
175
|
"propertyNames": { "pattern": "^[a-z0-9](?:[a-z0-9-]{1,48})[a-z0-9]$" },
|
|
99
176
|
"description": "Pinned ability dependencies as slug -> semver range (e.g. { \"fly\": \"^0.1\", \"swim\": \"^0.1\" }). Resolved to exact active versions at build; each ability's code + asset CDN base are baked into the immutable build."
|
|
100
177
|
}
|
|
178
|
+
},
|
|
179
|
+
"$defs": {
|
|
180
|
+
"varDecls": {
|
|
181
|
+
"type": "object",
|
|
182
|
+
"propertyNames": { "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" },
|
|
183
|
+
"additionalProperties": { "$ref": "#/$defs/varType" },
|
|
184
|
+
"description": "Declared custom-state vars: name -> VarType. Names are identifiers (a letter then up to 31 letters/digits/underscores)."
|
|
185
|
+
},
|
|
186
|
+
"varType": {
|
|
187
|
+
"description": "A declared variable's type + constraints (Tier 2 declared state, spec §5). Discriminated on `type`.",
|
|
188
|
+
"oneOf": [
|
|
189
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "default"], "properties": { "type": { "const": "number" }, "default": { "type": "number" }, "min": { "type": "number" }, "max": { "type": "number" }, "integer": { "type": "boolean" } } },
|
|
190
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "default"], "properties": { "type": { "const": "string" }, "default": { "type": "string" }, "maxLen": { "type": "integer", "minimum": 1 }, "enum": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } } },
|
|
191
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "default"], "properties": { "type": { "const": "boolean" }, "default": { "type": "boolean" } } },
|
|
192
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "default"], "properties": { "type": { "const": "vec3" }, "default": { "type": "array", "items": { "type": "number" }, "minItems": 3, "maxItems": 3 } } },
|
|
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" } } },
|
|
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-]*)$" } } }
|
|
214
|
+
]
|
|
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
|
+
},
|
|
223
|
+
"timerDecl": {
|
|
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).",
|
|
225
|
+
"type": "object",
|
|
226
|
+
"additionalProperties": false,
|
|
227
|
+
"properties": { "keyed": { "type": "string", "pattern": "^(player|entity:[a-z0-9][a-z0-9-]{0,31})$" } }
|
|
228
|
+
},
|
|
229
|
+
"entityDecl": {
|
|
230
|
+
"description": "A declared entity archetype (spec §9): per-kind custom `vars` (the §5 vocabulary), realized once per live entity, + optional server-authoritative `motion` (Phase 4.2) + attached `zone` (Phase 4.4) + single-owner `ownerLifecycle` (Phase 4.6a) + `authority`/`maxSpeed` (Phase 4.6b). shared-host modes arrive in 4.8.",
|
|
231
|
+
"type": "object",
|
|
232
|
+
"additionalProperties": false,
|
|
233
|
+
"properties": {
|
|
234
|
+
"vars": { "$ref": "#/$defs/varDecls" },
|
|
235
|
+
"motion": { "$ref": "#/$defs/entityMotion" },
|
|
236
|
+
"zone": { "$ref": "#/$defs/attachedZone" },
|
|
237
|
+
"authority": { "enum": ["server", "owner"], "description": "spec §9: 'server' (default) = deterministic kinematics, cheat-proof, never freezes; 'owner' = client-simulated + relayed, spoofable. An 'owner' kind MUST declare maxSpeed and is subject to the publish-time cross-player firewall." },
|
|
238
|
+
"maxSpeed": { "type": "number", "exclusiveMinimum": 0, "description": "spec §9 (required for authority:'owner'): metres/second — the movement-plausibility ceiling the room gates the entity's client-relayed transform against. Ignored on a server-authoritative kind." },
|
|
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)." },
|
|
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." },
|
|
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." },
|
|
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." }
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
"attachedZone": {
|
|
248
|
+
"description": "An entity-attached zone (spec §6/§9, Phase 4.4): a box/sphere volume centered on the entity's transform (+ optional offset), no id/center. Overlap fires zoneEnter/zoneExit/zoneInside addressed by the entity kind, binding self + source. tracks defaults to 'players'; 'entity:<kind>' makes self an entity of that kind (4.5.8).",
|
|
249
|
+
"type": "object",
|
|
250
|
+
"oneOf": [
|
|
251
|
+
{ "type": "object", "additionalProperties": false, "required": ["shape", "size"], "properties": { "shape": { "const": "box" }, "size": { "type": "array", "items": { "type": "number", "exclusiveMinimum": 0 }, "minItems": 3, "maxItems": 3 }, "offset": { "type": "array", "items": { "type": "number" }, "minItems": 3, "maxItems": 3 }, "tracks": { "type": "string", "pattern": "^(players|entity:[a-z0-9][a-z0-9-]*)$" }, "requireDwell": { "type": "number", "minimum": 0 }, "debounce": { "type": "number", "minimum": 0 } } },
|
|
252
|
+
{ "type": "object", "additionalProperties": false, "required": ["shape", "radius"], "properties": { "shape": { "const": "sphere" }, "radius": { "type": "number", "exclusiveMinimum": 0 }, "offset": { "type": "array", "items": { "type": "number" }, "minItems": 3, "maxItems": 3 }, "tracks": { "type": "string", "pattern": "^(players|entity:[a-z0-9][a-z0-9-]*)$" }, "requireDwell": { "type": "number", "minimum": 0 }, "debounce": { "type": "number", "minimum": 0 } } }
|
|
253
|
+
]
|
|
254
|
+
},
|
|
255
|
+
"entityMotion": {
|
|
256
|
+
"description": "Server-authoritative deterministic kinematics (spec §9, Phase 4.2/4.6a): static (no motion), linear (constant velocity, m/s), orbit (a circle in the XZ plane about center at speed rad/s), or seek (step straight toward the position of the member in the entity's own ref var `target`, at `speed` m/s — simple cheat-proof homing). waypoints arrives on demand.",
|
|
257
|
+
"oneOf": [
|
|
258
|
+
{ "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "const": "static" } } },
|
|
259
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "velocity"], "properties": { "type": { "const": "linear" }, "velocity": { "type": "array", "items": { "type": "number" }, "minItems": 3, "maxItems": 3 } } },
|
|
260
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "center", "radius", "speed"], "properties": { "type": { "const": "orbit" }, "center": { "type": "array", "items": { "type": "number" }, "minItems": 3, "maxItems": 3 }, "radius": { "type": "number", "exclusiveMinimum": 0 }, "speed": { "type": "number" } } },
|
|
261
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "target", "speed"], "properties": { "type": { "const": "seek" }, "target": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "speed": { "type": "number", "exclusiveMinimum": 0 } } },
|
|
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" } } }
|
|
263
|
+
]
|
|
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
|
+
},
|
|
292
|
+
"zone": {
|
|
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.",
|
|
294
|
+
"type": "object",
|
|
295
|
+
"required": ["id", "shape", "center"],
|
|
296
|
+
"properties": {
|
|
297
|
+
"id": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" },
|
|
298
|
+
"tracks": { "type": "string", "pattern": "^(players|entity:[a-z0-9][a-z0-9-]*)$" },
|
|
299
|
+
"requireDwell": { "type": "number", "minimum": 0 },
|
|
300
|
+
"debounce": { "type": "number", "minimum": 0 }
|
|
301
|
+
},
|
|
302
|
+
"oneOf": [
|
|
303
|
+
{ "type": "object", "additionalProperties": false, "required": ["id", "shape", "center", "size"], "properties": { "id": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "shape": { "const": "box" }, "center": { "type": "array", "items": { "type": "number" }, "minItems": 3, "maxItems": 3 }, "size": { "type": "array", "items": { "type": "number", "exclusiveMinimum": 0 }, "minItems": 3, "maxItems": 3 }, "tracks": { "type": "string", "pattern": "^(players|entity:[a-z0-9][a-z0-9-]*)$" }, "requireDwell": { "type": "number", "minimum": 0 }, "debounce": { "type": "number", "minimum": 0 } } },
|
|
304
|
+
{ "type": "object", "additionalProperties": false, "required": ["id", "shape", "center", "radius"], "properties": { "id": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "shape": { "const": "sphere" }, "center": { "type": "array", "items": { "type": "number" }, "minItems": 3, "maxItems": 3 }, "radius": { "type": "number", "exclusiveMinimum": 0 }, "tracks": { "type": "string", "pattern": "^(players|entity:[a-z0-9][a-z0-9-]*)$" }, "requireDwell": { "type": "number", "minimum": 0 }, "debounce": { "type": "number", "minimum": 0 } } }
|
|
305
|
+
]
|
|
306
|
+
},
|
|
307
|
+
"rule": {
|
|
308
|
+
"type": "object",
|
|
309
|
+
"additionalProperties": false,
|
|
310
|
+
"required": ["when", "then"],
|
|
311
|
+
"properties": {
|
|
312
|
+
"when": { "$ref": "#/$defs/ruleEvent" },
|
|
313
|
+
"if": { "$ref": "#/$defs/expr" },
|
|
314
|
+
"then": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/ruleEffect" } }
|
|
315
|
+
},
|
|
316
|
+
"description": "A behavior rule: when <event> [if <cond>] then [<effect>...]. The optional `if` is a pure condition expression."
|
|
317
|
+
},
|
|
318
|
+
"expr": {
|
|
319
|
+
"description": "A pure condition/value expression (spec §7.3). Phase-2.2: scalar literals, room/self var reads, comparison + arithmetic + logic, playerCount, random. Phase-2.4a: a vec3 literal {vec3:[x,y,z]}, the self.position built-in (via var), the distance op, and ref-addressed reads {ref,var}. Depth + node count are capped at publish (MULTIPLAYER_CAPS).",
|
|
320
|
+
"oneOf": [
|
|
321
|
+
{ "type": "number" },
|
|
322
|
+
{ "type": "string" },
|
|
323
|
+
{ "type": "boolean" },
|
|
324
|
+
{ "type": "object", "additionalProperties": false, "required": ["vec3"], "properties": { "vec3": { "type": "array", "items": { "type": "number" }, "minItems": 3, "maxItems": 3 } } },
|
|
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." },
|
|
326
|
+
{ "$ref": "#/$defs/refLvalue" },
|
|
327
|
+
{ "type": "object", "additionalProperties": false, "required": ["op", "a", "b"], "properties": { "op": { "enum": ["distance", "==", "!=", "<", "<=", ">", ">=", "+", "-", "*", "/"] }, "a": { "$ref": "#/$defs/expr" }, "b": { "$ref": "#/$defs/expr" } } },
|
|
328
|
+
{ "type": "object", "additionalProperties": false, "required": ["op", "of"], "properties": { "op": { "enum": ["and", "or"] }, "of": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/expr" } } } },
|
|
329
|
+
{ "type": "object", "additionalProperties": false, "required": ["op", "of"], "properties": { "op": { "const": "not" }, "of": { "$ref": "#/$defs/expr" } } },
|
|
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}$" } } },
|
|
331
|
+
{ "type": "object", "additionalProperties": false, "required": ["op"], "properties": { "op": { "enum": ["playerCount", "random", "timeInState", "now"] } } },
|
|
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" }] } } },
|
|
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." }
|
|
341
|
+
]
|
|
342
|
+
},
|
|
343
|
+
"refOp": {
|
|
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).",
|
|
345
|
+
"oneOf": [
|
|
346
|
+
{ "type": "object", "additionalProperties": false, "required": ["op", "from"], "properties": { "op": { "const": "nearestPlayer" }, "from": { "$ref": "#/$defs/expr" } } },
|
|
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)." },
|
|
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}$" } } }
|
|
352
|
+
]
|
|
353
|
+
},
|
|
354
|
+
"refExpr": {
|
|
355
|
+
"description": "The object forms of a Ref (spec §7.1): a ref-typed var read {var} (deref a stored ref), or a ref-returning op (refOp). A bare string Ref (a bound name) is handled separately by each slot.",
|
|
356
|
+
"oneOf": [
|
|
357
|
+
{ "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})$" } } },
|
|
358
|
+
{ "$ref": "#/$defs/refOp" }
|
|
359
|
+
]
|
|
360
|
+
},
|
|
361
|
+
"payloadValue": {
|
|
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" }]
|
|
364
|
+
},
|
|
365
|
+
"fieldType": {
|
|
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
|
+
]
|
|
373
|
+
},
|
|
374
|
+
"eventDecl": {
|
|
375
|
+
"description": "A declared server→client broadcast event (spec §10.3): an optional typed payload (field name -> fieldType).",
|
|
376
|
+
"type": "object",
|
|
377
|
+
"additionalProperties": false,
|
|
378
|
+
"properties": { "payload": { "type": "object", "propertyNames": { "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "additionalProperties": { "$ref": "#/$defs/fieldType" } } }
|
|
379
|
+
},
|
|
380
|
+
"actionArgType": {
|
|
381
|
+
"description": "A client-action argument type (spec §11.5): the §5 type vocabulary WITH validation constraints but NO default (the client supplies the value). A ref arg declares `of`. The room validates a client's args against this before any {on:action} rule fires.",
|
|
382
|
+
"oneOf": [
|
|
383
|
+
{ "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "const": "number" }, "min": { "type": "number" }, "max": { "type": "number" }, "integer": { "type": "boolean" } } },
|
|
384
|
+
{ "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "const": "string" }, "maxLen": { "type": "integer", "minimum": 1 }, "enum": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } } },
|
|
385
|
+
{ "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "const": "boolean" } } },
|
|
386
|
+
{ "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "const": "vec3" } } },
|
|
387
|
+
{ "type": "object", "additionalProperties": false, "required": ["type", "of"], "properties": { "type": { "const": "ref" }, "of": { "type": "string", "pattern": "^(player|entity:[a-z0-9][a-z0-9-]*)$" } } }
|
|
388
|
+
]
|
|
389
|
+
},
|
|
390
|
+
"actionDecl": {
|
|
391
|
+
"description": "A declared client→server action (spec §11.5): an optional typed arg schema (arg name -> actionArgType).",
|
|
392
|
+
"type": "object",
|
|
393
|
+
"additionalProperties": false,
|
|
394
|
+
"properties": { "args": { "type": "object", "propertyNames": { "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" }, "additionalProperties": { "$ref": "#/$defs/actionArgType" } } }
|
|
395
|
+
},
|
|
396
|
+
"refLvalue": {
|
|
397
|
+
"description": "A ref-addressed var slot (spec §7.1): a member's var. `ref` is a bound name (self/other) or a Ref object (refExpr); `var` is a bare identifier (a declared player var or the `position` built-in).",
|
|
398
|
+
"type": "object",
|
|
399
|
+
"additionalProperties": false,
|
|
400
|
+
"required": ["ref", "var"],
|
|
401
|
+
"properties": { "ref": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refExpr" }] }, "var": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" } }
|
|
402
|
+
},
|
|
403
|
+
"lvalue": {
|
|
404
|
+
"description": "An effect target (spec §7.1/§7.4): a string scope-path \"room.<var>\"/\"self.<var>\", or the ref-addressed write-through-ref {ref,var}.",
|
|
405
|
+
"oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refLvalue" }]
|
|
406
|
+
},
|
|
407
|
+
"ruleEvent": {
|
|
408
|
+
"description": "The rule trigger (spec §7.2). tick = room-level; playerJoin/playerLeave bind `self`; zoneEnter/zoneExit/zoneInside bind `self` and reference a declared zone (§6); playerContact binds `self`+`other` and fires once per entering pair within `radius`. More events arrive in later slices. (Phase 4.9) An unrecognized `on` passes the schema (the permissive branch below); a reserved future event then publishes with a warning + a typo hard-errors with did-you-mean — classified cross-field in checkFutureNodes. Keep the permissive-branch enum in sync with KNOWN_EVENTS in index.ts.",
|
|
409
|
+
"type": "object",
|
|
410
|
+
"required": ["on"],
|
|
411
|
+
"anyOf": [
|
|
412
|
+
{ "oneOf": [
|
|
413
|
+
{ "type": "object", "additionalProperties": false, "required": ["on"], "properties": { "on": { "const": "tick" }, "everyN": { "type": "integer", "minimum": 1 } } },
|
|
414
|
+
{ "type": "object", "additionalProperties": false, "required": ["on"], "properties": { "on": { "const": "playerJoin" } } },
|
|
415
|
+
{ "type": "object", "additionalProperties": false, "required": ["on"], "properties": { "on": { "const": "playerLeave" } } },
|
|
416
|
+
{ "type": "object", "additionalProperties": false, "required": ["on"], "properties": { "on": { "enum": ["playerDisconnect", "playerReconnect"] } } },
|
|
417
|
+
{ "type": "object", "additionalProperties": false, "required": ["on", "zone"], "properties": { "on": { "enum": ["zoneEnter", "zoneExit", "zoneInside"] }, "zone": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" } } },
|
|
418
|
+
{ "type": "object", "additionalProperties": false, "required": ["on", "radius"], "properties": { "on": { "const": "playerContact" }, "radius": { "type": "number", "exclusiveMinimum": 0 } } },
|
|
419
|
+
{ "type": "object", "additionalProperties": false, "required": ["on", "name"], "properties": { "on": { "const": "action" }, "name": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" } } },
|
|
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}$" } } },
|
|
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}$" } } },
|
|
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}$" } } },
|
|
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" }] } } }
|
|
424
|
+
] },
|
|
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"] } } } }
|
|
426
|
+
]
|
|
427
|
+
},
|
|
428
|
+
"ruleEffect": {
|
|
429
|
+
"description": "A then-effect (spec §7.4). add: number var += number. set: var ← expr value. setRef: ref var ← a bound ref name (e.g. \"self\") or null. Any target may be a ref-addressed {ref,var} write-through-ref (Phase 2.4a). teleport/respawn write a member's authoritative position (player is a Ref, to a vec3). forEachPlayer is the one bounded loop (single level, binds `as`, optional `where`). More effects arrive in later slices. (Phase 4.9) An unrecognized `do` passes the schema (the permissive branch below); a reserved future effect then publishes with a warning + a typo hard-errors with did-you-mean — classified cross-field in checkFutureNodes. Keep the permissive-branch enum in sync with KNOWN_EFFECTS in index.ts.",
|
|
430
|
+
"type": "object",
|
|
431
|
+
"required": ["do"],
|
|
432
|
+
"anyOf": [
|
|
433
|
+
{ "oneOf": [
|
|
434
|
+
{ "type": "object", "additionalProperties": false, "required": ["do", "target", "by"], "properties": { "do": { "const": "add" }, "target": { "$ref": "#/$defs/lvalue" }, "by": { "$ref": "#/$defs/expr" } } },
|
|
435
|
+
{ "type": "object", "additionalProperties": false, "required": ["do", "target", "to"], "properties": { "do": { "const": "set" }, "target": { "$ref": "#/$defs/lvalue" }, "to": { "$ref": "#/$defs/expr" } } },
|
|
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" }] } } },
|
|
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" } } },
|
|
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" } } } },
|
|
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." },
|
|
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}$" } } },
|
|
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" }] } } },
|
|
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" }] } } },
|
|
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" } } } },
|
|
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" } } },
|
|
446
|
+
{ "type": "object", "additionalProperties": false, "required": ["do", "entity"], "properties": { "do": { "const": "destroyEntity" }, "entity": { "oneOf": [{ "type": "string", "minLength": 1 }, { "$ref": "#/$defs/refExpr" }] } } },
|
|
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" }] } } },
|
|
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." }
|
|
456
|
+
] },
|
|
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"] } } } }
|
|
458
|
+
]
|
|
459
|
+
}
|
|
101
460
|
}
|
|
102
461
|
}
|