@jgengine/core 0.18.0 → 0.19.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 (318) hide show
  1. package/CHANGELOG.md +265 -2
  2. package/dist/ai/decisionGraph.d.ts +96 -0
  3. package/dist/ai/decisionGraph.js +212 -0
  4. package/dist/ai/groupAssist.d.ts +10 -0
  5. package/dist/ai/groupAssist.js +11 -0
  6. package/dist/ai/mobBrainGraph.d.ts +3 -0
  7. package/dist/ai/mobBrainGraph.js +25 -0
  8. package/dist/ai/pursuit.d.ts +1 -1
  9. package/dist/ai/pursuit.js +1 -1
  10. package/dist/ai/tacticalQueries.d.ts +30 -0
  11. package/dist/ai/tacticalQueries.js +45 -0
  12. package/dist/ai/threat.d.ts +8 -0
  13. package/dist/ai/threat.js +10 -0
  14. package/dist/anim/animGraph.d.ts +155 -0
  15. package/dist/anim/animGraph.js +414 -0
  16. package/dist/anim/boneTexture.d.ts +55 -0
  17. package/dist/anim/boneTexture.js +43 -0
  18. package/dist/anim/footPlacement.d.ts +57 -0
  19. package/dist/anim/footPlacement.js +67 -0
  20. package/dist/anim/ikSolver.d.ts +51 -0
  21. package/dist/anim/ikSolver.js +124 -0
  22. package/dist/anim/locomotionGraph.d.ts +35 -0
  23. package/dist/anim/locomotionGraph.js +62 -0
  24. package/dist/assets/spriteAtlas.d.ts +21 -0
  25. package/dist/assets/spriteAtlas.js +103 -0
  26. package/dist/audio/audioFalloff.d.ts +12 -0
  27. package/dist/audio/doppler.d.ts +21 -0
  28. package/dist/audio/doppler.js +32 -0
  29. package/dist/audio/engineLayers.d.ts +83 -0
  30. package/dist/audio/engineLayers.js +115 -0
  31. package/dist/combat/abilityKit.d.ts +1 -0
  32. package/dist/combat/abilityKit.js +1 -0
  33. package/dist/combat/death.d.ts +7 -0
  34. package/dist/combat/shotOrigin.d.ts +6 -0
  35. package/dist/combat/shotOrigin.js +6 -0
  36. package/dist/combat/weaponFeedback.d.ts +36 -0
  37. package/dist/combat/weaponFeedback.js +42 -0
  38. package/dist/combat/weaponHandling.d.ts +161 -0
  39. package/dist/combat/weaponHandling.js +202 -0
  40. package/dist/combat/weaponPresentation.d.ts +115 -0
  41. package/dist/combat/weaponPresentation.js +96 -0
  42. package/dist/devtools/warnOnce.d.ts +8 -0
  43. package/dist/devtools/warnOnce.js +18 -0
  44. package/dist/economy/currency.d.ts +16 -2
  45. package/dist/economy/currency.js +55 -14
  46. package/dist/economy/wallet.d.ts +5 -4
  47. package/dist/economy/wallet.js +8 -4
  48. package/dist/editor/commandApply.js +1 -0
  49. package/dist/editor/commands.d.ts +4 -1
  50. package/dist/editor/document.d.ts +2 -1
  51. package/dist/editor/document.js +92 -1
  52. package/dist/editor/environment.d.ts +3 -0
  53. package/dist/editor/environment.js +24 -0
  54. package/dist/editor/index.d.ts +4 -2
  55. package/dist/editor/index.js +1 -1
  56. package/dist/editor/types.d.ts +20 -0
  57. package/dist/faction/reputation.d.ts +6 -0
  58. package/dist/faction/reputation.js +11 -0
  59. package/dist/game/cameraConfig.d.ts +59 -1
  60. package/dist/game/chat.d.ts +22 -1
  61. package/dist/game/chat.js +48 -19
  62. package/dist/game/controlGate.d.ts +6 -0
  63. package/dist/game/controlGate.js +30 -1
  64. package/dist/game/defineGame.d.ts +10 -1
  65. package/dist/game/events.d.ts +14 -1
  66. package/dist/game/lootTable.d.ts +4 -0
  67. package/dist/game/lootTable.js +16 -0
  68. package/dist/game/pixelPerfect.d.ts +25 -0
  69. package/dist/game/pixelPerfect.js +29 -0
  70. package/dist/game/playableGame.d.ts +167 -6
  71. package/dist/game/systemRuntime.js +6 -1
  72. package/dist/game/toasts.d.ts +8 -0
  73. package/dist/game/toasts.js +7 -0
  74. package/dist/game/vfxInstance.d.ts +9 -0
  75. package/dist/game/vfxInstance.js +12 -1
  76. package/dist/game/viewports.d.ts +43 -0
  77. package/dist/game/viewports.js +46 -0
  78. package/dist/game/worldItem.d.ts +5 -1
  79. package/dist/game/worldItem.js +0 -1
  80. package/dist/input/actionBindings.d.ts +17 -2
  81. package/dist/input/actionBindings.js +24 -11
  82. package/dist/input/actionContexts.d.ts +45 -0
  83. package/dist/input/actionContexts.js +113 -0
  84. package/dist/input/axisInput.d.ts +4 -0
  85. package/dist/input/axisInput.js +4 -0
  86. package/dist/input/axisShaper.d.ts +73 -0
  87. package/dist/input/axisShaper.js +105 -0
  88. package/dist/input/gamepadModel.d.ts +79 -0
  89. package/dist/input/gamepadModel.js +108 -0
  90. package/dist/input/haptics.d.ts +50 -0
  91. package/dist/input/haptics.js +74 -0
  92. package/dist/input/inputBuffer.d.ts +40 -0
  93. package/dist/input/inputBuffer.js +59 -0
  94. package/dist/input/lookChannel.d.ts +10 -0
  95. package/dist/input/lookChannel.js +6 -0
  96. package/dist/input/pointer.d.ts +2 -0
  97. package/dist/input/pointer.js +4 -0
  98. package/dist/item/affix.d.ts +6 -0
  99. package/dist/item/affix.js +6 -0
  100. package/dist/meta/changelog.d.ts +1 -1
  101. package/dist/meta/changelog.js +196 -1
  102. package/dist/movement/characterController.d.ts +76 -0
  103. package/dist/movement/characterController.js +216 -0
  104. package/dist/movement/freeFlight.d.ts +119 -0
  105. package/dist/movement/freeFlight.js +294 -0
  106. package/dist/movement/glideModel.d.ts +3 -0
  107. package/dist/movement/glideModel.js +6 -0
  108. package/dist/movement/grappleSwing.d.ts +2 -0
  109. package/dist/movement/grappleSwing.js +5 -1
  110. package/dist/movement/movementModel.d.ts +39 -0
  111. package/dist/movement/movementModel.js +62 -19
  112. package/dist/movement/movementProbe.d.ts +51 -0
  113. package/dist/movement/movementProbe.js +119 -0
  114. package/dist/movement/playerMovement.d.ts +30 -3
  115. package/dist/movement/playerMovement.js +304 -7
  116. package/dist/movement/serverStep.d.ts +5 -0
  117. package/dist/movement/serverStep.js +6 -0
  118. package/dist/movement/solidObstacles.d.ts +39 -8
  119. package/dist/movement/solidObstacles.js +71 -6
  120. package/dist/movement/voxelController.js +7 -7
  121. package/dist/multiplayer/poseSyncGate.d.ts +3 -1
  122. package/dist/multiplayer/poseSyncGate.js +9 -1
  123. package/dist/multiplayer/presenceContract.d.ts +6 -0
  124. package/dist/multiplayer/presenceModel.d.ts +2 -0
  125. package/dist/multiplayer/presenceModel.js +11 -0
  126. package/dist/nav/navFromEnvironment.d.ts +9 -3
  127. package/dist/nav/navFromEnvironment.js +18 -12
  128. package/dist/nav/navMesh.d.ts +74 -0
  129. package/dist/nav/navMesh.js +570 -0
  130. package/dist/physics/aircraftDynamics.d.ts +390 -0
  131. package/dist/physics/aircraftDynamics.js +704 -0
  132. package/dist/physics/boatDynamics.d.ts +116 -0
  133. package/dist/physics/boatDynamics.js +160 -0
  134. package/dist/physics/drivableVehicle.d.ts +22 -8
  135. package/dist/physics/drivableVehicle.js +4 -2
  136. package/dist/physics/flightDynamics.d.ts +29 -4
  137. package/dist/physics/flightDynamics.js +28 -5
  138. package/dist/physics/handlingProbe.d.ts +249 -0
  139. package/dist/physics/handlingProbe.js +606 -0
  140. package/dist/physics/kinematicVehicle.d.ts +37 -2
  141. package/dist/physics/kinematicVehicle.js +15 -0
  142. package/dist/physics/physicsBackend.d.ts +203 -0
  143. package/dist/physics/physicsBackend.js +49 -0
  144. package/dist/physics/physicsBackendConformance.d.ts +27 -0
  145. package/dist/physics/physicsBackendConformance.js +151 -0
  146. package/dist/physics/physicsWorldBackend.d.ts +45 -0
  147. package/dist/physics/physicsWorldBackend.js +518 -0
  148. package/dist/physics/vehicleBackendLink.d.ts +49 -0
  149. package/dist/physics/vehicleBackendLink.js +102 -0
  150. package/dist/physics/vehicleDynamics.d.ts +374 -0
  151. package/dist/physics/vehicleDynamics.js +737 -0
  152. package/dist/physics/worldColliders.d.ts +13 -0
  153. package/dist/physics/worldColliders.js +128 -0
  154. package/dist/puzzle/cellGrid.d.ts +15 -9
  155. package/dist/puzzle/cellGrid.js +13 -9
  156. package/dist/random/rng.d.ts +12 -1
  157. package/dist/random/rng.js +20 -3
  158. package/dist/render/environment.d.ts +23 -0
  159. package/dist/render/environment.js +1 -0
  160. package/dist/render/lookPreset.d.ts +19 -6
  161. package/dist/render/lookPreset.js +56 -4
  162. package/dist/render/postProcessing.d.ts +40 -1
  163. package/dist/render/postProcessing.js +1 -1
  164. package/dist/render/sprite2d.d.ts +38 -0
  165. package/dist/render/sprite2d.js +40 -0
  166. package/dist/runtime/adapter.d.ts +20 -1
  167. package/dist/runtime/adapter.js +8 -1
  168. package/dist/runtime/cameraDirector.d.ts +17 -3
  169. package/dist/runtime/cameraDirector.js +25 -0
  170. package/dist/runtime/commandInput.d.ts +52 -0
  171. package/dist/runtime/commandInput.js +71 -0
  172. package/dist/runtime/commandRunner.d.ts +39 -7
  173. package/dist/runtime/commandRunner.js +35 -5
  174. package/dist/runtime/context/combat.d.ts +1 -0
  175. package/dist/runtime/context/combat.js +2 -1
  176. package/dist/runtime/context/deathLoot.d.ts +2 -0
  177. package/dist/runtime/context/deathLoot.js +1 -0
  178. package/dist/runtime/gameContext.js +18 -1
  179. package/dist/runtime/gameContextTypes.d.ts +33 -7
  180. package/dist/runtime/gameRuntime.d.ts +23 -5
  181. package/dist/runtime/gameRuntime.js +21 -4
  182. package/dist/runtime/headlessRunner.d.ts +10 -0
  183. package/dist/runtime/headlessRunner.js +26 -8
  184. package/dist/runtime/hostPolicy.d.ts +3 -2
  185. package/dist/runtime/hostPolicy.js +8 -10
  186. package/dist/runtime/hostedGameRunner.js +34 -6
  187. package/dist/runtime/hostedWorldSession.d.ts +12 -16
  188. package/dist/runtime/hostedWorldSession.js +29 -6
  189. package/dist/runtime/hostedWorldStore.d.ts +20 -0
  190. package/dist/runtime/hostedWorldStore.js +1 -0
  191. package/dist/runtime/inputRecorder.d.ts +30 -0
  192. package/dist/runtime/inputRecorder.js +59 -0
  193. package/dist/runtime/inputSnapshot.d.ts +14 -0
  194. package/dist/runtime/inputSnapshot.js +11 -0
  195. package/dist/runtime/localPlayers.d.ts +73 -0
  196. package/dist/runtime/localPlayers.js +119 -0
  197. package/dist/runtime/poseInterpolation.d.ts +36 -0
  198. package/dist/runtime/poseInterpolation.js +91 -0
  199. package/dist/runtime/prediction.d.ts +45 -0
  200. package/dist/runtime/prediction.js +62 -0
  201. package/dist/runtime/simContext.d.ts +45 -0
  202. package/dist/runtime/simContext.js +65 -0
  203. package/dist/runtime/simLoop.d.ts +52 -0
  204. package/dist/runtime/simLoop.js +72 -0
  205. package/dist/runtime/simSnapshot.d.ts +32 -0
  206. package/dist/runtime/simSnapshot.js +60 -0
  207. package/dist/runtime/snapshot.d.ts +8 -0
  208. package/dist/runtime/snapshotBuffer.d.ts +33 -0
  209. package/dist/runtime/snapshotBuffer.js +58 -0
  210. package/dist/runtime/transport.d.ts +41 -2
  211. package/dist/runtime/transport.js +7 -1
  212. package/dist/runtime/worldChunks.d.ts +2 -0
  213. package/dist/runtime/worldChunks.js +14 -0
  214. package/dist/runtime/worldProjection.d.ts +9 -0
  215. package/dist/runtime/worldProjection.js +17 -1
  216. package/dist/runtime/worldSnapshot.d.ts +6 -0
  217. package/dist/runtime/worldSnapshot.js +3 -0
  218. package/dist/scene/behaviorRuntime.d.ts +29 -1
  219. package/dist/scene/behaviorRuntime.js +71 -4
  220. package/dist/scene/behaviors.d.ts +12 -1
  221. package/dist/scene/builtinSceneKinds.js +10 -0
  222. package/dist/scene/sceneKinds.d.ts +9 -0
  223. package/dist/scene/vehicleSeat.d.ts +5 -1
  224. package/dist/scene/vehicleSeat.js +5 -1
  225. package/dist/sensor/concealment.d.ts +6 -0
  226. package/dist/sensor/concealment.js +8 -0
  227. package/dist/sensor/freezeMonitor.d.ts +6 -0
  228. package/dist/sensor/freezeMonitor.js +8 -0
  229. package/dist/sensor/perception.d.ts +70 -0
  230. package/dist/sensor/perception.js +189 -0
  231. package/dist/sensor/recordingBuffer.d.ts +3 -0
  232. package/dist/sensor/recordingBuffer.js +8 -0
  233. package/dist/session/roundState.d.ts +4 -0
  234. package/dist/session/roundState.js +11 -0
  235. package/dist/settings/frameRateLimit.d.ts +16 -0
  236. package/dist/settings/frameRateLimit.js +37 -0
  237. package/dist/settings/graphicsProfile.d.ts +21 -0
  238. package/dist/settings/graphicsProfile.js +48 -0
  239. package/dist/settings/graphicsSettings.d.ts +40 -0
  240. package/dist/settings/graphicsSettings.js +81 -0
  241. package/dist/settings/settingsModel.d.ts +9 -1
  242. package/dist/settings/settingsModel.js +10 -0
  243. package/dist/stats/eventMeter.d.ts +1 -1
  244. package/dist/stats/eventMeter.js +1 -1
  245. package/dist/time/accrueSince.d.ts +10 -0
  246. package/dist/time/accrueSince.js +9 -0
  247. package/dist/time/rateWindow.d.ts +16 -0
  248. package/dist/time/rateWindow.js +14 -0
  249. package/dist/time/serverTick.d.ts +21 -3
  250. package/dist/time/serverTick.js +14 -13
  251. package/dist/vfx/feedbackMixer.d.ts +61 -0
  252. package/dist/vfx/feedbackMixer.js +106 -0
  253. package/dist/vfx/particleDirector.d.ts +68 -0
  254. package/dist/vfx/particleDirector.js +81 -0
  255. package/dist/visibility/simulationCulling.d.ts +8 -1
  256. package/dist/visibility/simulationCulling.js +7 -0
  257. package/dist/visibility/spatialIndex.d.ts +21 -0
  258. package/dist/visibility/spatialIndex.js +16 -0
  259. package/dist/world/authoredSolids.d.ts +46 -0
  260. package/dist/world/authoredSolids.js +75 -0
  261. package/dist/world/buildingGenerator.js +2 -2
  262. package/dist/world/buildingKit.d.ts +3 -1
  263. package/dist/world/buildings.d.ts +46 -2
  264. package/dist/world/buildings.js +53 -3
  265. package/dist/world/cityContent.js +103 -25
  266. package/dist/world/cityKind.d.ts +139 -0
  267. package/dist/world/cityKind.js +367 -16
  268. package/dist/world/features.d.ts +56 -3
  269. package/dist/world/features.js +13 -2
  270. package/dist/world/footprintGrid.d.ts +7 -0
  271. package/dist/world/footprintGrid.js +16 -0
  272. package/dist/world/interiors.d.ts +1 -0
  273. package/dist/world/interiors.js +5 -3
  274. package/dist/world/lod.d.ts +6 -0
  275. package/dist/world/lod.js +8 -0
  276. package/dist/world/placement.d.ts +7 -0
  277. package/dist/world/placement.js +2 -0
  278. package/dist/world/placementController.d.ts +1 -1
  279. package/dist/world/placementController.js +7 -0
  280. package/dist/world/raceCircuit.d.ts +168 -0
  281. package/dist/world/raceCircuit.js +797 -0
  282. package/dist/world/terraform.d.ts +8 -0
  283. package/dist/world/terraform.js +7 -0
  284. package/dist/world/terrain.d.ts +5 -1
  285. package/dist/world/terrain.js +5 -0
  286. package/dist/world/territory.d.ts +97 -0
  287. package/dist/world/territory.js +182 -0
  288. package/dist/world/walls.d.ts +7 -0
  289. package/dist/world/walls.js +7 -0
  290. package/dist/world/worldSolids.d.ts +84 -0
  291. package/dist/world/worldSolids.js +240 -0
  292. package/dist/world.d.ts +5 -3
  293. package/dist/world.js +4 -2
  294. package/package.json +1 -1
  295. package/skills/ce-handoff/SKILL.md +2 -2
  296. package/skills/game-design/SKILL.md +11 -1
  297. package/skills/jgengine/SKILL.md +9 -5
  298. package/skills/jgengine/api.md +205 -101
  299. package/skills/jgengine/capabilities.md +56 -0
  300. package/skills/jgengine/recipes/minimal-game.md +3 -0
  301. package/skills/jgengine-combat/SKILL.md +5 -0
  302. package/skills/jgengine-combat/api.md +138 -103
  303. package/skills/jgengine-combat/capabilities.md +29 -1
  304. package/skills/jgengine-combat/recipes/weapon-feel.md +50 -0
  305. package/skills/jgengine-gameplay/SKILL.md +10 -0
  306. package/skills/jgengine-gameplay/api.md +501 -453
  307. package/skills/jgengine-gameplay/capabilities.md +36 -0
  308. package/skills/jgengine-gameplay/reference-systems.md +4 -0
  309. package/skills/jgengine-world/SKILL.md +21 -3
  310. package/skills/jgengine-world/api.md +1172 -625
  311. package/skills/jgengine-world/capabilities.md +229 -3
  312. package/skills/jgengine-world/recipes/character-animation.md +39 -0
  313. package/skills/jgengine-world/recipes/controls.md +44 -0
  314. package/skills/jgengine-world/recipes/flight-feel.md +76 -0
  315. package/skills/jgengine-world/recipes/npc-senses.md +43 -0
  316. package/skills/jgengine-world/recipes/vehicle-feel.md +85 -0
  317. package/skills/jgengine-world/reference.md +31 -2
  318. package/skills/jgengine-world/visibility.md +112 -0
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Computes an orthographic frustum whose world units map to the requested pixel density.
3
+ * Integer scaling chooses the largest whole-number density multiplier available in the viewport.
4
+ */
5
+ export function pixelPerfectFrustum(viewport, pixelsPerUnit, integerScale = false) {
6
+ const width = Math.max(1, viewport.width);
7
+ const height = Math.max(1, viewport.height);
8
+ const density = Math.max(1e-6, pixelsPerUnit);
9
+ const scale = integerScale ? Math.max(1, Math.floor(Math.min(width, height) / density)) : 1;
10
+ const zoom = density * scale;
11
+ return {
12
+ left: -width / (2 * zoom),
13
+ right: width / (2 * zoom),
14
+ top: height / (2 * zoom),
15
+ bottom: -height / (2 * zoom),
16
+ zoom,
17
+ scale,
18
+ };
19
+ }
20
+ /** Snaps a camera position to the authored pixel grid without changing its orientation. */
21
+ export function snapPixelPerfectPosition(position, pixelsPerUnit) {
22
+ const density = Math.max(1e-6, pixelsPerUnit);
23
+ return {
24
+ ...position,
25
+ x: Math.round(position.x * density) / density,
26
+ y: Math.round(position.y * density) / density,
27
+ z: Math.round(position.z * density) / density,
28
+ };
29
+ }
@@ -1,14 +1,22 @@
1
+ import type { AnimGraph } from "../anim/animGraph.js";
1
2
  import type { AudioBusDef, SoundDef } from "../audio/audioFalloff.js";
2
3
  import type { MusicTheme } from "../audio/music.js";
3
4
  import type { EditorCatalogDefinition, EditorDocument } from "../editor/types.js";
4
5
  import type { PostProcessingConfig } from "../render/postProcessing.js";
6
+ import type { EnvironmentSource } from "../render/environment.js";
7
+ import type { SpriteAtlas } from "../assets/spriteAtlas.js";
5
8
  import type { LookPreset } from "../render/lookPreset.js";
9
+ import type { GamepadFeelConfig } from "../input/gamepadModel.js";
10
+ import type { LocalPlayersConfig } from "../runtime/localPlayers.js";
6
11
  import type { TouchControlsConfig } from "../input/touchScheme.js";
7
12
  import type { GameSettingsConfig } from "../settings/settingsModel.js";
13
+ import type { GraphicsProfile } from "../settings/graphicsProfile.js";
8
14
  import type { GameOrientation } from "../ui/orientation.js";
9
15
  import type { HudPlatform, HudViewportConfig } from "../ui/hudScale.js";
10
16
  import type { PositionedPrompt } from "../interaction/proximityPrompt.js";
17
+ import type { ChaseCameraTuning } from "../runtime/cameraDirector.js";
11
18
  import type { CatalogEntityRole, GameContext, GameContextContent } from "../runtime/gameContext.js";
19
+ import type { FreeFlightBindings } from "../movement/freeFlight.js";
12
20
  import type { ModelDims } from "../scene/assetCatalog.js";
13
21
  import type { PartMotionParams, PartRole } from "./partAnimation.js";
14
22
  import type { CollisionMeshData } from "../scene/collisionMesh.js";
@@ -16,14 +24,21 @@ import type { SkyEnvironmentConfig } from "../world/features.js";
16
24
  import type { VisibilityConfig } from "../visibility/config.js";
17
25
  import type { GameDefinition, GameLoop } from "./defineGame.js";
18
26
  import type { GameCameraConfig } from "./cameraConfig.js";
27
+ import type { ViewportsConfig } from "./viewports.js";
19
28
  import type { LootFilterRule } from "./lootFilter.js";
20
29
  import type { RarityStyle } from "./worldItem.js";
21
- export type { CameraFollowState, CameraKeyframe, CameraProjection, CameraRigKind, CameraShakeConfig, ChaseCameraConfig, ChaseView, CinematicCameraConfig, FirstPersonCameraConfig, GameCameraConfig, InspectionCameraConfig, InspectionZoomAnchor, LockOnCameraConfig, ObserverCameraConfig, PlayerFovConfig, RtsCameraConfig, ShoulderCameraConfig, SideScrollCameraConfig, TopDownCameraConfig, TurntableCameraConfig, } from "./cameraConfig.js";
30
+ export type { CameraFollowState, CameraKeyframe, CameraProjection, CameraRigKind, CameraWeaponView, CameraShakeConfig, ChaseCameraConfig, ChaseView, CinematicCameraConfig, FirstPersonCameraConfig, GameCameraConfig, InspectionCameraConfig, InspectionZoomAnchor, LockOnCameraConfig, ObserverCameraConfig, PlayerFovConfig, RtsCameraConfig, ShoulderCameraConfig, SideScrollCameraConfig, TopDownCameraConfig, TurntableCameraConfig, } from "./cameraConfig.js";
22
31
  export { CAMERA_FRUSTUM_DEFAULTS } from "./cameraConfig.js";
32
+ /**
33
+ * Mouse/touch pointer bindings for `defineGame({ pointer })`.
34
+ *
35
+ * @capability pointer-commands click an entity or the ground to run a command (click-to-target, click-to-move, grab loot, marquee select)
36
+ */
23
37
  export interface PointerConfig {
24
38
  /**
25
- * Left-click on open ground runs this command with `{ point, entity, object }`
26
- * (click-to-move / ground-target). Suppresses the default left-click hotbar fire.
39
+ * Left-click runs this command with `{ point, entity, object }`; `entity` is the clicked entity id or
40
+ * null, so the same command covers click-to-move, ground-target and click-to-target
41
+ * (`ctx.scene.entity.setTarget`). Suppresses the default left-click hotbar fire.
27
42
  */
28
43
  moveCommand?: string;
29
44
  /** Enable left-drag marquee + single-click box-select of entities (RTS unit command). */
@@ -52,6 +67,11 @@ export interface EntitySpriteConfig {
52
67
  width: number;
53
68
  height: number;
54
69
  y: number;
70
+ /** Optional atlas-backed animation. The atlas image supersedes `url` when present. */
71
+ clip?: {
72
+ atlas: SpriteAtlas;
73
+ animation: string;
74
+ };
55
75
  }
56
76
  export interface WorldItemRenderConfig {
57
77
  /** Baseline rarity→color/beam/label render binding (#32); the game's rarity palette. */
@@ -119,6 +139,14 @@ export interface ModelAnimationConfig {
119
139
  * `string[]` picks a random variant per trigger. `death` clamps on its final frame instead of returning.
120
140
  */
121
141
  oneShots?: Record<string, string | readonly string[]>;
142
+ /**
143
+ * A full animation graph (`@jgengine/core/anim/animGraph`) — layered state machine with blend trees, data
144
+ * transitions, triggers, and clip events. Takes over from `states`/`oneShots` when set: the shell feeds the
145
+ * entity's smoothed ground speed as the `speed` parameter plus anything under the entity blackboard key
146
+ * `ANIM_PARAMS_KEY`, arms triggers from `entity.animation` (`hit` and `death` from their combat events), and
147
+ * emits `animation.event` for clip events. `locomotionGraph()` builds the default idle/walk/run graph.
148
+ */
149
+ graph?: AnimGraph;
122
150
  }
123
151
  /**
124
152
  * Real PBR map URLs (e.g. `buildMaterialCatalog(...).resolve(id)!.maps` from `@jgengine/assets`)
@@ -130,8 +158,11 @@ export interface ModelMaterialMaps {
130
158
  normal?: string;
131
159
  roughness?: string;
132
160
  ao?: string;
161
+ metalness?: string;
162
+ emissive?: string;
163
+ height?: string;
133
164
  }
134
- /** Per-entity PBR material override (#151.3) applied to every `MeshStandardMaterial` in the model's cloned scene graph. */
165
+ /** Per-entity PBR material override (#151.3) applied to every standard or physical material in the model's cloned scene graph. */
135
166
  export interface ModelMaterialOverride {
136
167
  color?: string;
137
168
  metalness?: number;
@@ -203,6 +234,8 @@ export interface ModelConfig {
203
234
  collisionMesh?: CollisionMeshData;
204
235
  /** Per-entity PBR tint/finish override (#151.3); cloned onto each `MeshStandardMaterial` in the model so shared GLTF caches stay untouched. */
205
236
  material?: ModelMaterialOverride;
237
+ /** Shadow participation of every mesh in the model. Default `"both"` (casts and receives); `"none"` for foliage cards, decals, or viewmodels that must never shadow the world. */
238
+ shadows?: "cast" | "receive" | "both" | "none";
206
239
  /**
207
240
  * Plays a GLTF animation clip on the model when the source has any (skinned or not).
208
241
  * A model resolved from a catalog id whose index carries `clips` animates automatically —
@@ -212,6 +245,14 @@ export interface ModelConfig {
212
245
  * rig's bind pose.
213
246
  */
214
247
  animation?: ModelAnimationConfig | "auto" | "none";
248
+ /**
249
+ * Runtime inverse kinematics for a rigged model, applied after the animation mixer each frame.
250
+ * Feet follow slopes and steps while keeping each clip's swing lift, soles never sink below the
251
+ * ground, the pelvis drops so the lower leg can reach, and the correction fades out while airborne.
252
+ * `"auto"` finds the legs by bone name (`UpLeg`, `UpperLeg`, `Thigh`); bone names resolve on the
253
+ * loaded scene.
254
+ */
255
+ ik?: "auto" | ModelIkConfig;
215
256
  /** Props/weapons parented to named bones on this model's rig; each follows its bone through animation. */
216
257
  attachments?: readonly ModelAttachment[];
217
258
  /** Static kit-of-parts pieces stacked at fixed local offsets — no bone/rig involved. Use this for a compound entity assembled from several modular meshes (a castle keep from base + mid + roof pieces); use `attachments` for props parented to an animated rig's bones. Tag parts with a `role` to procedurally animate a rig-less character composition. */
@@ -219,6 +260,25 @@ export interface ModelConfig {
219
260
  /** Tuning for the procedural part-motion driver when any part carries a `role`; omit for defaults. */
220
261
  partMotion?: PartMotionParams;
221
262
  }
263
+ /** Bone names and limits for {@link ModelConfig.ik}. */
264
+ export interface ModelIkConfig {
265
+ /** Thigh → shin → foot chains; omit to find them by bone name. `[]` turns foot IK off. */
266
+ feet?: readonly {
267
+ root: string;
268
+ mid: string;
269
+ tip: string;
270
+ }[];
271
+ /** Bone shifted down when a foot has to reach lower ground; defaults to the first thigh's parent. */
272
+ pelvis?: string;
273
+ /** Largest foot or pelvis correction, as a fraction of leg length. Default `0.4`. */
274
+ maxAdjust?: number;
275
+ /** Tilt planted feet to the ground normal, `0..1`. Default `1`. */
276
+ alignToGround?: number;
277
+ /** Turns this bone toward the camera. */
278
+ lookAt?: {
279
+ bone: string;
280
+ };
281
+ }
222
282
  export interface ObjectStyle {
223
283
  color?: string;
224
284
  opacity?: number;
@@ -255,10 +315,29 @@ export interface HemisphereLightingConfig {
255
315
  groundColor?: string;
256
316
  intensity?: number;
257
317
  }
318
+ /** A bounded dynamic point light in world space. */
319
+ export interface PointLightingConfig {
320
+ color?: string;
321
+ intensity?: number;
322
+ position: readonly [number, number, number];
323
+ distance?: number;
324
+ decay?: number;
325
+ castShadow?: boolean;
326
+ }
327
+ /** A bounded dynamic spot light in world space. */
328
+ export interface SpotLightingConfig extends PointLightingConfig {
329
+ target?: readonly [number, number, number];
330
+ angle?: number;
331
+ penumbra?: number;
332
+ }
258
333
  /** Declarative lighting replacing the shell's hardcoded ambient/directional default (#207.5); mounts regardless of world kind, only when supplied. */
259
334
  export interface LightingConfig {
260
335
  ambient?: AmbientLightingConfig;
261
336
  directional?: readonly DirectionalLightingConfig[];
337
+ point?: readonly PointLightingConfig[];
338
+ spot?: readonly SpotLightingConfig[];
339
+ /** Maximum dynamic point + spot lights mounted by the shell. Default 8. */
340
+ maxDynamicLights?: number;
262
341
  hemisphere?: HemisphereLightingConfig;
263
342
  }
264
343
  export interface BackdropFogConfig {
@@ -274,6 +353,62 @@ export interface BackdropConfig {
274
353
  sky?: SkyEnvironmentConfig;
275
354
  fog?: BackdropFogConfig;
276
355
  }
356
+ /** Free-flight families moved into the walk controller so one seam covers ground + air — creative/spectator/noclip/hover are weightless, aircraft, rotorcraft and rockets use `physics/aircraftDynamics`. */
357
+ export interface FlightConfig {
358
+ /** Which free-flight family to use when flight is active. Default "creative". */
359
+ mode?: "creative" | "spectator" | "noclip" | "hover";
360
+ /** Horizontal base speed (units/s). Default 8. */
361
+ speed?: number;
362
+ /** Vertical base speed for ascend/descend. Default = `speed`. */
363
+ verticalSpeed?: number;
364
+ /** Sprint multiplier while shift is held. Default 1.8. */
365
+ sprintMultiplier?: number;
366
+ /** Lerp rate toward target velocity. Default 20. */
367
+ acceleration?: number;
368
+ /** Downward acceleration for hover mode. Default 0 (weightless). */
369
+ gravity?: number;
370
+ /** Upward thrust while ascending in hover. Default 32. */
371
+ thrust?: number;
372
+ /** When true, W moves along the look pitch (6DOF spectator). Default false. */
373
+ alignWithLook?: boolean;
374
+ /** Whether flight slides against world solids. Default true for creative/hover, false for spectator/noclip. */
375
+ collide?: boolean;
376
+ /** Axis bindings for this flight — which actions drive each axis. Unlisted axes keep the defaults above. */
377
+ bindings?: FreeFlightBindings;
378
+ /** Camera POV while this flight is active — chase tuning overlay applied on mount, cleared on dismount. */
379
+ camera?: ChaseCameraTuning | null;
380
+ /** Gate flight behind live game state — creative toggle, stamina check, etc. `false` stays walking. Default true when `flight` is present. */
381
+ canFly?: (ctx: GameContext) => boolean;
382
+ }
383
+ /** Feel knobs for the built-in walk controller; see {@link PlayerMovementConfig.feel}. */
384
+ export interface MovementFeelConfig {
385
+ /** Ground velocity response, 1/s (default 26). Lower drifts into and out of motion; higher is snappy. */
386
+ groundAcceleration?: number;
387
+ /** Air velocity response, 1/s (default 12). `0` commits to the jump arc; high values steer freely in the air. */
388
+ airAcceleration?: number;
389
+ /** Stopping response on the ground with no input, 1/s (default 18). Lower slides like ice. */
390
+ groundFriction?: number;
391
+ /** Sprint speed as a multiple of walk speed (default 2.25). */
392
+ runMultiplier?: number;
393
+ /** Crouch speed as a multiple of walk speed (default 0.45). */
394
+ crouchMultiplier?: number;
395
+ /** A jump pressed up to this many ms before landing fires on landing (default 0). */
396
+ jumpBufferMs?: number;
397
+ /** A jump pressed up to this many ms after walking off a ledge still fires (default 0). */
398
+ coyoteMs?: number;
399
+ /** Rising speed kept when jump is released early, 0..1 (default 1: no cut). Lower makes a tap a hop. */
400
+ jumpCutFactor?: number;
401
+ /** Gravity multiplier near the peak, while vertical speed is under `apexSpeed` (default 1). Below 1 hangs. */
402
+ apexGravityScale?: number;
403
+ /** Vertical speed under which `apexGravityScale` applies, m/s (default 1.5). */
404
+ apexSpeed?: number;
405
+ /** Gravity multiplier while falling (default 1). Above 1 drops faster than it rose. */
406
+ fallGravityScale?: number;
407
+ /** After landing, jumps wait and walk speed ramps back from `landingSpeedScale` over this many ms (default 0). */
408
+ landingRecoveryMs?: number;
409
+ /** Walk speed share right after landing when `landingRecoveryMs` is set (default 0.5). */
410
+ landingSpeedScale?: number;
411
+ }
277
412
  /** Movement-control levers for the shell-driven local player walk controller. */
278
413
  export interface PlayerMovementConfig {
279
414
  /** "free" (default) moves camera-relative across the plane; "axis" locks travel to one world axis; "grid" snaps each committed position to cell centers. */
@@ -298,6 +433,13 @@ export interface PlayerMovementConfig {
298
433
  canSprint?: (ctx: GameContext) => boolean;
299
434
  /** Fraction of walk speed while backpedalling (holding `moveBack`). Overrides the engine default (0.65). */
300
435
  backpedalMult?: number;
436
+ /**
437
+ * Walk-controller feel. Acceleration and friction are exponential response rates (1/s): higher reaches
438
+ * the target velocity or stops sooner. Omitted fields keep the engine defaults, so nothing changes until a
439
+ * game sets one. The jump fields apply to the kinematic walk; a `physics.backend` capsule or `collision.voxel`
440
+ * body jumps on the press frame with plain gravity. Tune against `measureMovement` (`@jgengine/core/movement/movementProbe`).
441
+ */
442
+ feel?: MovementFeelConfig;
301
443
  /** Radians/second the rendered body rotates toward its movement heading (shortest arc), so strafing/backpedalling read as a turn rather than an instant flip; also the rate the internally-integrated `turnLeft`/`turnRight` heading turns when the shell doesn't own yaw. Unset = body facing snaps instantly (no change to existing feel). */
302
444
  turnSpeed?: number;
303
445
  /** On-foot swimming when the terrain declares a `waterLevel` and the player is submerged: caps speed and floats them at the surface. `true` uses defaults; default off. */
@@ -308,6 +450,15 @@ export interface PlayerMovementConfig {
308
450
  slopeSlide?: {
309
451
  maxClimbSlope?: number;
310
452
  } | boolean;
453
+ /**
454
+ * Free-flight — creative/spectator/noclip/hover — folded into the same walk controller so a game
455
+ * doesn't hand-roll a second movement loop to get Minecraft-like flight. `true` uses creative defaults;
456
+ * an object tunes speed/vertical/sprint/collide/canFly. Horizontal is always yaw-relative strafe
457
+ * (A = left, D = right — never roll/bank), vertical is Space/Ctrl, and aircraft, rotorcraft and rockets
458
+ * use `physics/aircraftDynamics`. The shell drives this automatically when present; `canFly` toggles
459
+ * between walking and flying per tick without rebuilding the config.
460
+ */
461
+ flight?: FlightConfig | boolean;
311
462
  }
312
463
  /** One frame's movement resolution handed to `PlayerMovementConfig.beforeCommit`. */
313
464
  export interface MovementCommitFrame {
@@ -412,7 +563,7 @@ export interface PlayableGame<TUi = unknown, TWorldOverlay = unknown, TRenderEnt
412
563
  /** Optional canvas-layer VFX component (e.g. traveling projectiles); receives `{ ctx }` (#542) so overlay VFX read live engine state without a separate hook or a module-global workaround. */
413
564
  WorldOverlay?: TOverlay;
414
565
  /** Replaces the default demo backdrop (ground + grid + rocks) with the game's own scene — ground, sky, structures. Camera, input, HUD, entity rendering, and the loop stay shell-provided; supply your world without forking the shell. When unset and `game.world` is an `environment()` descriptor, the shell auto-renders that world here — no manual wiring needed. */
415
- environment?: TWorldOverlay;
566
+ environment?: TWorldOverlay | EnvironmentSource;
416
567
  /** The game's authored scene document (`editor.scene.json`, normalized). When set, `defineGame` always mounts `AuthoredScene` over it (draped paths, scatter, studios, placed catalog props) — any `WorldOverlay` renders alongside it as VFX only — and the embedded editor opens it as its default layers: the zero-wiring path from document to play and edit modes. */
417
568
  editorLayers?: EditorDocument;
418
569
  /** Game-exported gameplay catalog definitions (schemas + defaults). `GameHost` forwards them to the summoned editor's Data panel; no per-game editor bootstrap needed. */
@@ -441,10 +592,18 @@ export interface PlayableGame<TUi = unknown, TWorldOverlay = unknown, TRenderEnt
441
592
  capture?: GameCaptureConfig;
442
593
  /** Cast/receive shadows across the scene (R3F Canvas shadow pass). Default true. */
443
594
  shadows?: boolean;
595
+ /** Per-quality renderer budgets; omitted tiers use {@link DEFAULT_GRAPHICS_PROFILES}. */
596
+ graphics?: Partial<Record<"low" | "medium" | "high", Partial<GraphicsProfile>>>;
444
597
  /** Pointer-driven input: click-to-move, box-select, right-click verbs, cursor aim (#22/#30/#31). */
445
598
  pointer?: PointerConfig;
446
599
  /** Touch controls on coarse-pointer devices. Unset derives a scheme from `input` (virtual joystick for movement actions, on-screen buttons for the rest); a config refines it with gestures and curated buttons; `false` opts out. */
447
600
  touch?: TouchControlsConfig | false;
601
+ /** Gamepad stick deadzone, response curve and trigger deadzone the shell's pad poll applies before publishing analog values. Unset keeps the shell defaults (axial `0.12`/`0.95`, linear). */
602
+ gamepad?: GamepadFeelConfig;
603
+ /** Local seats on this screen for couch co-op. With `maxSlots > 1` each extra pad hot-joins its own seat on its first button press, the shell calls `loop.onNewPlayer(ctx, { userId, isNew: true })` for it, and its input lands on `localPlayers(ctx).local(slotId).input` instead of `ctx.input`. Unset keeps one seat: every device drives `ctx.player`. */
604
+ localPlayers?: LocalPlayersConfig;
605
+ /** Split-screen for local seats (`localPlayers`): one viewport per joined seat, each following its own entity. The primary seat keeps the main camera rig; post-processing is skipped while more than one viewport is drawn. */
606
+ viewports?: ViewportsConfig;
448
607
  /** Phone orientation contract. Legacy `"landscape"`/`"portrait"` stays advisory (a dismissible rotate hint). The object form `{ mobile: "landscape-required" }` is strict — the shell shows an engine-owned rotate screen and blocks gameplay until the device is turned. See `GameOrientation`. */
449
608
  orientation?: GameOrientation;
450
609
  /** Where the game is meant to be played. Default `["web", "mobile"]` — design-resolution HUD fit is on for every game: `HudCanvas` auto-scales from `hudFit.designSize` down to the live viewport, so the desktop layout shrinks to fit a phone instead of overflowing it. Declare `["web"]` to opt a desktop-only game out (compact displays fall back to the legacy fixed 0.85 zoom). */
@@ -499,10 +658,12 @@ export interface PlayableGame<TUi = unknown, TWorldOverlay = unknown, TRenderEnt
499
658
  collision?: VoxelCollisionConfig;
500
659
  /** Movement-control levers (axis/grid constraints, object collision, pre-commit hook) for the shell-driven walk controller. */
501
660
  movement?: PlayerMovementConfig;
502
- /** Default-look preset (#773). Unset/`"cinematic"` composes a real sky, a shadow-casting sun+hemisphere rig, a network-free image-based-lighting environment (soft PBR reflections), and a tuned tone-map/bloom/AO/vignette post stack, so a scene reads lit-like-a-game out of the box; `"flat"` opts out to the bare ambient+directional default. Upgraded default primitive materials (tuned PBR + subtle procedural surface detail) apply under both presets. Any explicit `lighting`/`backdrop`/`postProcessing` always wins. */
661
+ /** Graphics look profile (#1680). Unset/`"neutral"` keeps the output unstyled; `"photoreal"` and `"toon"` select authored presentation profiles, while `"cinematic"` and `"flat"` remain compatibility aliases. Explicit `lighting`/`backdrop`/`postProcessing` always wins. */
503
662
  look?: LookPreset;
504
663
  /** Declarative ambient/directional/hemisphere lighting (#207.5); replaces the shell's hardcoded default lights when present, regardless of world kind. */
505
664
  lighting?: LightingConfig;
665
+ /** Image-based environment source; omit to use the procedural gradient probe. */
666
+ environmentSource?: EnvironmentSource;
506
667
  /** Generic background/sky/fog (#207.6), applied for any world kind including a custom `environment` component. */
507
668
  backdrop?: BackdropConfig;
508
669
  /** Declarative post-processing chain (AO/bloom/tone-map/grade). When set, the shell mounts an EffectComposer and owns the render; absent leaves the renderer drawing directly (unchanged). */
@@ -1,4 +1,5 @@
1
1
  import { compileSystemSchedule, } from "./systemSchedule.js";
2
+ const MAX_FIXED_SUBSTEPS = 8;
2
3
  function resolveModule(spec, ctx) {
3
4
  if (spec === undefined)
4
5
  return undefined;
@@ -56,10 +57,14 @@ export function installSystems(ctx, systems, options) {
56
57
  for (const group of schedule.fixed) {
57
58
  const step = 1 / group.rate;
58
59
  let acc = (fixedAcc.get(group.rate) ?? 0) + dt;
59
- while (acc >= step) {
60
+ let substeps = 0;
61
+ while (acc >= step && substeps < MAX_FIXED_SUBSTEPS) {
60
62
  runIds(ctx, group.order, step);
61
63
  acc -= step;
64
+ substeps += 1;
62
65
  }
66
+ if (acc >= step)
67
+ acc %= step;
63
68
  fixedAcc.set(group.rate, acc);
64
69
  }
65
70
  runIds(ctx, schedule.frameOrder, dt);
@@ -15,6 +15,11 @@ export interface Toast<T = string> {
15
15
  export declare function appendToast<T>(toasts: readonly Toast<T>[], toast: Toast<T>, cap: number): readonly Toast<T>[];
16
16
  /** Drop every toast whose `expiresAt` is at or before `now`. Returns the same array when nothing expired. */
17
17
  export declare function pruneToasts<T>(toasts: readonly Toast<T>[], now: number): readonly Toast<T>[];
18
+ /** Plain JSON state of a {@link ToastQueue}: live toasts plus the id counter. */
19
+ export interface ToastQueueState<T = string> {
20
+ toasts: Toast<T>[];
21
+ counter: number;
22
+ }
18
23
  /** Stateful transient-toast list with a size cap and time-to-live eviction. */
19
24
  export interface ToastQueue<T = string> {
20
25
  /** Raise a toast at `now` living `ttlSeconds` (falling back to the queue default); returns it. */
@@ -25,6 +30,9 @@ export interface ToastQueue<T = string> {
25
30
  list(): readonly Toast<T>[];
26
31
  /** Remove all toasts. */
27
32
  clear(): void;
33
+ /** Copies the queue; each toast's `body` is shared, since the queue never mutates it. */
34
+ snapshot(): ToastQueueState<T>;
35
+ restore(next: ToastQueueState<T>): void;
28
36
  }
29
37
  /** Options for {@link createToastQueue}. */
30
38
  export interface ToastQueueOptions {
@@ -40,5 +40,12 @@ export function createToastQueue(options = {}) {
40
40
  clear() {
41
41
  toasts = [];
42
42
  },
43
+ snapshot() {
44
+ return { toasts: toasts.map((toast) => ({ ...toast })), counter };
45
+ },
46
+ restore(next) {
47
+ toasts = next.toasts.map((toast) => ({ ...toast }));
48
+ counter = next.counter;
49
+ },
43
50
  };
44
51
  }
@@ -115,6 +115,15 @@ export interface VfxInstanceStore {
115
115
  tick(nowMs?: number): number;
116
116
  /** Dispose every instance (scene change / reset), emitting a `stop` op for each. */
117
117
  clear(): void;
118
+ /** Plain JSON copy of every live instance plus the id counter. */
119
+ snapshot(): VfxInstanceStoreState;
120
+ /** Replaces the live set: emits `stop` for each current instance, then `upsert` for each restored one. */
121
+ restore(next: VfxInstanceStoreState): void;
122
+ }
123
+ /** Plain JSON state of a {@link VfxInstanceStore}. */
124
+ export interface VfxInstanceStoreState {
125
+ instances: VfxInstanceState[];
126
+ seq: number;
118
127
  }
119
128
  /** Options for {@link createVfxInstanceStore}. */
120
129
  export interface VfxInstanceStoreOptions {
@@ -92,5 +92,16 @@ export function createVfxInstanceStore(options = {}) {
92
92
  for (const id of [...instances.keys()])
93
93
  stop(id);
94
94
  }
95
- return { upsert, update, stop, get, list, count, tick, clear };
95
+ function snapshot() {
96
+ return { instances: structuredClone([...instances.values()]), seq };
97
+ }
98
+ function restore(next) {
99
+ clear();
100
+ seq = next.seq;
101
+ for (const state of structuredClone(next.instances)) {
102
+ instances.set(state.id, state);
103
+ emit({ op: "upsert", id: state.id, instance: state });
104
+ }
105
+ }
106
+ return { upsert, update, stop, get, list, count, tick, clear, snapshot, restore };
96
107
  }
@@ -0,0 +1,43 @@
1
+ import type { ChaseCameraConfig, TopDownCameraConfig } from "./cameraConfig.js";
2
+ /** A screen region as fractions of the canvas, `[x, y, width, height]` in `0..1` with the origin at the top left. */
3
+ export type ViewportRect = readonly [number, number, number, number];
4
+ /** The follow camera an extra viewport runs; the primary seat keeps the game's main camera rig. */
5
+ export interface ViewportCameraConfig {
6
+ /** Default: `"topDown"` when the game's main rig is top-down, otherwise `"chase"`. */
7
+ rig?: "chase" | "topDown";
8
+ chase?: ChaseCameraConfig;
9
+ topDown?: TopDownCameraConfig;
10
+ }
11
+ /** One viewport: which local seat it shows and where. */
12
+ export interface ViewportDef {
13
+ /** Local seat id (`"slot:0"`, `"slot:1"`, …) from `localPlayers(ctx)`. */
14
+ slot: string;
15
+ rect: ViewportRect;
16
+ camera?: ViewportCameraConfig;
17
+ }
18
+ /** `defineGame({ viewports })`: split-screen layout for local seats. */
19
+ export interface ViewportsConfig {
20
+ /** `"auto"` (default) splits the screen evenly across the seats currently joined; a list pins explicit rects. */
21
+ layout?: "auto" | readonly ViewportDef[];
22
+ /** Two-seat split: `"vertical"` puts them side by side (default), `"horizontal"` stacks them. */
23
+ split?: "vertical" | "horizontal";
24
+ /** Follow camera for seats whose viewport has none. */
25
+ camera?: ViewportCameraConfig;
26
+ }
27
+ /** A viewport in canvas pixels with a bottom-left origin, ready for `gl.setViewport`/`setScissor`. */
28
+ export interface ViewportPixels {
29
+ x: number;
30
+ y: number;
31
+ width: number;
32
+ height: number;
33
+ }
34
+ /**
35
+ * Even split-screen rects for seats in order: one fills the screen, two split side by side (or stacked),
36
+ * three put the first on a full-width top half, four make a 2×2 grid, more fill a grid row by row.
37
+ * @capability split-screen Split the screen into one viewport per local seat, each with its own follow camera.
38
+ */
39
+ export declare function splitViewports(slots: readonly string[], split?: "vertical" | "horizontal"): ViewportDef[];
40
+ /** The viewports to draw for the seats currently joined: the explicit layout filtered to joined seats, or an even split. */
41
+ export declare function resolveViewports(config: ViewportsConfig | undefined, slots: readonly string[]): ViewportDef[];
42
+ /** Convert a top-left fractional rect into whole canvas pixels with a bottom-left origin; neighbours share edges exactly. */
43
+ export declare function viewportPixels(rect: ViewportRect, width: number, height: number): ViewportPixels;
@@ -0,0 +1,46 @@
1
+ function gridRects(count) {
2
+ const columns = Math.ceil(Math.sqrt(count));
3
+ const rows = Math.ceil(count / columns);
4
+ const rects = [];
5
+ for (let index = 0; index < count; index += 1) {
6
+ const row = Math.floor(index / columns);
7
+ const inRow = row === rows - 1 ? count - row * columns : columns;
8
+ const column = index - row * columns;
9
+ rects.push([column / inRow, row / rows, 1 / inRow, 1 / rows]);
10
+ }
11
+ return rects;
12
+ }
13
+ /**
14
+ * Even split-screen rects for seats in order: one fills the screen, two split side by side (or stacked),
15
+ * three put the first on a full-width top half, four make a 2×2 grid, more fill a grid row by row.
16
+ * @capability split-screen Split the screen into one viewport per local seat, each with its own follow camera.
17
+ */
18
+ export function splitViewports(slots, split = "vertical") {
19
+ const count = slots.length;
20
+ let rects;
21
+ if (count <= 1)
22
+ rects = [[0, 0, 1, 1]];
23
+ else if (count === 2) {
24
+ rects = split === "vertical" ? [[0, 0, 0.5, 1], [0.5, 0, 0.5, 1]] : [[0, 0, 1, 0.5], [0, 0.5, 1, 0.5]];
25
+ }
26
+ else if (count === 3)
27
+ rects = [[0, 0, 1, 0.5], [0, 0.5, 0.5, 0.5], [0.5, 0.5, 0.5, 0.5]];
28
+ else
29
+ rects = gridRects(count);
30
+ return slots.map((slot, index) => ({ slot, rect: rects[index] }));
31
+ }
32
+ /** The viewports to draw for the seats currently joined: the explicit layout filtered to joined seats, or an even split. */
33
+ export function resolveViewports(config, slots) {
34
+ const layout = config?.layout ?? "auto";
35
+ if (layout === "auto")
36
+ return splitViewports(slots, config?.split);
37
+ return layout.filter((def) => slots.includes(def.slot));
38
+ }
39
+ /** Convert a top-left fractional rect into whole canvas pixels with a bottom-left origin; neighbours share edges exactly. */
40
+ export function viewportPixels(rect, width, height) {
41
+ const left = Math.round(rect[0] * width);
42
+ const right = Math.round((rect[0] + rect[2]) * width);
43
+ const top = Math.round(rect[1] * height);
44
+ const bottom = Math.round((rect[1] + rect[3]) * height);
45
+ return { x: left, y: height - bottom, width: Math.max(1, right - left), height: Math.max(1, bottom - top) };
46
+ }
@@ -62,7 +62,6 @@ export declare function selectNearestWorldItem(candidates: readonly {
62
62
  /**
63
63
  * Spawn and track pickup-able items scattered in the world, including drops that scatter on death.
64
64
  *
65
- * @capability world-drops spawn pickup-able items in the world, including death drops
66
65
  * @internal
67
66
  */
68
67
  export declare function createWorldItemStore(deps: WorldItemStoreDeps): WorldItemStore;
@@ -87,6 +86,11 @@ export interface ResolvedDeathDrops {
87
86
  * @internal
88
87
  */
89
88
  export declare function resolveDeathDrops(drops: readonly Drop[], options: ResolveDeathDropsOptions): ResolvedDeathDrops;
89
+ /**
90
+ * Per-rarity look of a dropped item: beam, color and label. Set via `defineGame({ worldItem: { rarityStyle } })`.
91
+ *
92
+ * @capability loot-beams rarity-colored loot beams and labels on dropped items via defineGame({ worldItem: { rarityStyle, beamHeight } })
93
+ */
90
94
  export interface RarityStyle {
91
95
  color?: string;
92
96
  beam?: boolean;
@@ -39,7 +39,6 @@ export function selectNearestWorldItem(candidates, from, radius) {
39
39
  /**
40
40
  * Spawn and track pickup-able items scattered in the world, including drops that scatter on death.
41
41
  *
42
- * @capability world-drops spawn pickup-able items in the world, including death drops
43
42
  * @internal
44
43
  */
45
44
  export function createWorldItemStore(deps) {
@@ -1,3 +1,4 @@
1
+ import { type GamepadGlyphName, type GamepadGlyphSet } from "./gamepadModel.js";
1
2
  /**
2
3
  * Action-binding model: games bind semantic actions ("jump", "interact") to
3
4
  * physical control codes; capture layers resolve raw events through this map
@@ -33,7 +34,13 @@ export type ActionCodes<TCode extends string = string> = readonly TCode[] | {
33
34
  toggle?: readonly TCode[];
34
35
  repeatMs?: number;
35
36
  };
36
- /** Maps each game action name to the input codes (hold/toggle keys, repeat rate) that trigger it. */
37
+ /**
38
+ * Maps each game action name to the input codes (hold/toggle keys, repeat rate) that trigger it. In a
39
+ * `defineGame({ input })`, pressing an action runs the same-named command with `{ yaw, pitch, aim }`;
40
+ * `repeatMs` re-fires it while held (automatic weapons).
41
+ *
42
+ * @capability fire-input bind a key or mouse button to a named action that runs the same-named command with the aim; repeatMs auto-fires while held
43
+ */
37
44
  export type ActionCodesMap<TAction extends string = string, TCode extends string = string> = Record<TAction, ActionCodes<TCode>>;
38
45
  /** @internal */
39
46
  export declare function actionRepeatMs(codes: ActionCodes | undefined): number | undefined;
@@ -69,7 +76,7 @@ export declare function resolveActionCommand(action: string, has: (command: stri
69
76
  /** Short display label for a raw key/button code (e.g. `"KeyW"` → `"W"`).
70
77
  * @internal
71
78
  */
72
- export declare function bindingLabel(code: string): string;
79
+ export declare function bindingLabel(code: string, glyphSet?: GamepadGlyphName | GamepadGlyphSet): string;
73
80
  /** Short display label for an action's first bound code, or null when unbound.
74
81
  * @internal
75
82
  */
@@ -81,6 +88,14 @@ export interface ActionStateTracker<TAction extends string> {
81
88
  wasPressed(action: TAction): boolean;
82
89
  endFrame(): void;
83
90
  reset(): void;
91
+ /**
92
+ * Swap the binding map without dropping what is physically held: a key still down counts toward
93
+ * whatever action it maps to now, with no fresh press edge. Toggle state survives for actions that
94
+ * come back later.
95
+ */
96
+ rebind(map: ActionStateBindingMap<TAction, string>): void;
97
+ /** The action names in the current binding map. */
98
+ actions(): readonly TAction[];
84
99
  }
85
100
  /** @internal */
86
101
  export declare function createActionStateTracker<TAction extends string, TCode extends string = string>(map: ActionStateBindingMap<TAction, TCode>): ActionStateTracker<TAction>;
@@ -1,10 +1,4 @@
1
- /**
2
- * Action-binding model: games bind semantic actions ("jump", "interact") to
3
- * physical control codes; capture layers resolve raw events through this map
4
- * so gameplay code never sees keycodes. Control codes are plain strings, so
5
- * the same model serves keyboard codes, mouse buttons, touch controls, or
6
- * gamepad inputs.
7
- */
1
+ import { GAMEPAD_GLYPH_SETS } from "./gamepadModel.js";
8
2
  /** @internal */
9
3
  export function bindingMatches(code, binding) {
10
4
  return code === binding.primary || code === binding.secondary;
@@ -142,7 +136,15 @@ const CODE_LABELS = {
142
136
  /** Short display label for a raw key/button code (e.g. `"KeyW"` → `"W"`).
143
137
  * @internal
144
138
  */
145
- export function bindingLabel(code) {
139
+ export function bindingLabel(code, glyphSet = "generic") {
140
+ const padButton = /^pad:(\d+)$/.exec(code);
141
+ if (padButton !== null) {
142
+ const set = typeof glyphSet === "string" ? GAMEPAD_GLYPH_SETS[glyphSet] : glyphSet;
143
+ return set.buttons[Number(padButton[1])] ?? `Button ${padButton[1]}`;
144
+ }
145
+ const padAxis = /^padaxis:(\d+)([+-])$/.exec(code);
146
+ if (padAxis !== null)
147
+ return `Axis ${padAxis[1]}${padAxis[2]}`;
146
148
  const direct = CODE_LABELS[code];
147
149
  if (direct !== undefined)
148
150
  return direct;
@@ -177,9 +179,9 @@ function resolveActionBindingModes(config) {
177
179
  }
178
180
  /** @internal */
179
181
  export function createActionStateTracker(map) {
180
- const actions = Object.keys(map);
181
- const modesByAction = new Map(actions.map((action) => [action, resolveActionBindingModes(map[action])]));
182
- const heldCodesByAction = new Map(actions.map((action) => [action, new Set()]));
182
+ let actions = Object.keys(map);
183
+ let modesByAction = new Map(actions.map((action) => [action, resolveActionBindingModes(map[action])]));
184
+ let heldCodesByAction = new Map(actions.map((action) => [action, new Set()]));
183
185
  const toggledActions = new Set();
184
186
  const pressedThisFrame = new Set();
185
187
  const activeCodes = new Set();
@@ -238,5 +240,16 @@ export function createActionStateTracker(map) {
238
240
  pressedThisFrame.clear();
239
241
  activeCodes.clear();
240
242
  },
243
+ rebind(next) {
244
+ actions = Object.keys(next);
245
+ modesByAction = new Map(actions.map((action) => [action, resolveActionBindingModes(next[action])]));
246
+ heldCodesByAction = new Map(actions.map((action) => [action, new Set()]));
247
+ for (const code of activeCodes) {
248
+ const holdAction = findAction(code, (modes) => modes.hold);
249
+ if (holdAction !== null)
250
+ heldCodesByAction.get(holdAction).add(code);
251
+ }
252
+ },
253
+ actions: () => actions,
241
254
  };
242
255
  }