@jgengine/core 0.7.0 → 0.9.0

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.
Files changed (329) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +2 -0
  3. package/dist/ai/flock.d.ts +36 -0
  4. package/dist/ai/flock.js +182 -0
  5. package/dist/ai/groupAssist.d.ts +19 -0
  6. package/dist/ai/groupAssist.js +59 -0
  7. package/dist/ai/laneSelect.d.ts +21 -0
  8. package/dist/ai/laneSelect.js +37 -0
  9. package/dist/ai/mobBrain.d.ts +68 -0
  10. package/dist/ai/mobBrain.js +115 -0
  11. package/dist/ai/spawnDirector.d.ts +5 -0
  12. package/dist/ai/spawnDirector.js +14 -2
  13. package/dist/ai/threat.d.ts +2 -0
  14. package/dist/ai/threat.js +34 -0
  15. package/dist/anim/easing.d.ts +14 -0
  16. package/dist/anim/easing.js +55 -0
  17. package/dist/behaviour/behaviour.d.ts +136 -0
  18. package/dist/behaviour/behaviour.js +388 -0
  19. package/dist/cards/cardPile.d.ts +1 -0
  20. package/dist/cards/cardPile.js +12 -3
  21. package/dist/cartridge/runtime.d.ts +15 -0
  22. package/dist/cartridge/runtime.js +556 -0
  23. package/dist/cartridge/spec.d.ts +188 -0
  24. package/dist/cartridge/spec.js +19 -0
  25. package/dist/cartridge/validate.d.ts +2 -0
  26. package/dist/cartridge/validate.js +78 -0
  27. package/dist/combat/abilityKit.d.ts +22 -2
  28. package/dist/combat/abilityKit.js +72 -13
  29. package/dist/combat/castRunner.d.ts +40 -0
  30. package/dist/combat/castRunner.js +56 -0
  31. package/dist/combat/comboPoints.d.ts +14 -0
  32. package/dist/combat/comboPoints.js +52 -0
  33. package/dist/combat/defensiveWindow.d.ts +1 -1
  34. package/dist/combat/defensiveWindow.js +5 -3
  35. package/dist/combat/effects.d.ts +1 -1
  36. package/dist/combat/effects.js +9 -6
  37. package/dist/combat/projectiles.d.ts +64 -6
  38. package/dist/combat/projectiles.js +232 -74
  39. package/dist/combat/resistance.d.ts +16 -4
  40. package/dist/combat/resistance.js +43 -7
  41. package/dist/combat/resourcePool.d.ts +20 -0
  42. package/dist/combat/resourcePool.js +55 -0
  43. package/dist/combat/shotOrigin.d.ts +40 -0
  44. package/dist/combat/shotOrigin.js +57 -0
  45. package/dist/combat/telegraph.d.ts +30 -1
  46. package/dist/combat/telegraph.js +61 -0
  47. package/dist/commands/commandRegistry.d.ts +1 -1
  48. package/dist/commands/commandRegistry.js +2 -1
  49. package/dist/crafting/recipe.js +4 -1
  50. package/dist/data/dataSource.d.ts +26 -0
  51. package/dist/data/dataSource.js +93 -0
  52. package/dist/data/devProxy.d.ts +11 -0
  53. package/dist/data/devProxy.js +38 -0
  54. package/dist/data/fetchJson.d.ts +20 -0
  55. package/dist/data/fetchJson.js +43 -0
  56. package/dist/data/jsonDataSource.d.ts +4 -0
  57. package/dist/data/jsonDataSource.js +6 -0
  58. package/dist/devtools/devtools.d.ts +205 -0
  59. package/dist/devtools/devtools.js +832 -0
  60. package/dist/devtools/transformTunables.d.ts +14 -0
  61. package/dist/devtools/transformTunables.js +38 -0
  62. package/dist/devtools/tunableSchema.d.ts +140 -0
  63. package/dist/devtools/tunableSchema.js +476 -0
  64. package/dist/faction/factions.d.ts +33 -0
  65. package/dist/faction/factions.js +76 -0
  66. package/dist/faction/reputation.d.ts +32 -0
  67. package/dist/faction/reputation.js +82 -0
  68. package/dist/game/chat.d.ts +68 -0
  69. package/dist/game/chat.js +173 -0
  70. package/dist/game/chatFilter.d.ts +15 -0
  71. package/dist/game/chatFilter.js +55 -0
  72. package/dist/game/controlGate.d.ts +4 -0
  73. package/dist/game/controlGate.js +7 -0
  74. package/dist/game/defineGame.d.ts +10 -2
  75. package/dist/game/defineGame.js +2 -1
  76. package/dist/game/events.d.ts +35 -0
  77. package/dist/game/gamePhase.d.ts +20 -0
  78. package/dist/game/gamePhase.js +21 -0
  79. package/dist/game/levelSequence.d.ts +44 -0
  80. package/dist/game/levelSequence.js +78 -0
  81. package/dist/game/lootTable.d.ts +10 -1
  82. package/dist/game/lootTable.js +27 -7
  83. package/dist/game/playableGame.d.ts +286 -6
  84. package/dist/game/playableGame.js +6 -1
  85. package/dist/game/progression.d.ts +1 -0
  86. package/dist/game/progression.js +23 -2
  87. package/dist/game/quest.d.ts +7 -3
  88. package/dist/game/quest.js +17 -8
  89. package/dist/game/race.d.ts +44 -1
  90. package/dist/game/race.js +116 -11
  91. package/dist/game/recordBook.d.ts +29 -0
  92. package/dist/game/recordBook.js +86 -0
  93. package/dist/game/social.d.ts +42 -0
  94. package/dist/game/social.js +122 -0
  95. package/dist/game/spawnPoints.d.ts +19 -0
  96. package/dist/game/spawnPoints.js +23 -0
  97. package/dist/game/talents.d.ts +47 -0
  98. package/dist/game/talents.js +140 -0
  99. package/dist/game/worldItem.d.ts +3 -0
  100. package/dist/game/worldItem.js +12 -6
  101. package/dist/index.d.ts +1 -0
  102. package/dist/index.js +1 -0
  103. package/dist/input/actionBindings.d.ts +10 -0
  104. package/dist/input/actionBindings.js +14 -0
  105. package/dist/input/axisInput.d.ts +26 -3
  106. package/dist/input/axisInput.js +44 -4
  107. package/dist/input/bindingOverrides.d.ts +25 -0
  108. package/dist/input/bindingOverrides.js +95 -0
  109. package/dist/input/gestureSurface.d.ts +31 -0
  110. package/dist/input/gestureSurface.js +120 -0
  111. package/dist/input/pointer.d.ts +45 -0
  112. package/dist/input/pointer.js +48 -0
  113. package/dist/input/pointerAxis.d.ts +30 -0
  114. package/dist/input/pointerAxis.js +31 -0
  115. package/dist/input/touchScheme.d.ts +89 -0
  116. package/dist/input/touchScheme.js +153 -0
  117. package/dist/item/affix.d.ts +1 -1
  118. package/dist/item/affix.js +1 -19
  119. package/dist/meta/changelog.d.ts +1 -1
  120. package/dist/meta/changelog.js +59 -1
  121. package/dist/movement/dash.d.ts +3 -0
  122. package/dist/movement/dash.js +19 -5
  123. package/dist/movement/glideModel.d.ts +44 -0
  124. package/dist/movement/glideModel.js +54 -0
  125. package/dist/movement/grappleSwing.d.ts +34 -0
  126. package/dist/movement/grappleSwing.js +56 -0
  127. package/dist/movement/leaderTrail.d.ts +29 -0
  128. package/dist/movement/leaderTrail.js +67 -0
  129. package/dist/movement/movementModel.d.ts +36 -3
  130. package/dist/movement/movementModel.js +81 -5
  131. package/dist/movement/steering.d.ts +27 -0
  132. package/dist/movement/steering.js +40 -0
  133. package/dist/movement/voxelController.d.ts +61 -0
  134. package/dist/movement/voxelController.js +229 -0
  135. package/dist/multiplayer/chatContract.d.ts +42 -0
  136. package/dist/multiplayer/chatContract.js +57 -0
  137. package/dist/multiplayer/feedWriteGate.d.ts +10 -0
  138. package/dist/multiplayer/feedWriteGate.js +12 -0
  139. package/dist/multiplayer/identity.d.ts +13 -0
  140. package/dist/multiplayer/identity.js +20 -0
  141. package/dist/multiplayer/poseSyncGate.d.ts +1 -0
  142. package/dist/multiplayer/poseSyncGate.js +13 -1
  143. package/dist/multiplayer/presenceModel.d.ts +17 -2
  144. package/dist/multiplayer/presenceModel.js +30 -4
  145. package/dist/multiplayer/voiceContract.d.ts +44 -0
  146. package/dist/multiplayer/voiceContract.js +108 -0
  147. package/dist/nav/corridors.d.ts +30 -0
  148. package/dist/nav/corridors.js +69 -0
  149. package/dist/nav/navConstrain.d.ts +14 -0
  150. package/dist/nav/navConstrain.js +26 -0
  151. package/dist/nav/navFromEnvironment.d.ts +11 -0
  152. package/dist/nav/navFromEnvironment.js +16 -0
  153. package/dist/nav/navGrid.d.ts +10 -0
  154. package/dist/nav/navGrid.js +48 -5
  155. package/dist/nav/pathFollow.d.ts +4 -2
  156. package/dist/nav/pathFollow.js +5 -3
  157. package/dist/nav/railGraph.d.ts +66 -0
  158. package/dist/nav/railGraph.js +129 -0
  159. package/dist/nav/timetable.d.ts +41 -0
  160. package/dist/nav/timetable.js +94 -0
  161. package/dist/physics/ballisticSweep.d.ts +18 -0
  162. package/dist/physics/ballisticSweep.js +49 -0
  163. package/dist/physics/buoyancy.d.ts +6 -0
  164. package/dist/physics/buoyancy.js +14 -0
  165. package/dist/physics/flowTube.d.ts +32 -0
  166. package/dist/physics/flowTube.js +64 -0
  167. package/dist/physics/forceVolume.d.ts +27 -0
  168. package/dist/physics/forceVolume.js +43 -5
  169. package/dist/physics/kinematicVehicle.d.ts +52 -0
  170. package/dist/physics/kinematicVehicle.js +81 -0
  171. package/dist/physics/physicsWorld.d.ts +54 -8
  172. package/dist/physics/physicsWorld.js +400 -79
  173. package/dist/physics/ragdoll.js +1 -1
  174. package/dist/physics/spatialGrid.d.ts +1 -1
  175. package/dist/physics/spatialGrid.js +5 -0
  176. package/dist/physics/vehicleBody.d.ts +6 -0
  177. package/dist/physics/vehicleBody.js +18 -18
  178. package/dist/puzzle/cellGrid.d.ts +26 -0
  179. package/dist/puzzle/cellGrid.js +110 -0
  180. package/dist/puzzle/fallingPiece.d.ts +24 -0
  181. package/dist/puzzle/fallingPiece.js +51 -0
  182. package/dist/random/nameGen.d.ts +18 -0
  183. package/dist/random/nameGen.js +35 -0
  184. package/dist/random/rng.d.ts +2 -0
  185. package/dist/random/rng.js +23 -0
  186. package/dist/random/seedLink.d.ts +5 -0
  187. package/dist/random/seedLink.js +35 -0
  188. package/dist/runtime/adapter.d.ts +35 -0
  189. package/dist/runtime/adapter.js +37 -1
  190. package/dist/runtime/cameraDirector.d.ts +15 -0
  191. package/dist/runtime/cameraDirector.js +31 -0
  192. package/dist/runtime/commandRunner.d.ts +2 -2
  193. package/dist/runtime/commandRunner.js +2 -2
  194. package/dist/runtime/gameContext.d.ts +65 -2
  195. package/dist/runtime/gameContext.js +280 -9
  196. package/dist/runtime/gameRuntime.d.ts +5 -1
  197. package/dist/runtime/gameRuntime.js +13 -12
  198. package/dist/runtime/hostPersistence.d.ts +30 -1
  199. package/dist/runtime/hostPersistence.js +70 -5
  200. package/dist/runtime/inputSnapshot.d.ts +12 -0
  201. package/dist/runtime/inputSnapshot.js +17 -0
  202. package/dist/runtime/motionIntents.d.ts +33 -0
  203. package/dist/runtime/motionIntents.js +43 -0
  204. package/dist/runtime/snapshot.d.ts +1 -0
  205. package/dist/runtime/snapshot.js +1 -1
  206. package/dist/runtime/transport.d.ts +32 -0
  207. package/dist/runtime/visibility.d.ts +74 -0
  208. package/dist/runtime/visibility.js +127 -0
  209. package/dist/scene/colliders.d.ts +44 -0
  210. package/dist/scene/colliders.js +77 -0
  211. package/dist/scene/entityStore.d.ts +30 -5
  212. package/dist/scene/entityStore.js +83 -13
  213. package/dist/scene/movementSpeed.d.ts +35 -0
  214. package/dist/scene/movementSpeed.js +20 -0
  215. package/dist/scene/objectQuery.d.ts +29 -0
  216. package/dist/scene/objectQuery.js +140 -0
  217. package/dist/scene/objectStore.d.ts +13 -0
  218. package/dist/scene/objectStore.js +130 -6
  219. package/dist/scene/paintLayer.d.ts +15 -0
  220. package/dist/scene/paintLayer.js +53 -0
  221. package/dist/scene/sceneRaycast.d.ts +80 -0
  222. package/dist/scene/sceneRaycast.js +284 -0
  223. package/dist/scene/spatial.d.ts +17 -1
  224. package/dist/scene/spatial.js +140 -17
  225. package/dist/sensor/concealment.d.ts +21 -0
  226. package/dist/sensor/concealment.js +63 -0
  227. package/dist/sensor/freezeMonitor.d.ts +17 -0
  228. package/dist/sensor/freezeMonitor.js +35 -0
  229. package/dist/sensor/recordingBuffer.d.ts +6 -0
  230. package/dist/sensor/recordingBuffer.js +7 -0
  231. package/dist/sensor/replayLoop.d.ts +39 -0
  232. package/dist/sensor/replayLoop.js +64 -0
  233. package/dist/sensor/visionCone.d.ts +31 -0
  234. package/dist/sensor/visionCone.js +72 -0
  235. package/dist/session/extraction.d.ts +2 -2
  236. package/dist/session/extraction.js +13 -2
  237. package/dist/session/roles.d.ts +6 -0
  238. package/dist/session/roles.js +32 -0
  239. package/dist/session/roundState.d.ts +41 -17
  240. package/dist/session/roundState.js +67 -38
  241. package/dist/settings/settingsModel.d.ts +97 -0
  242. package/dist/settings/settingsModel.js +100 -0
  243. package/dist/stats/statModifiers.d.ts +5 -2
  244. package/dist/stats/statModifiers.js +32 -14
  245. package/dist/store/observableKeyedStore.js +8 -2
  246. package/dist/survival/decayMeter.d.ts +1 -1
  247. package/dist/survival/decayMeter.js +0 -1
  248. package/dist/tactics/fallingGrid.d.ts +62 -0
  249. package/dist/tactics/fallingGrid.js +158 -0
  250. package/dist/tactics/tacticalGrid.d.ts +11 -0
  251. package/dist/tactics/tacticalGrid.js +17 -0
  252. package/dist/time/beatClock.d.ts +18 -0
  253. package/dist/time/beatClock.js +24 -0
  254. package/dist/time/idleProgress.d.ts +29 -0
  255. package/dist/time/idleProgress.js +31 -0
  256. package/dist/time/serverTick.d.ts +7 -1
  257. package/dist/time/serverTick.js +27 -4
  258. package/dist/time/simClock.d.ts +25 -1
  259. package/dist/time/simClock.js +28 -3
  260. package/dist/time/stateSchedule.d.ts +57 -0
  261. package/dist/time/stateSchedule.js +121 -0
  262. package/dist/turn/commit.d.ts +2 -2
  263. package/dist/turn/commit.js +2 -2
  264. package/dist/turn/intent.d.ts +14 -0
  265. package/dist/turn/intent.js +29 -0
  266. package/dist/turn/turnLoop.d.ts +2 -0
  267. package/dist/turn/turnLoop.js +8 -1
  268. package/dist/ui/hudLayout.d.ts +58 -0
  269. package/dist/ui/hudLayout.js +207 -0
  270. package/dist/ui/hudScale.d.ts +44 -0
  271. package/dist/ui/hudScale.js +55 -0
  272. package/dist/visibility/assetStreaming.d.ts +66 -0
  273. package/dist/visibility/assetStreaming.js +201 -0
  274. package/dist/visibility/bounds.d.ts +66 -0
  275. package/dist/visibility/bounds.js +113 -0
  276. package/dist/visibility/camera.d.ts +21 -0
  277. package/dist/visibility/camera.js +3 -0
  278. package/dist/visibility/config.d.ts +21 -0
  279. package/dist/visibility/config.js +1 -0
  280. package/dist/visibility/diagnostics.d.ts +26 -0
  281. package/dist/visibility/diagnostics.js +36 -0
  282. package/dist/visibility/distance.d.ts +11 -0
  283. package/dist/visibility/distance.js +30 -0
  284. package/dist/visibility/frustum.d.ts +47 -0
  285. package/dist/visibility/frustum.js +186 -0
  286. package/dist/visibility/occlusion.d.ts +36 -0
  287. package/dist/visibility/occlusion.js +45 -0
  288. package/dist/visibility/settings.d.ts +75 -0
  289. package/dist/visibility/settings.js +49 -0
  290. package/dist/visibility/simulationCulling.d.ts +34 -0
  291. package/dist/visibility/simulationCulling.js +36 -0
  292. package/dist/visibility/spatialIndex.d.ts +45 -0
  293. package/dist/visibility/spatialIndex.js +198 -0
  294. package/dist/visibility/visibilitySystem.d.ts +90 -0
  295. package/dist/visibility/visibilitySystem.js +257 -0
  296. package/dist/world/buildings.d.ts +6 -0
  297. package/dist/world/buildings.js +152 -0
  298. package/dist/world/carve.d.ts +7 -2
  299. package/dist/world/carve.js +17 -7
  300. package/dist/world/cellStates.d.ts +45 -0
  301. package/dist/world/cellStates.js +103 -0
  302. package/dist/world/environmentSummary.d.ts +61 -0
  303. package/dist/world/environmentSummary.js +138 -0
  304. package/dist/world/features.d.ts +138 -9
  305. package/dist/world/features.js +73 -2
  306. package/dist/world/gridInstances.d.ts +12 -0
  307. package/dist/world/gridInstances.js +160 -0
  308. package/dist/world/lod.d.ts +17 -0
  309. package/dist/world/lod.js +66 -0
  310. package/dist/world/mapLayers.d.ts +60 -0
  311. package/dist/world/mapLayers.js +44 -0
  312. package/dist/world/massing.d.ts +45 -0
  313. package/dist/world/massing.js +581 -0
  314. package/dist/world/minimap.d.ts +13 -1
  315. package/dist/world/minimap.js +21 -2
  316. package/dist/world/placementController.d.ts +22 -1
  317. package/dist/world/placementController.js +56 -0
  318. package/dist/world/terrain.d.ts +62 -1
  319. package/dist/world/terrain.js +253 -9
  320. package/dist/world/voxelField.d.ts +54 -0
  321. package/dist/world/voxelField.js +241 -0
  322. package/dist/world/water.d.ts +7 -0
  323. package/dist/world/water.js +19 -4
  324. package/dist/world/weather.d.ts +3 -18
  325. package/dist/world/weather.js +4 -17
  326. package/dist/world/windZones.d.ts +47 -0
  327. package/dist/world/windZones.js +59 -0
  328. package/llms.txt +4983 -0
  329. package/package.json +14 -3
@@ -88,7 +88,7 @@ export function createRagdoll(world, config) {
88
88
  (world.posY[a] + world.posY[b]) / 2,
89
89
  (world.posZ[a] + world.posZ[b]) / 2,
90
90
  ];
91
- links.push(world.hingeJoint({
91
+ links.push(world.fixedJoint({
92
92
  bodyA: a,
93
93
  bodyB: b,
94
94
  anchorA: [mid[0] - world.posX[a], mid[1] - world.posY[a], mid[2] - world.posZ[a]],
@@ -1,4 +1,4 @@
1
- import type { PhysicsBounds } from "./physicsWorld.js";
1
+ import { type PhysicsBounds } from "./physicsWorld.js";
2
2
  export interface SpatialGridConfig {
3
3
  /** Horizontal extent to index; only x/z of the bounds are used (a top-down plane). */
4
4
  bounds: PhysicsBounds;
@@ -1,3 +1,4 @@
1
+ import { MAX_BROADPHASE_CELLS } from "./physicsWorld.js";
1
2
  /**
2
3
  * A uniform-grid broad-phase over the x/z plane, separate from the rigid-body sim, for cheap
3
4
  * same-tick proximity across hundreds–thousands of simple movers (swarm enemies). Rebuild each
@@ -30,6 +31,10 @@ export class SpatialGrid {
30
31
  this.nx = Math.max(1, Math.ceil(spanX / config.cellSize));
31
32
  this.nz = Math.max(1, Math.ceil(spanZ / config.cellSize));
32
33
  this.numCells = this.nx * this.nz;
34
+ if (this.numCells > MAX_BROADPHASE_CELLS) {
35
+ throw new Error(`SpatialGrid: grid too large (${this.nx}x${this.nz} = ${this.numCells} cells, cap ${MAX_BROADPHASE_CELLS}). ` +
36
+ `bounds=[${config.bounds.min}]..[${config.bounds.max}] cellSize=${config.cellSize}; raise cellSize or shrink bounds.`);
37
+ }
33
38
  this.cellStart = new Int32Array(this.numCells + 1);
34
39
  this.cursor = new Int32Array(this.numCells);
35
40
  this.sorted = new Int32Array(config.capacity);
@@ -77,6 +77,12 @@ export declare class VehicleBody {
77
77
  private readonly surfaceFriction;
78
78
  private groundedCount;
79
79
  constructor(world: PhysicsWorld, config: VehicleBodyConfig);
80
+ /**
81
+ * A wheel's local offset rotated by the current heading — the ground-side mount and the
82
+ * chassis-side spring anchor both derive from this, so the suspension axis stays vertical (not a
83
+ * mix of rotated and unrotated ends) as the chassis turns.
84
+ */
85
+ private rotateOffset;
80
86
  private wheelMount;
81
87
  get position(): [number, number, number];
82
88
  get forward(): [number, number];
@@ -1,3 +1,4 @@
1
+ import { steerYaw } from "../movement/steering.js";
1
2
  /**
2
3
  * Piecewise-linear tire-grip curve: normalized lateral slip → available grip (0..1). Grip peaks near
3
4
  * the breakaway slip then falls off as the tire slides — the shape that separates a planted corner
@@ -101,7 +102,7 @@ export class VehicleBody {
101
102
  const rest = wheel.restLength ?? this.suspensionRest;
102
103
  const id = world.springJoint({
103
104
  bodyA: this.chassis,
104
- anchorA: [wheel.offset[0], wheel.offset[1], wheel.offset[2]],
105
+ anchorA: this.rotateOffset(wheel.offset),
105
106
  anchorB: [worldPos[0], worldPos[1] - rest, worldPos[2]],
106
107
  restLength: rest,
107
108
  stiffness,
@@ -118,15 +119,20 @@ export class VehicleBody {
118
119
  });
119
120
  }
120
121
  }
121
- wheelMount(wheel) {
122
+ /**
123
+ * A wheel's local offset rotated by the current heading — the ground-side mount and the
124
+ * chassis-side spring anchor both derive from this, so the suspension axis stays vertical (not a
125
+ * mix of rotated and unrotated ends) as the chassis turns.
126
+ */
127
+ rotateOffset(offset) {
122
128
  const sin = Math.sin(this.heading);
123
129
  const cos = Math.cos(this.heading);
124
- const [ox, oy, oz] = wheel.offset;
125
- return [
126
- this.world.posX[this.chassis] + ox * cos + oz * sin,
127
- this.world.posY[this.chassis] + oy,
128
- this.world.posZ[this.chassis] - ox * sin + oz * cos,
129
- ];
130
+ const [ox, oy, oz] = offset;
131
+ return [ox * cos + oz * sin, oy, -ox * sin + oz * cos];
132
+ }
133
+ wheelMount(wheel) {
134
+ const [dx, dy, dz] = this.rotateOffset(wheel.offset);
135
+ return [this.world.posX[this.chassis] + dx, this.world.posY[this.chassis] + dy, this.world.posZ[this.chassis] + dz];
130
136
  }
131
137
  get position() {
132
138
  return [this.world.posX[this.chassis], this.world.posY[this.chassis], this.world.posZ[this.chassis]];
@@ -149,16 +155,8 @@ export class VehicleBody {
149
155
  return this.wheels.length;
150
156
  }
151
157
  resetTo(position, heading) {
152
- const w = this.world;
153
- const c = this.chassis;
154
- w.posX[c] = position[0];
155
- w.posY[c] = position[1];
156
- w.posZ[c] = position[2];
157
- w.velX[c] = 0;
158
- w.velY[c] = 0;
159
- w.velZ[c] = 0;
158
+ this.world.teleport(this.chassis, position[0], position[1], position[2]);
160
159
  this.heading = heading;
161
- w.wake(c);
162
160
  }
163
161
  update(dt, input) {
164
162
  const w = this.world;
@@ -169,7 +167,7 @@ export class VehicleBody {
169
167
  if (grounded) {
170
168
  const steerScale = Math.min(1, Math.abs(speed) / this.turnSpeedRef);
171
169
  const dir = speed >= 0 ? 1 : -1;
172
- this.heading += input.steer * this.turnRate * steerScale * dir * dt;
170
+ this.heading = steerYaw(this.heading, input.steer * steerScale * dir, this.turnRate, dt);
173
171
  }
174
172
  const [fx, fz] = this.forward;
175
173
  if (grounded) {
@@ -212,6 +210,8 @@ export class VehicleBody {
212
210
  const gap = mount[1] - (groundY + radius);
213
211
  const maxTravel = rest + radius;
214
212
  const grounded = gap <= maxTravel;
213
+ const anchorA = this.rotateOffset(wheel.offset);
214
+ w.setJointAnchorA(this.springs[i], anchorA[0], anchorA[1], anchorA[2]);
215
215
  w.setJointAnchor(this.springs[i], mount[0], groundY + radius, mount[2]);
216
216
  state.worldX = mount[0];
217
217
  state.worldY = groundY + radius;
@@ -0,0 +1,26 @@
1
+ export interface CellGrid<T> {
2
+ readonly width: number;
3
+ readonly height: number;
4
+ readonly cells: readonly (T | null)[];
5
+ }
6
+ export interface CellRun<T> {
7
+ readonly value: T;
8
+ readonly cells: readonly {
9
+ readonly x: number;
10
+ readonly y: number;
11
+ }[];
12
+ readonly direction: "row" | "column";
13
+ }
14
+ export declare function createCellGrid<T>(width: number, height: number): CellGrid<T>;
15
+ export declare function inGridBounds<T>(grid: CellGrid<T>, x: number, y: number): boolean;
16
+ export declare function cellAt<T>(grid: CellGrid<T>, x: number, y: number): T | null;
17
+ export declare function withCell<T>(grid: CellGrid<T>, x: number, y: number, value: T | null): CellGrid<T>;
18
+ export declare function withCells<T>(grid: CellGrid<T>, entries: readonly {
19
+ readonly x: number;
20
+ readonly y: number;
21
+ readonly value: T | null;
22
+ }[]): CellGrid<T>;
23
+ export declare function fullRows<T>(grid: CellGrid<T>): number[];
24
+ export declare function clearRows<T>(grid: CellGrid<T>, rows: readonly number[]): CellGrid<T>;
25
+ export declare function collapseColumns<T>(grid: CellGrid<T>): CellGrid<T>;
26
+ export declare function findRuns<T>(grid: CellGrid<T>, minLength: number, matches?: (a: T, b: T) => boolean): CellRun<T>[];
@@ -0,0 +1,110 @@
1
+ export function createCellGrid(width, height) {
2
+ return { width, height, cells: new Array(width * height).fill(null) };
3
+ }
4
+ export function inGridBounds(grid, x, y) {
5
+ return x >= 0 && x < grid.width && y >= 0 && y < grid.height;
6
+ }
7
+ export function cellAt(grid, x, y) {
8
+ if (!inGridBounds(grid, x, y))
9
+ return null;
10
+ return grid.cells[y * grid.width + x] ?? null;
11
+ }
12
+ export function withCell(grid, x, y, value) {
13
+ if (!inGridBounds(grid, x, y))
14
+ return grid;
15
+ const cells = grid.cells.slice();
16
+ cells[y * grid.width + x] = value;
17
+ return { width: grid.width, height: grid.height, cells };
18
+ }
19
+ export function withCells(grid, entries) {
20
+ const cells = grid.cells.slice();
21
+ for (const entry of entries) {
22
+ if (inGridBounds(grid, entry.x, entry.y))
23
+ cells[entry.y * grid.width + entry.x] = entry.value;
24
+ }
25
+ return { width: grid.width, height: grid.height, cells };
26
+ }
27
+ export function fullRows(grid) {
28
+ const rows = [];
29
+ for (let y = 0; y < grid.height; y += 1) {
30
+ let full = true;
31
+ for (let x = 0; x < grid.width; x += 1) {
32
+ if (cellAt(grid, x, y) === null) {
33
+ full = false;
34
+ break;
35
+ }
36
+ }
37
+ if (full)
38
+ rows.push(y);
39
+ }
40
+ return rows;
41
+ }
42
+ export function clearRows(grid, rows) {
43
+ const removed = new Set(rows);
44
+ const kept = [];
45
+ for (let y = 0; y < grid.height; y += 1) {
46
+ if (removed.has(y))
47
+ continue;
48
+ kept.push(grid.cells.slice(y * grid.width, y * grid.width + grid.width));
49
+ }
50
+ const clearedCount = grid.height - kept.length;
51
+ const cells = [];
52
+ for (let i = 0; i < clearedCount; i += 1)
53
+ cells.push(...new Array(grid.width).fill(null));
54
+ for (const row of kept)
55
+ cells.push(...row);
56
+ return { width: grid.width, height: grid.height, cells };
57
+ }
58
+ export function collapseColumns(grid) {
59
+ const cells = grid.cells.slice();
60
+ for (let x = 0; x < grid.width; x += 1) {
61
+ const values = [];
62
+ for (let y = 0; y < grid.height; y += 1) {
63
+ const value = cellAt(grid, x, y);
64
+ if (value !== null)
65
+ values.push(value);
66
+ }
67
+ const emptyCount = grid.height - values.length;
68
+ for (let y = 0; y < grid.height; y += 1) {
69
+ cells[y * grid.width + x] = y < emptyCount ? null : values[y - emptyCount];
70
+ }
71
+ }
72
+ return { width: grid.width, height: grid.height, cells };
73
+ }
74
+ function scanRuns(length, cellAtIndex, minLength, matches) {
75
+ const runs = [];
76
+ let start = 0;
77
+ for (let i = 1; i <= length; i += 1) {
78
+ const prev = cellAtIndex(i - 1);
79
+ const cur = i < length ? cellAtIndex(i) : null;
80
+ const continues = prev !== null && cur !== null && matches(prev, cur);
81
+ if (!continues) {
82
+ const runLength = i - start;
83
+ const value = cellAtIndex(start);
84
+ if (runLength >= minLength && value !== null)
85
+ runs.push({ value, start, end: i });
86
+ start = i;
87
+ }
88
+ }
89
+ return runs;
90
+ }
91
+ export function findRuns(grid, minLength, matches = (a, b) => a === b) {
92
+ const runs = [];
93
+ for (let y = 0; y < grid.height; y += 1) {
94
+ for (const run of scanRuns(grid.width, (x) => cellAt(grid, x, y), minLength, matches)) {
95
+ const cells = [];
96
+ for (let x = run.start; x < run.end; x += 1)
97
+ cells.push({ x, y });
98
+ runs.push({ value: run.value, cells, direction: "row" });
99
+ }
100
+ }
101
+ for (let x = 0; x < grid.width; x += 1) {
102
+ for (const run of scanRuns(grid.height, (y) => cellAt(grid, x, y), minLength, matches)) {
103
+ const cells = [];
104
+ for (let y = run.start; y < run.end; y += 1)
105
+ cells.push({ x, y });
106
+ runs.push({ value: run.value, cells, direction: "column" });
107
+ }
108
+ }
109
+ return runs;
110
+ }
@@ -0,0 +1,24 @@
1
+ import { type CellGrid } from "./cellGrid.js";
2
+ export interface FallingPiece<TShape extends string = string> {
3
+ readonly shape: TShape;
4
+ readonly rotation: number;
5
+ readonly x: number;
6
+ readonly y: number;
7
+ }
8
+ export type ShapeTable<TShape extends string = string> = Record<TShape, readonly (readonly (readonly [number, number])[])[]>;
9
+ export declare function pieceCells<TShape extends string>(table: ShapeTable<TShape>, piece: FallingPiece<TShape>): readonly (readonly [number, number])[];
10
+ export declare function pieceCollides<T, TShape extends string>(grid: CellGrid<T>, table: ShapeTable<TShape>, piece: FallingPiece<TShape>): boolean;
11
+ export declare function mergePiece<T, TShape extends string>(grid: CellGrid<T>, table: ShapeTable<TShape>, piece: FallingPiece<TShape>, value: T): CellGrid<T>;
12
+ export declare function dropDistance<T, TShape extends string>(grid: CellGrid<T>, table: ShapeTable<TShape>, piece: FallingPiece<TShape>): number;
13
+ export declare function gravityInterval(level: number, base?: number, perLevel?: number, min?: number): number;
14
+ export declare function levelForLines(lines: number, linesPerLevel?: number): number;
15
+ export declare function lineScore(cleared: number, level: number, table?: readonly number[]): number;
16
+ export interface LockDelayState {
17
+ readonly delaySeconds: number;
18
+ readonly elapsed: number;
19
+ }
20
+ export declare function createLockDelay(delaySeconds: number): LockDelayState;
21
+ export declare function stepLockDelay(state: LockDelayState, grounded: boolean, dt: number): {
22
+ state: LockDelayState;
23
+ locked: boolean;
24
+ };
@@ -0,0 +1,51 @@
1
+ import { cellAt } from "./cellGrid.js";
2
+ export function pieceCells(table, piece) {
3
+ const rotations = table[piece.shape];
4
+ const index = ((piece.rotation % rotations.length) + rotations.length) % rotations.length;
5
+ const state = rotations[index];
6
+ return state.map(([ox, oy]) => [piece.x + ox, piece.y + oy]);
7
+ }
8
+ export function pieceCollides(grid, table, piece) {
9
+ for (const [x, y] of pieceCells(table, piece)) {
10
+ if (x < 0 || x >= grid.width || y >= grid.height)
11
+ return true;
12
+ if (y >= 0 && cellAt(grid, x, y) !== null)
13
+ return true;
14
+ }
15
+ return false;
16
+ }
17
+ export function mergePiece(grid, table, piece, value) {
18
+ const cells = grid.cells.slice();
19
+ for (const [x, y] of pieceCells(table, piece)) {
20
+ if (x >= 0 && x < grid.width && y >= 0 && y < grid.height)
21
+ cells[y * grid.width + x] = value;
22
+ }
23
+ return { width: grid.width, height: grid.height, cells };
24
+ }
25
+ export function dropDistance(grid, table, piece) {
26
+ let distance = 0;
27
+ while (!pieceCollides(grid, table, { ...piece, y: piece.y + distance + 1 }))
28
+ distance += 1;
29
+ return distance;
30
+ }
31
+ export function gravityInterval(level, base = 0.8, perLevel = 0.07, min = 0.05) {
32
+ return Math.max(min, base - level * perLevel);
33
+ }
34
+ export function levelForLines(lines, linesPerLevel = 10) {
35
+ return Math.floor(lines / linesPerLevel);
36
+ }
37
+ const CLASSIC_LINE_SCORE = [0, 40, 100, 300, 1200];
38
+ export function lineScore(cleared, level, table = CLASSIC_LINE_SCORE) {
39
+ return (table[cleared] ?? 0) * (level + 1);
40
+ }
41
+ export function createLockDelay(delaySeconds) {
42
+ return { delaySeconds, elapsed: 0 };
43
+ }
44
+ export function stepLockDelay(state, grounded, dt) {
45
+ if (!grounded)
46
+ return { state: { ...state, elapsed: 0 }, locked: false };
47
+ const elapsed = state.elapsed + dt;
48
+ if (elapsed >= state.delaySeconds)
49
+ return { state: { ...state, elapsed: 0 }, locked: true };
50
+ return { state: { ...state, elapsed }, locked: false };
51
+ }
@@ -0,0 +1,18 @@
1
+ export interface SyllableBank {
2
+ onset?: readonly string[];
3
+ nucleus: readonly string[];
4
+ coda?: readonly string[];
5
+ }
6
+ export interface NameGeneratorOptions {
7
+ rng: () => number;
8
+ syllables: SyllableBank;
9
+ minSyllables?: number;
10
+ maxSyllables?: number;
11
+ capitalize?: boolean;
12
+ }
13
+ export interface NameGenerator {
14
+ name(): string;
15
+ }
16
+ export declare function pickFrom(rng: () => number, bank: readonly string[]): string;
17
+ export declare function createNameGenerator(options: NameGeneratorOptions): NameGenerator;
18
+ export declare function fillTemplate(template: string, vars: Record<string, () => string> | Record<string, string>): string;
@@ -0,0 +1,35 @@
1
+ export function pickFrom(rng, bank) {
2
+ if (bank.length === 0)
3
+ throw new Error("pickFrom: bank must not be empty");
4
+ return bank[Math.floor(rng() * bank.length)];
5
+ }
6
+ export function createNameGenerator(options) {
7
+ const { rng, syllables } = options;
8
+ const minSyllables = Math.max(1, options.minSyllables ?? 2);
9
+ const maxSyllables = Math.max(minSyllables, options.maxSyllables ?? minSyllables);
10
+ const capitalize = options.capitalize ?? true;
11
+ function syllable() {
12
+ const onset = syllables.onset === undefined || syllables.onset.length === 0 ? "" : pickFrom(rng, syllables.onset);
13
+ const nucleus = pickFrom(rng, syllables.nucleus);
14
+ const coda = syllables.coda === undefined || syllables.coda.length === 0 ? "" : pickFrom(rng, syllables.coda);
15
+ return `${onset}${nucleus}${coda}`;
16
+ }
17
+ return {
18
+ name() {
19
+ const span = maxSyllables - minSyllables;
20
+ const count = minSyllables + (span === 0 ? 0 : Math.floor(rng() * (span + 1)));
21
+ let result = "";
22
+ for (let i = 0; i < count; i++)
23
+ result += syllable();
24
+ return capitalize && result.length > 0 ? result.charAt(0).toUpperCase() + result.slice(1) : result;
25
+ },
26
+ };
27
+ }
28
+ export function fillTemplate(template, vars) {
29
+ return template.replace(/\{(\w+)\}/g, (match, key) => {
30
+ if (!(key in vars))
31
+ return match;
32
+ const value = vars[key];
33
+ return typeof value === "function" ? value() : value;
34
+ });
35
+ }
@@ -0,0 +1,2 @@
1
+ export declare function seededRng(seed: string | number): () => number;
2
+ export declare function seededStreams(seed: string | number): (stream: string) => () => number;
@@ -0,0 +1,23 @@
1
+ function hashSeed(seed) {
2
+ const text = typeof seed === "number" ? seed.toString() : seed;
3
+ let h = 2166136261 >>> 0;
4
+ for (let i = 0; i < text.length; i++) {
5
+ h ^= text.charCodeAt(i);
6
+ h = Math.imul(h, 16777619);
7
+ }
8
+ return h >>> 0;
9
+ }
10
+ export function seededRng(seed) {
11
+ let a = hashSeed(seed) >>> 0;
12
+ return () => {
13
+ a = (a + 0x6d2b79f5) >>> 0;
14
+ let t = a;
15
+ t = Math.imul(t ^ (t >>> 15), t | 1);
16
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
17
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
18
+ };
19
+ }
20
+ export function seededStreams(seed) {
21
+ const base = typeof seed === "number" ? seed.toString() : seed;
22
+ return (stream) => seededRng(`${base}:${stream}`);
23
+ }
@@ -0,0 +1,5 @@
1
+ export declare const DEFAULT_SEED_PARAM = "seed";
2
+ export declare function withSeedParam(url: string, seed: string | number, param?: string): string;
3
+ export declare function seedFromSearch(search: string, param?: string): string | null;
4
+ export declare function seedFromUrl(url: string, param?: string): string | null;
5
+ export declare function dailySeed(nowMs: number, salt?: string): string;
@@ -0,0 +1,35 @@
1
+ export const DEFAULT_SEED_PARAM = "seed";
2
+ function splitUrl(url) {
3
+ const hashIndex = url.indexOf("#");
4
+ const hash = hashIndex === -1 ? "" : url.slice(hashIndex + 1);
5
+ const rest = hashIndex === -1 ? url : url.slice(0, hashIndex);
6
+ const queryIndex = rest.indexOf("?");
7
+ const base = queryIndex === -1 ? rest : rest.slice(0, queryIndex);
8
+ const query = queryIndex === -1 ? "" : rest.slice(queryIndex + 1);
9
+ return { base, query, hash };
10
+ }
11
+ export function withSeedParam(url, seed, param = DEFAULT_SEED_PARAM) {
12
+ const { base, query, hash } = splitUrl(url);
13
+ const params = new URLSearchParams(query);
14
+ params.set(param, typeof seed === "number" ? seed.toString() : seed);
15
+ const queryString = params.toString();
16
+ return `${base}${queryString ? `?${queryString}` : ""}${hash ? `#${hash}` : ""}`;
17
+ }
18
+ export function seedFromSearch(search, param = DEFAULT_SEED_PARAM) {
19
+ const normalized = search.startsWith("?") ? search.slice(1) : search;
20
+ const params = new URLSearchParams(normalized);
21
+ const value = params.get(param);
22
+ return value === null || value === "" ? null : value;
23
+ }
24
+ export function seedFromUrl(url, param = DEFAULT_SEED_PARAM) {
25
+ const { query } = splitUrl(url);
26
+ return seedFromSearch(query, param);
27
+ }
28
+ export function dailySeed(nowMs, salt) {
29
+ const date = new Date(nowMs);
30
+ const year = date.getUTCFullYear().toString().padStart(4, "0");
31
+ const month = (date.getUTCMonth() + 1).toString().padStart(2, "0");
32
+ const day = date.getUTCDate().toString().padStart(2, "0");
33
+ const base = `${year}-${month}-${day}`;
34
+ return salt !== undefined && salt.length > 0 ? `${base}:${salt}` : base;
35
+ }
@@ -5,6 +5,20 @@ export type MultiplayerAdapterConfig = {
5
5
  } | {
6
6
  kind: "ws";
7
7
  topology?: MultiplayerTopology;
8
+ url?: string;
9
+ } | {
10
+ kind: "socketio";
11
+ topology?: MultiplayerTopology;
12
+ url?: string;
13
+ } | {
14
+ kind: "p2p";
15
+ topology?: MultiplayerTopology;
16
+ room?: string;
17
+ } | {
18
+ kind: "lan";
19
+ topology?: MultiplayerTopology;
20
+ port?: number;
21
+ path?: string;
8
22
  } | {
9
23
  kind: "offline";
10
24
  };
@@ -13,8 +27,29 @@ export declare function convex(config?: {
13
27
  }): MultiplayerAdapterConfig;
14
28
  export declare function ws(config?: {
15
29
  topology?: MultiplayerTopology;
30
+ url?: string;
31
+ }): MultiplayerAdapterConfig;
32
+ export declare function fly(config: {
33
+ app: string;
34
+ topology?: MultiplayerTopology;
35
+ path?: string;
36
+ }): MultiplayerAdapterConfig;
37
+ export declare function socketIo(config?: {
38
+ topology?: MultiplayerTopology;
39
+ url?: string;
40
+ }): MultiplayerAdapterConfig;
41
+ export declare function p2p(config?: {
42
+ topology?: MultiplayerTopology;
43
+ room?: string;
44
+ }): MultiplayerAdapterConfig;
45
+ export declare function lan(config?: {
46
+ topology?: MultiplayerTopology;
47
+ port?: number;
48
+ path?: string;
16
49
  }): MultiplayerAdapterConfig;
17
50
  export declare function offline(): MultiplayerAdapterConfig;
51
+ export declare function adapterOf(multiplayer: unknown): MultiplayerAdapterConfig | null;
52
+ export declare function multiplayerAdapterKind(multiplayer: unknown): string | null;
18
53
  export type ServersPoolConfig = {
19
54
  maxServers: number;
20
55
  slotsPerServer: number;
@@ -2,11 +2,47 @@ export function convex(config) {
2
2
  return { kind: "convex", topology: config?.topology ?? "shared" };
3
3
  }
4
4
  export function ws(config) {
5
- return { kind: "ws", topology: config?.topology ?? "shared" };
5
+ return { kind: "ws", topology: config?.topology ?? "shared", url: config?.url };
6
+ }
7
+ export function fly(config) {
8
+ return {
9
+ kind: "ws",
10
+ topology: config.topology ?? "shared",
11
+ url: `wss://${config.app}.fly.dev${config.path ?? "/ws"}`,
12
+ };
13
+ }
14
+ export function socketIo(config) {
15
+ return { kind: "socketio", topology: config?.topology ?? "shared", url: config?.url };
16
+ }
17
+ export function p2p(config) {
18
+ return { kind: "p2p", topology: config?.topology ?? "private", room: config?.room };
19
+ }
20
+ export function lan(config) {
21
+ return { kind: "lan", topology: config?.topology ?? "shared", port: config?.port, path: config?.path };
6
22
  }
7
23
  export function offline() {
8
24
  return { kind: "offline" };
9
25
  }
26
+ const ADAPTER_KINDS = new Set(["convex", "ws", "socketio", "p2p", "lan", "offline"]);
27
+ function isAdapterConfig(value) {
28
+ if (typeof value !== "object" || value === null)
29
+ return false;
30
+ const kind = value.kind;
31
+ return typeof kind === "string" && ADAPTER_KINDS.has(kind);
32
+ }
33
+ export function adapterOf(multiplayer) {
34
+ if (isAdapterConfig(multiplayer))
35
+ return multiplayer;
36
+ if (typeof multiplayer === "object" && multiplayer !== null) {
37
+ const nested = multiplayer.adapter;
38
+ if (isAdapterConfig(nested))
39
+ return nested;
40
+ }
41
+ return null;
42
+ }
43
+ export function multiplayerAdapterKind(multiplayer) {
44
+ return adapterOf(multiplayer)?.kind ?? null;
45
+ }
10
46
  export function servers(config) {
11
47
  return config;
12
48
  }
@@ -0,0 +1,15 @@
1
+ import type { ChaseCameraConfig, CinematicCameraConfig } from "../game/playableGame.js";
2
+ /** Runtime patch over the static `camera.chase` config — distance/height/fov retuning from gameplay events (#286.11). */
3
+ export type ChaseCameraTuning = Partial<Pick<ChaseCameraConfig, "distance" | "height" | "lookHeight" | "springDamping" | "fov" | "lead" | "bank">>;
4
+ export interface CameraDirector {
5
+ follow(entityId: string | null): void;
6
+ /** `undefined` means no runtime override — the shell falls back to the static `playable.camera.followEntityId`. `null` means explicitly follow nothing. */
7
+ followedEntityId(): string | null | undefined;
8
+ setCinematic(config: CinematicCameraConfig | null): void;
9
+ cinematic(): CinematicCameraConfig | null;
10
+ /** Overlay a runtime patch on the chase rig's config — a boss-intro pull-back, drift zoom-out; `null` restores the static config. */
11
+ setChaseTuning(tuning: ChaseCameraTuning | null): void;
12
+ chaseTuning(): ChaseCameraTuning | null;
13
+ subscribe(listener: () => void): () => void;
14
+ }
15
+ export declare function createCameraDirector(): CameraDirector;
@@ -0,0 +1,31 @@
1
+ export function createCameraDirector() {
2
+ const listeners = new Set();
3
+ let followEntity = undefined;
4
+ let cinematicConfig = null;
5
+ let chaseTuningPatch = null;
6
+ function notify() {
7
+ for (const listener of listeners)
8
+ listener();
9
+ }
10
+ return {
11
+ follow(entityId) {
12
+ followEntity = entityId;
13
+ notify();
14
+ },
15
+ followedEntityId: () => followEntity,
16
+ setCinematic(config) {
17
+ cinematicConfig = config;
18
+ notify();
19
+ },
20
+ cinematic: () => cinematicConfig,
21
+ setChaseTuning(tuning) {
22
+ chaseTuningPatch = tuning;
23
+ notify();
24
+ },
25
+ chaseTuning: () => chaseTuningPatch,
26
+ subscribe(listener) {
27
+ listeners.add(listener);
28
+ return () => listeners.delete(listener);
29
+ },
30
+ };
31
+ }
@@ -2,8 +2,8 @@ export type CommandValidationError = {
2
2
  reason: string;
3
3
  };
4
4
  export type CommandDef<TInput = unknown> = {
5
- validate: (snapshot: import("./snapshot.js").GameRuntimeSnapshot, input: TInput) => CommandValidationError | null;
6
- apply: (snapshot: import("./snapshot.js").GameRuntimeSnapshot, input: TInput) => import("./snapshot.js").GameRuntimeSnapshot;
5
+ validate: (snapshot: import("./snapshot.js").GameRuntimeSnapshot, input: TInput, actorUserId: string) => CommandValidationError | null;
6
+ apply: (snapshot: import("./snapshot.js").GameRuntimeSnapshot, input: TInput, actorUserId: string) => import("./snapshot.js").GameRuntimeSnapshot;
7
7
  };
8
8
  export type RunCommandResult = {
9
9
  ok: true;
@@ -3,11 +3,11 @@ export function runCommand(snapshot, commands, commandName, input, actorUserId)
3
3
  if (!command) {
4
4
  return { ok: false, reason: `Unknown command: ${commandName}` };
5
5
  }
6
- const validationError = command.validate(snapshot, input);
6
+ const validationError = command.validate(snapshot, input, actorUserId);
7
7
  if (validationError) {
8
8
  return { ok: false, reason: validationError.reason };
9
9
  }
10
- const next = command.apply(snapshot, input);
10
+ const next = command.apply(snapshot, input, actorUserId);
11
11
  return {
12
12
  ok: true,
13
13
  snapshot: {