@jgengine/core 0.11.0 → 0.12.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 (219) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/ai/populationDirector.d.ts +103 -0
  3. package/dist/ai/populationDirector.js +190 -0
  4. package/dist/ai/pursuit.d.ts +56 -0
  5. package/dist/ai/pursuit.js +53 -0
  6. package/dist/authoring.d.ts +1 -1
  7. package/dist/authoring.js +1 -1
  8. package/dist/combat.d.ts +1 -0
  9. package/dist/combat.js +1 -0
  10. package/dist/devtools/controls.d.ts +38 -0
  11. package/dist/devtools/controls.js +181 -0
  12. package/dist/devtools/devtools.d.ts +5 -197
  13. package/dist/devtools/devtools.js +38 -774
  14. package/dist/devtools/discover.d.ts +32 -0
  15. package/dist/devtools/discover.js +240 -0
  16. package/dist/devtools/fallbackSeams.d.ts +50 -0
  17. package/dist/devtools/fallbackSeams.js +111 -0
  18. package/dist/devtools/frame.d.ts +15 -0
  19. package/dist/devtools/frame.js +247 -0
  20. package/dist/devtools/logs.d.ts +15 -0
  21. package/dist/devtools/logs.js +86 -0
  22. package/dist/devtools/overrides.d.ts +16 -0
  23. package/dist/devtools/overrides.js +66 -0
  24. package/dist/devtools/types.d.ts +212 -0
  25. package/dist/devtools/types.js +1 -0
  26. package/dist/economy/auctionBook.d.ts +122 -0
  27. package/dist/economy/auctionBook.js +213 -0
  28. package/dist/economy/priceHistory.d.ts +44 -0
  29. package/dist/economy/priceHistory.js +89 -0
  30. package/dist/economy/unlockPoints.d.ts +92 -0
  31. package/dist/economy/unlockPoints.js +103 -0
  32. package/dist/editor/commandApply.d.ts +5 -0
  33. package/dist/editor/commandApply.js +755 -0
  34. package/dist/editor/commands.d.ts +48 -3
  35. package/dist/editor/commands.js +40 -754
  36. package/dist/editor/directives.d.ts +94 -0
  37. package/dist/editor/directives.js +206 -0
  38. package/dist/editor/document.d.ts +37 -2
  39. package/dist/editor/document.js +422 -6
  40. package/dist/editor/entityCatalog.d.ts +42 -0
  41. package/dist/editor/entityCatalog.js +101 -0
  42. package/dist/editor/environment.d.ts +74 -0
  43. package/dist/editor/environment.js +169 -0
  44. package/dist/editor/index.d.ts +26 -1
  45. package/dist/editor/index.js +8 -1
  46. package/dist/editor/minimap.d.ts +31 -0
  47. package/dist/editor/minimap.js +73 -0
  48. package/dist/editor/streamer.d.ts +66 -0
  49. package/dist/editor/streamer.js +68 -0
  50. package/dist/editor/types.d.ts +143 -0
  51. package/dist/editor/world.d.ts +136 -0
  52. package/dist/editor/world.js +303 -0
  53. package/dist/game/breeding.d.ts +161 -0
  54. package/dist/game/breeding.js +181 -0
  55. package/dist/game/cameraConfig.d.ts +451 -0
  56. package/dist/game/cameraConfig.js +1 -0
  57. package/dist/game/defineGame.d.ts +16 -9
  58. package/dist/game/defineGame.js +14 -6
  59. package/dist/game/defineSystem.d.ts +2 -0
  60. package/dist/game/defineSystem.js +2 -0
  61. package/dist/game/events.d.ts +28 -0
  62. package/dist/game/playableGame.d.ts +34 -435
  63. package/dist/game/playableGame.js +1 -1
  64. package/dist/game/progression.d.ts +35 -0
  65. package/dist/game/progression.js +19 -0
  66. package/dist/game/quest.js +1 -3
  67. package/dist/game/tribe.d.ts +184 -0
  68. package/dist/game/tribe.js +432 -0
  69. package/dist/gameplay.d.ts +3 -1
  70. package/dist/gameplay.js +3 -1
  71. package/dist/inventory/encumbrance.d.ts +69 -0
  72. package/dist/inventory/encumbrance.js +68 -0
  73. package/dist/item/durability.js +1 -3
  74. package/dist/meta/changelog.d.ts +1 -1
  75. package/dist/meta/changelog.js +24 -1
  76. package/dist/movement/movementModel.d.ts +15 -1
  77. package/dist/movement/movementModel.js +33 -15
  78. package/dist/movement/playerMovement.js +102 -2
  79. package/dist/multiplayer/combatSnapshot.js +13 -8
  80. package/dist/multiplayer/presenceModel.d.ts +2 -0
  81. package/dist/multiplayer/presenceModel.js +8 -0
  82. package/dist/physics/drivableVehicle.d.ts +2 -0
  83. package/dist/physics/drivableVehicle.js +1 -1
  84. package/dist/physics/flightDynamics.d.ts +82 -0
  85. package/dist/physics/flightDynamics.js +165 -0
  86. package/dist/physics/gravityField.d.ts +25 -0
  87. package/dist/physics/gravityField.js +41 -0
  88. package/dist/physics/kinematicVehicle.d.ts +87 -0
  89. package/dist/physics/kinematicVehicle.js +193 -11
  90. package/dist/physics/physicsWorld.d.ts +26 -0
  91. package/dist/physics/physicsWorld.js +109 -117
  92. package/dist/physics/vehicleObstacles.d.ts +44 -0
  93. package/dist/physics/vehicleObstacles.js +115 -0
  94. package/dist/random/rng.d.ts +4 -1
  95. package/dist/random/rng.js +4 -1
  96. package/dist/runtime/adapter.d.ts +13 -0
  97. package/dist/runtime/adapter.js +13 -0
  98. package/dist/runtime/context/combat.d.ts +51 -0
  99. package/dist/runtime/context/combat.js +130 -0
  100. package/dist/runtime/context/deathLoot.d.ts +30 -0
  101. package/dist/runtime/context/deathLoot.js +39 -0
  102. package/dist/runtime/context/player.d.ts +48 -0
  103. package/dist/runtime/context/player.js +125 -0
  104. package/dist/runtime/context/scene.d.ts +59 -0
  105. package/dist/runtime/context/scene.js +291 -0
  106. package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
  107. package/dist/runtime/context/snapshotCodecs.js +26 -0
  108. package/dist/runtime/descriptors/baseline.js +26 -3
  109. package/dist/runtime/descriptors/features.d.ts +5 -0
  110. package/dist/runtime/descriptors/features.js +20 -2
  111. package/dist/runtime/gameContext.d.ts +2 -428
  112. package/dist/runtime/gameContext.js +88 -501
  113. package/dist/runtime/gameContextTypes.d.ts +467 -0
  114. package/dist/runtime/gameContextTypes.js +1 -0
  115. package/dist/runtime/headlessRunner.d.ts +4 -2
  116. package/dist/runtime/headlessRunner.js +1 -0
  117. package/dist/runtime/hostPolicy.d.ts +51 -0
  118. package/dist/runtime/hostPolicy.js +68 -0
  119. package/dist/runtime/hostedGameRunner.d.ts +3 -1
  120. package/dist/runtime/hostedGameRunner.js +1 -0
  121. package/dist/runtime/hostedWorldSession.d.ts +3 -1
  122. package/dist/runtime/hostedWorldSession.js +1 -0
  123. package/dist/runtime/worldSnapshot.d.ts +13 -1
  124. package/dist/runtime/worldSnapshot.js +15 -3
  125. package/dist/scene/assetCatalog.d.ts +5 -0
  126. package/dist/scene/assetSpace.js +1 -3
  127. package/dist/scene/authoredTriggers.d.ts +50 -0
  128. package/dist/scene/authoredTriggers.js +114 -0
  129. package/dist/scene/builtinSceneKinds.js +8 -3
  130. package/dist/scene/colliders.d.ts +58 -0
  131. package/dist/scene/colliders.js +108 -0
  132. package/dist/scene/collisionMesh.d.ts +94 -0
  133. package/dist/scene/collisionMesh.js +699 -0
  134. package/dist/scene/companion.d.ts +147 -0
  135. package/dist/scene/companion.js +193 -0
  136. package/dist/scene/entityStats.d.ts +7 -9
  137. package/dist/scene/entityStats.js +10 -14
  138. package/dist/scene/sceneKinds.d.ts +16 -0
  139. package/dist/scene/sceneKinds.js +4 -0
  140. package/dist/scene/sceneRaycast.js +15 -0
  141. package/dist/stats/spawnLevelStats.d.ts +110 -0
  142. package/dist/stats/spawnLevelStats.js +140 -0
  143. package/dist/stats/statPool.d.ts +70 -0
  144. package/dist/stats/statPool.js +59 -0
  145. package/dist/store/changeSignal.d.ts +3 -0
  146. package/dist/store/changeSignal.js +2 -0
  147. package/dist/store/observableKeyedStore.d.ts +2 -0
  148. package/dist/store/observableKeyedStore.js +1 -0
  149. package/dist/survival/decayMeter.js +1 -3
  150. package/dist/survival/regionHealth.js +1 -3
  151. package/dist/ui/hudLayout.d.ts +5 -0
  152. package/dist/ui/hudLayout.js +17 -1
  153. package/dist/world/authoredEntities.d.ts +29 -0
  154. package/dist/world/authoredEntities.js +29 -0
  155. package/dist/world/authoredObjects.d.ts +15 -1
  156. package/dist/world/authoredObjects.js +13 -1
  157. package/dist/world/authoredSpawn.d.ts +39 -0
  158. package/dist/world/authoredSpawn.js +37 -0
  159. package/dist/world/buildSockets.d.ts +152 -0
  160. package/dist/world/buildSockets.js +188 -0
  161. package/dist/world/cityBlocks.d.ts +152 -0
  162. package/dist/world/cityBlocks.js +670 -0
  163. package/dist/world/cityContent.d.ts +77 -0
  164. package/dist/world/cityContent.js +261 -0
  165. package/dist/world/cityGeometry.d.ts +114 -0
  166. package/dist/world/cityGeometry.js +573 -0
  167. package/dist/world/cityKind.d.ts +314 -0
  168. package/dist/world/cityKind.js +1578 -0
  169. package/dist/world/features.d.ts +18 -3
  170. package/dist/world/features.js +10 -1
  171. package/dist/world/grassKind.js +2 -2
  172. package/dist/world/markers.d.ts +50 -4
  173. package/dist/world/markers.js +49 -0
  174. package/dist/world/massing.js +1 -1
  175. package/dist/world/minimapBake.d.ts +78 -0
  176. package/dist/world/minimapBake.js +208 -0
  177. package/dist/world/minimapTrack.d.ts +17 -0
  178. package/dist/world/minimapTrack.js +22 -0
  179. package/dist/world/pathNetwork.d.ts +159 -0
  180. package/dist/world/pathNetwork.js +684 -0
  181. package/dist/world/place.d.ts +166 -0
  182. package/dist/world/place.js +96 -0
  183. package/dist/world/placeAsset.js +3 -1
  184. package/dist/world/poleLineKind.d.ts +40 -0
  185. package/dist/world/poleLineKind.js +97 -0
  186. package/dist/world/polyline.js +9 -2
  187. package/dist/world/resourceNode.d.ts +109 -0
  188. package/dist/world/resourceNode.js +140 -0
  189. package/dist/world/scatter.js +2 -20
  190. package/dist/world/streets.d.ts +29 -0
  191. package/dist/world/streets.js +55 -0
  192. package/dist/world/terraform.js +58 -88
  193. package/dist/world/terrain.d.ts +45 -0
  194. package/dist/world/terrain.js +78 -0
  195. package/dist/world.d.ts +14 -5
  196. package/dist/world.js +12 -3
  197. package/package.json +1 -1
  198. package/skills/game-design/SKILL.md +28 -0
  199. package/skills/game-design/agents/openai.yaml +4 -0
  200. package/skills/game-design/references/application-playbook.md +170 -0
  201. package/skills/game-design/references/field-guide.md +288 -0
  202. package/skills/jgengine/SKILL.md +7 -4
  203. package/skills/jgengine/api.md +172 -79
  204. package/skills/jgengine/capabilities.md +104 -2
  205. package/skills/jgengine/recipes/minimal-game.md +88 -0
  206. package/skills/jgengine-combat/SKILL.md +5 -1
  207. package/skills/jgengine-combat/api.md +36 -0
  208. package/skills/jgengine-combat/capabilities.md +44 -0
  209. package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
  210. package/skills/jgengine-combat/reference.md +12 -2
  211. package/skills/jgengine-gameplay/api.md +152 -26
  212. package/skills/jgengine-gameplay/capabilities.md +78 -2
  213. package/skills/jgengine-gameplay/reference-systems.md +1 -5
  214. package/skills/jgengine-world/SKILL.md +13 -0
  215. package/skills/jgengine-world/api.md +419 -15
  216. package/skills/jgengine-world/capabilities.md +108 -2
  217. package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
  218. package/skills/jgengine-world/reference.md +43 -4
  219. package/skills/jgengine/reference-cartridge.md +0 -72
@@ -95,6 +95,9 @@ export class PhysicsWorld {
95
95
  jFreeCount = 0;
96
96
  jointHigh = 0;
97
97
  jointActiveCount = 0;
98
+ // Reused scratch for a joint's two world anchor points [ax,ay,az,bx,by,bz]; filled by anchorWorld
99
+ // so the gather is defined once and no per-call array is allocated on the joint hot path.
100
+ anchorScratch = new Float64Array(6);
98
101
  // Removed bodies tombstone (FLAG_DEAD) rather than compact: `id` is the SoA slot itself, handed
99
102
  // out to callers and stored raw in joints/game state, so a slot can never move once allocated.
100
103
  // `bodyHigh` is the high-water mark of ever-used slots; `bodyFree` recycles dead ones for addBody.
@@ -313,18 +316,29 @@ export class PhysicsWorld {
313
316
  setJointRest(id, restLength) {
314
317
  this.jRest[id] = restLength;
315
318
  }
316
- jointDistance(id) {
319
+ /**
320
+ * Gather joint `id`'s two world anchor points into {@link anchorScratch} as [ax,ay,az,bx,by,bz]
321
+ * (A's local offset added to body A's centre, or world-origin when A is unset; B likewise, treating
322
+ * `anchorB` as a world point when B is unset). The single definition shared by {@link jointDistance},
323
+ * {@link readJointSegments}, and {@link solveJoints}.
324
+ */
325
+ anchorWorld(id) {
326
+ const s = this.anchorScratch;
317
327
  const bA = this.jBodyA[id];
318
328
  const bB = this.jBodyB[id];
319
- const ax = (bA >= 0 ? this.posX[bA] : 0) + this.jAnchorAX[id];
320
- const ay = (bA >= 0 ? this.posY[bA] : 0) + this.jAnchorAY[id];
321
- const az = (bA >= 0 ? this.posZ[bA] : 0) + this.jAnchorAZ[id];
322
- const bx = bB >= 0 ? this.posX[bB] + this.jAnchorBX[id] : this.jAnchorBX[id];
323
- const by = bB >= 0 ? this.posY[bB] + this.jAnchorBY[id] : this.jAnchorBY[id];
324
- const bz = bB >= 0 ? this.posZ[bB] + this.jAnchorBZ[id] : this.jAnchorBZ[id];
325
- const dx = bx - ax;
326
- const dy = by - ay;
327
- const dz = bz - az;
329
+ s[0] = (bA >= 0 ? this.posX[bA] : 0) + this.jAnchorAX[id];
330
+ s[1] = (bA >= 0 ? this.posY[bA] : 0) + this.jAnchorAY[id];
331
+ s[2] = (bA >= 0 ? this.posZ[bA] : 0) + this.jAnchorAZ[id];
332
+ s[3] = bB >= 0 ? this.posX[bB] + this.jAnchorBX[id] : this.jAnchorBX[id];
333
+ s[4] = bB >= 0 ? this.posY[bB] + this.jAnchorBY[id] : this.jAnchorBY[id];
334
+ s[5] = bB >= 0 ? this.posZ[bB] + this.jAnchorBZ[id] : this.jAnchorBZ[id];
335
+ }
336
+ jointDistance(id) {
337
+ this.anchorWorld(id);
338
+ const s = this.anchorScratch;
339
+ const dx = s[3] - s[0];
340
+ const dy = s[4] - s[1];
341
+ const dz = s[5] - s[2];
328
342
  return Math.sqrt(dx * dx + dy * dy + dz * dz);
329
343
  }
330
344
  /** Write [ax,ay,az,bx,by,bz] world endpoints per active joint into `out`; returns the joint count. */
@@ -333,15 +347,15 @@ export class PhysicsWorld {
333
347
  for (let id = 0; id < this.jointHigh; id += 1) {
334
348
  if (this.jActive[id] !== 1)
335
349
  continue;
336
- const bA = this.jBodyA[id];
337
- const bB = this.jBodyB[id];
350
+ this.anchorWorld(id);
351
+ const s = this.anchorScratch;
338
352
  const o = n * 6;
339
- out[o] = (bA >= 0 ? this.posX[bA] : 0) + this.jAnchorAX[id];
340
- out[o + 1] = (bA >= 0 ? this.posY[bA] : 0) + this.jAnchorAY[id];
341
- out[o + 2] = (bA >= 0 ? this.posZ[bA] : 0) + this.jAnchorAZ[id];
342
- out[o + 3] = bB >= 0 ? this.posX[bB] + this.jAnchorBX[id] : this.jAnchorBX[id];
343
- out[o + 4] = bB >= 0 ? this.posY[bB] + this.jAnchorBY[id] : this.jAnchorBY[id];
344
- out[o + 5] = bB >= 0 ? this.posZ[bB] + this.jAnchorBZ[id] : this.jAnchorBZ[id];
353
+ out[o] = s[0];
354
+ out[o + 1] = s[1];
355
+ out[o + 2] = s[2];
356
+ out[o + 3] = s[3];
357
+ out[o + 4] = s[4];
358
+ out[o + 5] = s[5];
345
359
  n += 1;
346
360
  }
347
361
  return n;
@@ -609,6 +623,29 @@ export class PhysicsWorld {
609
623
  this.velY[i] = this.velY[i] < rt ? 0 : -this.velY[i] * e;
610
624
  }
611
625
  }
626
+ /**
627
+ * Walk every broadphase cell body `i`'s AABB overlaps, invoking `visit(cell)` once per cell. The
628
+ * cell-range math (six `cellCoord` calls plus the row-major triple loop) lives here so the count
629
+ * and scatter passes of {@link buildGrid} share one definition. `visit` is a caller-hoisted closure
630
+ * (created once per `buildGrid`, not per body) so the hot loop stays allocation-free.
631
+ */
632
+ forEachBodyCell(i, visit) {
633
+ const nx = this.nx;
634
+ const ny = this.ny;
635
+ const x0 = cellCoord(this.posX[i] - this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
636
+ const x1 = cellCoord(this.posX[i] + this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
637
+ const y0 = cellCoord(this.posY[i] - this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
638
+ const y1 = cellCoord(this.posY[i] + this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
639
+ const z0 = cellCoord(this.posZ[i] - this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
640
+ const z1 = cellCoord(this.posZ[i] + this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
641
+ for (let z = z0; z <= z1; z += 1) {
642
+ for (let y = y0; y <= y1; y += 1) {
643
+ const rowBase = (z * ny + y) * nx;
644
+ for (let x = x0; x <= x1; x += 1)
645
+ visit(rowBase + x);
646
+ }
647
+ }
648
+ }
612
649
  buildGrid() {
613
650
  const n = this.bodyHigh;
614
651
  const start = this.cellStart;
@@ -618,24 +655,13 @@ export class PhysicsWorld {
618
655
  const i = this.awakeList[a];
619
656
  this.bodyCell[i] = this.cellOf(i);
620
657
  }
621
- const nx = this.nx;
622
- const ny = this.ny;
658
+ const countCell = (cell) => {
659
+ start[cell + 1] += 1;
660
+ };
623
661
  for (let i = 0; i < n; i += 1) {
624
662
  if ((this.flags[i] & FLAG_DEAD) !== 0)
625
663
  continue;
626
- const x0 = cellCoord(this.posX[i] - this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
627
- const x1 = cellCoord(this.posX[i] + this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
628
- const y0 = cellCoord(this.posY[i] - this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
629
- const y1 = cellCoord(this.posY[i] + this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
630
- const z0 = cellCoord(this.posZ[i] - this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
631
- const z1 = cellCoord(this.posZ[i] + this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
632
- for (let z = z0; z <= z1; z += 1) {
633
- for (let y = y0; y <= y1; y += 1) {
634
- const rowBase = (z * ny + y) * nx;
635
- for (let x = x0; x <= x1; x += 1)
636
- start[rowBase + x + 1] += 1;
637
- }
638
- }
664
+ this.forEachBodyCell(i, countCell);
639
665
  }
640
666
  for (let c = 0; c < this.numCells; c += 1) {
641
667
  start[c + 1] += start[c];
@@ -645,24 +671,15 @@ export class PhysicsWorld {
645
671
  if (total > this.sorted.length) {
646
672
  this.sorted = new Int32Array(Math.max(total, this.sorted.length * 2));
647
673
  }
674
+ let scatterBody = 0;
675
+ const scatterCell = (cell) => {
676
+ this.sorted[this.cursor[cell]++] = scatterBody;
677
+ };
648
678
  for (let i = 0; i < n; i += 1) {
649
679
  if ((this.flags[i] & FLAG_DEAD) !== 0)
650
680
  continue;
651
- const x0 = cellCoord(this.posX[i] - this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
652
- const x1 = cellCoord(this.posX[i] + this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
653
- const y0 = cellCoord(this.posY[i] - this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
654
- const y1 = cellCoord(this.posY[i] + this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
655
- const z0 = cellCoord(this.posZ[i] - this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
656
- const z1 = cellCoord(this.posZ[i] + this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
657
- for (let z = z0; z <= z1; z += 1) {
658
- for (let y = y0; y <= y1; y += 1) {
659
- const rowBase = (z * ny + y) * nx;
660
- for (let x = x0; x <= x1; x += 1) {
661
- const c = rowBase + x;
662
- this.sorted[this.cursor[c]++] = i;
663
- }
664
- }
665
- }
681
+ scatterBody = i;
682
+ this.forEachBodyCell(i, scatterCell);
666
683
  }
667
684
  }
668
685
  cellOf(i) {
@@ -867,6 +884,39 @@ export class PhysicsWorld {
867
884
  this.posY[j] += corr * imJ * nY;
868
885
  this.posZ[j] += corr * imJ * nZ;
869
886
  }
887
+ /**
888
+ * Apply an axial velocity impulse `jn` along unit axis (nX,nY,nZ) to a joint's two bodies: body A
889
+ * recoils (−) and body B advances (+), each scaled by its inverse mass. A world anchor (body index
890
+ * < 0) is skipped. Scalar args only — no allocation on the joint hot path.
891
+ */
892
+ applyAxialImpulse(bA, bB, imA, imB, jn, nX, nY, nZ) {
893
+ if (bA >= 0) {
894
+ this.velX[bA] -= jn * imA * nX;
895
+ this.velY[bA] -= jn * imA * nY;
896
+ this.velZ[bA] -= jn * imA * nZ;
897
+ }
898
+ if (bB >= 0) {
899
+ this.velX[bB] += jn * imB * nX;
900
+ this.velY[bB] += jn * imB * nY;
901
+ this.velZ[bB] += jn * imB * nZ;
902
+ }
903
+ }
904
+ /**
905
+ * Positional (Baumgarte) twin of {@link applyAxialImpulse}: nudge the joint's two bodies along
906
+ * (nX,nY,nZ) by `corr` — body A advances (+), body B recoils (−), each scaled by its inverse mass.
907
+ */
908
+ applyAxialCorrection(bA, bB, imA, imB, corr, nX, nY, nZ) {
909
+ if (bA >= 0) {
910
+ this.posX[bA] += corr * imA * nX;
911
+ this.posY[bA] += corr * imA * nY;
912
+ this.posZ[bA] += corr * imA * nZ;
913
+ }
914
+ if (bB >= 0) {
915
+ this.posX[bB] -= corr * imB * nX;
916
+ this.posY[bB] -= corr * imB * nY;
917
+ this.posZ[bB] -= corr * imB * nZ;
918
+ }
919
+ }
870
920
  solveJoints(dt) {
871
921
  const beta = this.jointCorrection;
872
922
  const slop = this.slop;
@@ -876,15 +926,11 @@ export class PhysicsWorld {
876
926
  const kind = this.jKind[id];
877
927
  const bA = this.jBodyA[id];
878
928
  const bB = this.jBodyB[id];
879
- const ax = (bA >= 0 ? this.posX[bA] : 0) + this.jAnchorAX[id];
880
- const ay = (bA >= 0 ? this.posY[bA] : 0) + this.jAnchorAY[id];
881
- const az = (bA >= 0 ? this.posZ[bA] : 0) + this.jAnchorAZ[id];
882
- const bx = bB >= 0 ? this.posX[bB] + this.jAnchorBX[id] : this.jAnchorBX[id];
883
- const by = bB >= 0 ? this.posY[bB] + this.jAnchorBY[id] : this.jAnchorBY[id];
884
- const bz = bB >= 0 ? this.posZ[bB] + this.jAnchorBZ[id] : this.jAnchorBZ[id];
885
- let dx = bx - ax;
886
- let dy = by - ay;
887
- let dz = bz - az;
929
+ this.anchorWorld(id);
930
+ const s = this.anchorScratch;
931
+ let dx = s[3] - s[0];
932
+ let dy = s[4] - s[1];
933
+ let dz = s[5] - s[2];
888
934
  const imA = bA >= 0 ? this.invMass[bA] : 0;
889
935
  const imB = bB >= 0 ? this.invMass[bB] : 0;
890
936
  const imSum = imA + imB;
@@ -924,27 +970,9 @@ export class PhysicsWorld {
924
970
  }
925
971
  const rvn = rvx * nX + rvy * nY + rvz * nZ;
926
972
  const jn = -rvn / imSum;
927
- if (bA >= 0) {
928
- this.velX[bA] -= jn * imA * nX;
929
- this.velY[bA] -= jn * imA * nY;
930
- this.velZ[bA] -= jn * imA * nZ;
931
- }
932
- if (bB >= 0) {
933
- this.velX[bB] += jn * imB * nX;
934
- this.velY[bB] += jn * imB * nY;
935
- this.velZ[bB] += jn * imB * nZ;
936
- }
973
+ this.applyAxialImpulse(bA, bB, imA, imB, jn, nX, nY, nZ);
937
974
  const corr = (dist * beta) / imSum;
938
- if (bA >= 0) {
939
- this.posX[bA] += corr * imA * nX;
940
- this.posY[bA] += corr * imA * nY;
941
- this.posZ[bA] += corr * imA * nZ;
942
- }
943
- if (bB >= 0) {
944
- this.posX[bB] -= corr * imB * nX;
945
- this.posY[bB] -= corr * imB * nY;
946
- this.posZ[bB] -= corr * imB * nZ;
947
- }
975
+ this.applyAxialCorrection(bA, bB, imA, imB, corr, nX, nY, nZ);
948
976
  }
949
977
  else {
950
978
  const rvl = Math.sqrt(rvx * rvx + rvy * rvy + rvz * rvz);
@@ -953,16 +981,7 @@ export class PhysicsWorld {
953
981
  const nY = rvy / rvl;
954
982
  const nZ = rvz / rvl;
955
983
  const jn = -rvl / imSum;
956
- if (bA >= 0) {
957
- this.velX[bA] -= jn * imA * nX;
958
- this.velY[bA] -= jn * imA * nY;
959
- this.velZ[bA] -= jn * imA * nZ;
960
- }
961
- if (bB >= 0) {
962
- this.velX[bB] += jn * imB * nX;
963
- this.velY[bB] += jn * imB * nY;
964
- this.velZ[bB] += jn * imB * nZ;
965
- }
984
+ this.applyAxialImpulse(bA, bB, imA, imB, jn, nX, nY, nZ);
966
985
  }
967
986
  }
968
987
  continue;
@@ -994,16 +1013,7 @@ export class PhysicsWorld {
994
1013
  jn = maxJ;
995
1014
  else if (jn < -maxJ)
996
1015
  jn = -maxJ;
997
- if (bA >= 0) {
998
- this.velX[bA] -= jn * imA * nX;
999
- this.velY[bA] -= jn * imA * nY;
1000
- this.velZ[bA] -= jn * imA * nZ;
1001
- }
1002
- if (bB >= 0) {
1003
- this.velX[bB] += jn * imB * nX;
1004
- this.velY[bB] += jn * imB * nY;
1005
- this.velZ[bB] += jn * imB * nZ;
1006
- }
1016
+ this.applyAxialImpulse(bA, bB, imA, imB, jn, nX, nY, nZ);
1007
1017
  continue;
1008
1018
  }
1009
1019
  if (kind !== JOINT_FIXED && kind !== JOINT_DISTANCE) {
@@ -1016,27 +1026,9 @@ export class PhysicsWorld {
1016
1026
  this.wake(bB);
1017
1027
  }
1018
1028
  const jn = -rvn / imSum;
1019
- if (bA >= 0) {
1020
- this.velX[bA] -= jn * imA * nX;
1021
- this.velY[bA] -= jn * imA * nY;
1022
- this.velZ[bA] -= jn * imA * nZ;
1023
- }
1024
- if (bB >= 0) {
1025
- this.velX[bB] += jn * imB * nX;
1026
- this.velY[bB] += jn * imB * nY;
1027
- this.velZ[bB] += jn * imB * nZ;
1028
- }
1029
+ this.applyAxialImpulse(bA, bB, imA, imB, jn, nX, nY, nZ);
1029
1030
  const corr = (c * beta) / imSum;
1030
- if (bA >= 0) {
1031
- this.posX[bA] += corr * imA * nX;
1032
- this.posY[bA] += corr * imA * nY;
1033
- this.posZ[bA] += corr * imA * nZ;
1034
- }
1035
- if (bB >= 0) {
1036
- this.posX[bB] -= corr * imB * nX;
1037
- this.posY[bB] -= corr * imB * nY;
1038
- this.posZ[bB] -= corr * imB * nZ;
1039
- }
1031
+ this.applyAxialCorrection(bA, bB, imA, imB, corr, nX, nY, nZ);
1040
1032
  }
1041
1033
  }
1042
1034
  updateSleep() {
@@ -0,0 +1,44 @@
1
+ import { type CollisionObstacle } from "../movement/movementModel.js";
2
+ /**
3
+ * A single blocked-move event a car can react to (#1051): crash damage, screen shake, a metal-crunch
4
+ * cue. Reported once, when a move is first vetoed, then consumed by {@link VehicleObstacleClamp.takeImpact}.
5
+ */
6
+ export interface VehicleImpact {
7
+ /** Speed (world units/s) the car was carrying *into* the obstacle at the moment the move was blocked. */
8
+ closingSpeed: number;
9
+ /** Unit push-back direction — points from the struck surface back toward the car. */
10
+ normal: readonly [number, number];
11
+ /** Index into the obstacle set passed for the blocking tick. */
12
+ obstacleIndex: number;
13
+ }
14
+ /**
15
+ * A planar move clamp for a kinematic car: feed {@link clampMove} to a vehicle's `clampMove` hook so
16
+ * an attempted XZ displacement slides along world solids instead of driving through them, and read
17
+ * {@link takeImpact} once per tick for the crash it produced.
18
+ */
19
+ export interface VehicleObstacleClamp {
20
+ /** Resolve an attempted `from`→`to` XZ move to the destination actually allowed (slide-along). */
21
+ clampMove(from: readonly [number, number], to: readonly [number, number]): readonly [number, number];
22
+ /** Impact recorded since last take, consumed on read (null when none). Closing speed is the velocity component into the obstacle at block time, units/s. */
23
+ takeImpact(): VehicleImpact | null;
24
+ }
25
+ /**
26
+ * Build a slide-along move clamp for a kinematic car (#1051). `obstacles` is sampled fresh each tick —
27
+ * the caller hands back the already-filtered set of solids near the car — and `dt` supplies the current
28
+ * tick length so a blocked move's lost displacement converts to a closing speed. `radius` inflates each
29
+ * obstacle footprint by the car's body radius (default {@link DEFAULT_VEHICLE_RADIUS}).
30
+ *
31
+ * The slide itself is delegated to {@link resolveObstacleStep} so cars and pedestrians share one proven
32
+ * axis-separated resolver: a shallow graze keeps its tangential run and only the into-wall component is
33
+ * shed, a head-on zeroes forward progress. When any component is shed the largest such block this tick is
34
+ * kept as a {@link VehicleImpact}; {@link VehicleObstacleClamp.takeImpact} returns and clears it so a hit
35
+ * fires exactly once. The happy path (no solids near) is a zero-allocation passthrough returning `to`.
36
+ */
37
+ export declare function createVehicleObstacleClamp(options: {
38
+ /** Solids near the car this tick (already filtered to the relevant, solid set). */
39
+ obstacles: () => readonly CollisionObstacle[];
40
+ /** Vehicle body radius (units). Default {@link DEFAULT_VEHICLE_RADIUS}. */
41
+ radius?: number;
42
+ /** Current tick dt (seconds) — the divisor turning shed displacement into closing speed. */
43
+ dt: () => number;
44
+ }): VehicleObstacleClamp;
@@ -0,0 +1,115 @@
1
+ import { resolveObstacleStep } from "../movement/movementModel.js";
2
+ /** Matches the unit-box default {@link resolveObstacleStep} applies to obstacles without explicit extents. */
3
+ const DEFAULT_OBSTACLE_HALF_EXTENTS = [0.5, 0.5, 0.5];
4
+ /** Default vehicle body radius (units) inflating obstacle footprints; a compact hatchback-ish disc. */
5
+ const DEFAULT_VEHICLE_RADIUS = 1.4;
6
+ /**
7
+ * Y the car body is pinned to when handing solids to {@link resolveObstacleStep}. That stepper gates
8
+ * each obstacle by a 1.8-tall pedestrian span around `current[1]`; centring every pre-filtered solid
9
+ * on this value makes the gate always admit them, so collision is purely horizontal and the car hits
10
+ * world solids at any ground elevation (bridges, hills) even though the clamp only knows XZ.
11
+ */
12
+ const CAR_BODY_MID_Y = 0.9;
13
+ /** Below this the returned step is treated as unblocked (float-noise slack), so no phantom impacts fire. */
14
+ const BLOCK_EPSILON = 1e-6;
15
+ /**
16
+ * Build a slide-along move clamp for a kinematic car (#1051). `obstacles` is sampled fresh each tick —
17
+ * the caller hands back the already-filtered set of solids near the car — and `dt` supplies the current
18
+ * tick length so a blocked move's lost displacement converts to a closing speed. `radius` inflates each
19
+ * obstacle footprint by the car's body radius (default {@link DEFAULT_VEHICLE_RADIUS}).
20
+ *
21
+ * The slide itself is delegated to {@link resolveObstacleStep} so cars and pedestrians share one proven
22
+ * axis-separated resolver: a shallow graze keeps its tangential run and only the into-wall component is
23
+ * shed, a head-on zeroes forward progress. When any component is shed the largest such block this tick is
24
+ * kept as a {@link VehicleImpact}; {@link VehicleObstacleClamp.takeImpact} returns and clears it so a hit
25
+ * fires exactly once. The happy path (no solids near) is a zero-allocation passthrough returning `to`.
26
+ */
27
+ export function createVehicleObstacleClamp(options) {
28
+ const radius = options.radius ?? DEFAULT_VEHICLE_RADIUS;
29
+ // Grow-only scratch of Y-normalised solids, reused across ticks so a sustained crash allocates nothing
30
+ // after warm-up. Per-clamp (closure-local) so concurrent cars never share it.
31
+ const scratch = [];
32
+ let pending = null;
33
+ function vehicleView(source) {
34
+ const n = source.length;
35
+ for (let i = 0; i < n; i += 1) {
36
+ let entry = scratch[i];
37
+ if (entry === undefined) {
38
+ entry = { position: [0, 0, 0] };
39
+ scratch[i] = entry;
40
+ }
41
+ const from = source[i];
42
+ entry.position[0] = from.position[0];
43
+ entry.position[1] = CAR_BODY_MID_Y;
44
+ entry.position[2] = from.position[2];
45
+ entry.halfExtents = from.halfExtents;
46
+ }
47
+ if (scratch.length > n)
48
+ scratch.length = n;
49
+ return scratch;
50
+ }
51
+ /** Obstacle the attempted endpoint penetrates deepest (its inflated footprint), else nearest centre. */
52
+ function blockingIndex(source, tx, tz) {
53
+ let bestIndex = -1;
54
+ let bestDepth = -Number.POSITIVE_INFINITY;
55
+ let fallbackIndex = 0;
56
+ let fallbackDistSq = Number.POSITIVE_INFINITY;
57
+ for (let i = 0; i < source.length; i += 1) {
58
+ const o = source[i];
59
+ const half = o.halfExtents ?? DEFAULT_OBSTACLE_HALF_EXTENTS;
60
+ const ex = half[0] + radius;
61
+ const ez = half[2] + radius;
62
+ const minX = o.position[0] - ex;
63
+ const maxX = o.position[0] + ex;
64
+ const minZ = o.position[2] - ez;
65
+ const maxZ = o.position[2] + ez;
66
+ if (tx > minX && tx < maxX && tz > minZ && tz < maxZ) {
67
+ const depth = Math.min(tx - minX, maxX - tx, tz - minZ, maxZ - tz);
68
+ if (depth > bestDepth) {
69
+ bestDepth = depth;
70
+ bestIndex = i;
71
+ }
72
+ }
73
+ const dx = o.position[0] - tx;
74
+ const dz = o.position[2] - tz;
75
+ const distSq = dx * dx + dz * dz;
76
+ if (distSq < fallbackDistSq) {
77
+ fallbackDistSq = distSq;
78
+ fallbackIndex = i;
79
+ }
80
+ }
81
+ return bestIndex >= 0 ? bestIndex : fallbackIndex;
82
+ }
83
+ return {
84
+ clampMove(from, to) {
85
+ const obstacles = options.obstacles();
86
+ if (obstacles.length === 0)
87
+ return to;
88
+ const attemptedX = to[0] - from[0];
89
+ const attemptedZ = to[1] - from[1];
90
+ const step = resolveObstacleStep([from[0], CAR_BODY_MID_Y, from[1]], attemptedX, attemptedZ, vehicleView(obstacles), radius);
91
+ const blockedX = attemptedX - step.stepX;
92
+ const blockedZ = attemptedZ - step.stepZ;
93
+ if (blockedX === 0 && blockedZ === 0)
94
+ return to;
95
+ const blockedMag = Math.hypot(blockedX, blockedZ);
96
+ if (blockedMag > BLOCK_EPSILON) {
97
+ const dt = options.dt();
98
+ const closingSpeed = dt > 0 ? blockedMag / dt : 0;
99
+ if (pending === null || closingSpeed > pending.closingSpeed) {
100
+ pending = {
101
+ closingSpeed,
102
+ normal: [-blockedX / blockedMag, -blockedZ / blockedMag],
103
+ obstacleIndex: blockingIndex(obstacles, to[0], to[1]),
104
+ };
105
+ }
106
+ }
107
+ return [from[0] + step.stepX, from[1] + step.stepZ];
108
+ },
109
+ takeImpact() {
110
+ const impact = pending;
111
+ pending = null;
112
+ return impact;
113
+ },
114
+ };
115
+ }
@@ -20,7 +20,10 @@ export declare function randomSeedFrom(seed: number): RandomSeed;
20
20
  * instead of closing over a generator.
21
21
  */
22
22
  export declare function stepRandomSeed(seed: RandomSeed): readonly [value: number, next: RandomSeed];
23
- /** Deterministic pseudo-random generator seeded from a string or number — same seed, same sequence. */
23
+ /** Deterministic pseudo-random generator seeded from a string or number — same seed, same sequence.
24
+ *
25
+ * @capability seeded-random injected deterministic randomness for game logic — never `Math.random` in a simulation
26
+ */
24
27
  export declare function seededRng(seed: string | number): () => number;
25
28
  /** Derives independent, deterministic {@link seededRng} streams from one base seed, keyed by stream name. */
26
29
  export declare function seededStreams(seed: string | number): (stream: string) => () => number;
@@ -29,7 +29,10 @@ export function stepRandomSeed(seed) {
29
29
  const value = ((t ^ (t >>> 14)) >>> 0) / 4294967296;
30
30
  return [value, next];
31
31
  }
32
- /** Deterministic pseudo-random generator seeded from a string or number — same seed, same sequence. */
32
+ /** Deterministic pseudo-random generator seeded from a string or number — same seed, same sequence.
33
+ *
34
+ * @capability seeded-random injected deterministic randomness for game logic — never `Math.random` in a simulation
35
+ */
33
36
  export function seededRng(seed) {
34
37
  let cursor = randomSeedFrom(hashSeed(seed));
35
38
  return () => {
@@ -56,6 +56,8 @@ export declare function convexPresence(config?: {
56
56
  * WebSocket transport. Omitting `authority` (or passing `"client"`) is **presence-only** — prefer
57
57
  * `wsPresence()` to name that intent explicitly. Pass `{ authority: "server" }` for a shared,
58
58
  * host-authoritative world — see `examples/HOSTED.md`.
59
+ *
60
+ * @capability multiplayer-ws server-hosted shared world over WebSocket — `multiplayer: ws({ authority: "server" })`
59
61
  */
60
62
  export declare function ws(config?: {
61
63
  topology?: MultiplayerTopology;
@@ -78,6 +80,11 @@ export declare function socketIo(config?: {
78
80
  url?: string;
79
81
  authority?: MultiplayerAuthority;
80
82
  }): MultiplayerAdapterConfig;
83
+ /**
84
+ * Serverless peer-to-peer (WebRTC) session — one peer hosts, friends join by room code.
85
+ *
86
+ * @capability multiplayer-p2p serverless co-op over WebRTC — `multiplayer: p2p({ room })`
87
+ */
81
88
  export declare function p2p(config?: {
82
89
  topology?: MultiplayerTopology;
83
90
  room?: string;
@@ -105,6 +112,12 @@ export declare function resolveAuthority(multiplayer: unknown): MultiplayerAutho
105
112
  export declare function isPresenceOnly(multiplayer: unknown): boolean;
106
113
  /** True for a single-player world — no adapter, or an explicit `offline()` one. Gates offline-only wiring like local whole-world save. */
107
114
  export declare function isOffline(multiplayer: unknown): boolean;
115
+ /**
116
+ * Explicit single-player adapter. Solo games never need this — omitting `multiplayer` in the shell
117
+ * `defineGame` already defaults to offline; pass it only where an adapter value is structurally required.
118
+ *
119
+ * @capability multiplayer-offline explicit solo adapter — omit `multiplayer` instead; offline is the shell default
120
+ */
108
121
  export declare function offline(): MultiplayerAdapterConfig;
109
122
  export declare function adapterOf(multiplayer: unknown): MultiplayerAdapterConfig | null;
110
123
  export declare function multiplayerAdapterKind(multiplayer: unknown): string | null;
@@ -14,6 +14,8 @@ export function convexPresence(config) {
14
14
  * WebSocket transport. Omitting `authority` (or passing `"client"`) is **presence-only** — prefer
15
15
  * `wsPresence()` to name that intent explicitly. Pass `{ authority: "server" }` for a shared,
16
16
  * host-authoritative world — see `examples/HOSTED.md`.
17
+ *
18
+ * @capability multiplayer-ws server-hosted shared world over WebSocket — `multiplayer: ws({ authority: "server" })`
17
19
  */
18
20
  export function ws(config) {
19
21
  return { kind: "ws", topology: config?.topology ?? "shared", url: config?.url, authority: config?.authority };
@@ -33,6 +35,11 @@ export function fly(config) {
33
35
  export function socketIo(config) {
34
36
  return { kind: "socketio", topology: config?.topology ?? "shared", url: config?.url, authority: config?.authority };
35
37
  }
38
+ /**
39
+ * Serverless peer-to-peer (WebRTC) session — one peer hosts, friends join by room code.
40
+ *
41
+ * @capability multiplayer-p2p serverless co-op over WebRTC — `multiplayer: p2p({ room })`
42
+ */
36
43
  export function p2p(config) {
37
44
  return { kind: "p2p", topology: config?.topology ?? "private", room: config?.room, authority: config?.authority };
38
45
  }
@@ -69,6 +76,12 @@ export function isOffline(multiplayer) {
69
76
  const adapter = adapterOf(multiplayer);
70
77
  return adapter === null || adapter.kind === "offline";
71
78
  }
79
+ /**
80
+ * Explicit single-player adapter. Solo games never need this — omitting `multiplayer` in the shell
81
+ * `defineGame` already defaults to offline; pass it only where an adapter value is structurally required.
82
+ *
83
+ * @capability multiplayer-offline explicit solo adapter — omit `multiplayer` instead; offline is the shell default
84
+ */
72
85
  export function offline() {
73
86
  return { kind: "offline" };
74
87
  }
@@ -0,0 +1,51 @@
1
+ import { type DeathSystem } from "../../combat/death.js";
2
+ import { type CombatSpatialDeps, type EffectSystem } from "../../combat/effects.js";
3
+ import { type ProjectileSystem } from "../../combat/projectiles.js";
4
+ import type { PhysicsConfig } from "../../game/defineGame.js";
5
+ import type { GameEvents } from "../../game/events.js";
6
+ import { type VfxInstanceStore } from "../../game/vfxInstance.js";
7
+ import type { LootRegistry } from "../../game/lootTable.js";
8
+ import type { EntityColliderSet } from "../../scene/colliders.js";
9
+ import type { EntityStore } from "../../scene/entityStore.js";
10
+ import type { ObjectStore } from "../../scene/objectStore.js";
11
+ import type { SceneRaycastApi } from "../../scene/sceneRaycast.js";
12
+ import type { WeaponStats } from "../../item/weapon.js";
13
+ import type { SimClock } from "../../time/simClock.js";
14
+ import type { WorldItemRecord, WorldItemSpawnInput } from "../../game/worldItem.js";
15
+ import type { GameContextContent, GameContextEntityEntry, GameContextLoot } from "../gameContext.js";
16
+ import { type CombatFx } from "./combatFx.js";
17
+ /** @internal Wiring combat needs from the live scene, loot, and command seams. */
18
+ export interface CombatSubsystemDeps {
19
+ content: GameContextContent;
20
+ signalNotify: () => void;
21
+ now: () => number;
22
+ events: GameEvents;
23
+ time: SimClock;
24
+ entities: EntityStore;
25
+ objects: ObjectStore;
26
+ combatSpatial: CombatSpatialDeps;
27
+ sceneRaycast: SceneRaycastApi;
28
+ entityCollidersOf: (instanceId: string) => EntityColliderSet | null;
29
+ objectCollidersOf: (instanceId: string) => EntityColliderSet | null;
30
+ catalogEntry: (instanceId: string) => GameContextEntityEntry | null | undefined;
31
+ statsByInstance: Map<string, import("../../scene/entityStats.js").StatValueMap>;
32
+ weapon: WeaponStats;
33
+ loot: GameContextLoot;
34
+ lootRegistry: LootRegistry;
35
+ spawnWorldItem: (input: WorldItemSpawnInput) => WorldItemRecord;
36
+ despawnEntity: (instanceId: string) => boolean;
37
+ runCommand: (name: string, args: unknown) => void;
38
+ localUserId: string;
39
+ physics?: PhysicsConfig;
40
+ }
41
+ /** @internal Effects, projectiles, death, and combat presentation surface. */
42
+ export interface CombatSubsystem {
43
+ death: DeathSystem;
44
+ effects: EffectSystem;
45
+ floatingEffects: EffectSystem;
46
+ projectiles: ProjectileSystem;
47
+ combatFx: CombatFx;
48
+ vfxInstances: VfxInstanceStore;
49
+ }
50
+ /** @internal */
51
+ export declare function createCombatSubsystem(d: CombatSubsystemDeps): CombatSubsystem;