@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
@@ -3,6 +3,7 @@ import type { AvoidZone } from "../world/geometry.js";
3
3
  import { type ClearanceOptions } from "../world/scatterRegion.js";
4
4
  import type { TerraformSnapshot } from "../world/terraform.js";
5
5
  import type { EditorDocument, EditorEnvironment } from "./types.js";
6
+ import type { LightingConfig } from "../game/playableGame.js";
6
7
  /**
7
8
  * World-authoring convergence (#1018, epic #1006 Phase 4): derive the **coordinate/placement**
8
9
  * content of an `environment()` world from the scene document, so a game with an authored document
@@ -46,6 +47,8 @@ export declare function skyFromDocument(doc: EditorDocument): SkyEnvironmentConf
46
47
  * @internal
47
48
  */
48
49
  export declare function skyConfigFromEnvironment(env: EditorEnvironment): SkyEnvironmentConfig;
50
+ /** Converts authored `light` markers into the runtime lighting contract. */
51
+ export declare function lightingFromDocument(doc: EditorDocument): LightingConfig | undefined;
49
52
  /**
50
53
  * The origin-centered terrain footprint that covers every authored object in a document, padded and
51
54
  * floored to `minBounds`. An empty document yields exactly `minBounds`. Because `environment()`
@@ -25,9 +25,33 @@ export function skyConfigFromEnvironment(env) {
25
25
  ...(env.zenithColor === undefined ? {} : { zenithColor: env.zenithColor }),
26
26
  ...(env.sunIntensity === undefined ? {} : { sunIntensity: env.sunIntensity }),
27
27
  ...(env.ambientIntensity === undefined ? {} : { ambientIntensity: env.ambientIntensity }),
28
+ ...(env.sunAzimuth === undefined && env.sunElevation === undefined
29
+ ? {}
30
+ : {
31
+ sun: {
32
+ ...(env.sunAzimuth === undefined ? {} : { azimuth: env.sunAzimuth }),
33
+ ...(env.sunElevation === undefined ? {} : { elevation: env.sunElevation }),
34
+ },
35
+ }),
28
36
  ...(env.fog === undefined ? {} : { fog: { ...env.fog } }),
29
37
  };
30
38
  }
39
+ /** Converts authored `light` markers into the runtime lighting contract. */
40
+ export function lightingFromDocument(doc) {
41
+ const points = [];
42
+ const spots = [];
43
+ for (const marker of doc.markers) {
44
+ if (marker.kind !== "light" || marker.position === undefined)
45
+ continue;
46
+ const meta = marker.meta ?? {};
47
+ const base = { position: [marker.position.x, marker.position.y, marker.position.z], color: typeof meta.color === "string" ? meta.color : "#ffd6a0", intensity: typeof meta.intensity === "number" ? meta.intensity : 2, distance: typeof meta.range === "number" ? meta.range : 12, castShadow: meta.castShadow === true };
48
+ if (meta.type === "spot")
49
+ spots.push({ ...base, angle: typeof meta.angle === "number" ? meta.angle : Math.PI / 6 });
50
+ else
51
+ points.push(base);
52
+ }
53
+ return points.length === 0 && spots.length === 0 ? undefined : { point: points, spot: spots };
54
+ }
31
55
  /**
32
56
  * The origin-centered terrain footprint that covers every authored object in a document, padded and
33
57
  * floored to `minBounds`. An empty document yields exactly `minBounds`. Because `environment()`
@@ -5,7 +5,9 @@ export type { EditorTerrain } from "./types.js";
5
5
  /** A baked top-down minimap (PNG data URI + world bounds) persisted on the scene document. */
6
6
  export type { EditorMinimapBake } from "./types.js";
7
7
  /** Scene-document sky/fog/lighting bag edited by the lighting workspace. */
8
- export type { EditorEnvironment, EditorFogConfig, EditorSkyPreset } from "./types.js";
8
+ export type { EditorEnvironment, EditorFogConfig, EditorSkyPreset, PointLightingConfig } from "./types.js";
9
+ /** Declarative HDRI, cube, or gradient environment source used by the shell. */
10
+ export type { EnvironmentSource } from "../render/environment.js";
9
11
  /** Per-kind show/hide flags for the editor's layer panel. */
10
12
  export type { EditorKindVisibility } from "./types.js";
11
13
  /** The four placeable-object collections a prefab fragment or clipboard fragment carries. */
@@ -62,7 +64,7 @@ export { WORLD_BASE_SHARD_ID, WORLD_MANIFEST_KIND, decodeWorldManifest, exportWo
62
64
  export type { DecodeWorldManifestResult, SplitWorldOptions, SplitWorldResult, WorldGrid, WorldManifest, WorldManifestShard, WorldQuery, WorldShardBounds, WorldShardResidency, WorldShardResolver, WorldStreamingConfig, } from "./world.js";
63
65
  /** Result of {@link decodeEditorDocument}: a typed document, or every diagnostic collected while decoding it. */
64
66
  export type { DecodeEditorDocumentResult } from "./document.js";
65
- export { environmentContentFromDocument, skyConfigFromEnvironment, skyFromDocument, terrainBoundsFromDocument, } from "./environment.js";
67
+ export { environmentContentFromDocument, lightingFromDocument, skyConfigFromEnvironment, skyFromDocument, terrainBoundsFromDocument, } from "./environment.js";
66
68
  /** Coordinate/placement content of an `environment()` world derived from the scene document. */
67
69
  export type { EnvironmentContent, EnvironmentContentOptions } from "./environment.js";
68
70
  export { bakeMinimapFromDocument, documentBakeZones } from "./minimap.js";
@@ -3,7 +3,7 @@ export { ENTITY_CATALOG_ID, entityDefinitionSchema, entityEntryFromCatalog, reso
3
3
  export { WELL_KNOWN_MARKER_KINDS, WELL_KNOWN_PATH_KINDS, WELL_KNOWN_VOLUME_KINDS, } from "./types.js";
4
4
  export { applyEditorDocumentOverlay, cloneEditorDocument, collectDescendants, createEmptyEditorDocument, createPrefabFragment, decodeEditorDocument, editorChildren, editorDocumentBounds, editorDocumentExtras, editorDocumentSize, editorMarkerPosition, editorMarkerXZ, editorParentOf, editorRoots, exportEditorDocumentJson, extractEditorFragment, findEditorCatalog, findEditorCatalogEntry, findEditorCollection, findEditorMarker, findEditorNote, findEditorPath, findEditorPrefab, findEditorVolume, importEditorDocumentJson, isEditorObjectCollectionLocked, isEditorObjectHidden, isEditorObjectLocked, isEditorObjectSelfLocked, listEditorKinds, mergeEditorDocuments, normalizeEditorLayers, requireEditorMarker, seedEditorCatalogs, wouldCreateCycle, } from "./document.js";
5
5
  export { WORLD_BASE_SHARD_ID, WORLD_MANIFEST_KIND, decodeWorldManifest, exportWorldManifestJson, loadWorldDocument, selectWorldShards, shardMatchesQuery, singleShardWorldManifest, splitEditorDocumentIntoShards, } from "./world.js";
6
- export { environmentContentFromDocument, skyConfigFromEnvironment, skyFromDocument, terrainBoundsFromDocument, } from "./environment.js";
6
+ export { environmentContentFromDocument, lightingFromDocument, skyConfigFromEnvironment, skyFromDocument, terrainBoundsFromDocument, } from "./environment.js";
7
7
  export { bakeMinimapFromDocument, documentBakeZones } from "./minimap.js";
8
8
  export { createWorldStreamer, shardDistance } from "./streamer.js";
9
9
  export { applyDirectiveOverlay, materializeDirective, materializeDirectives, materializePopulationDirective, materializeScatterDirective, resolveDirectiveFootprint, } from "./directives.js";
@@ -3,6 +3,10 @@ import type { EditorUiDocument } from "../ui/hudDocument.js";
3
3
  import type { MinimapBakeBounds } from "../world/minimapBake.js";
4
4
  import type { TerraformSnapshot } from "../world/terraform.js";
5
5
  import type { EditorGridLayer } from "./grid.js";
6
+ import type { EnvironmentSource } from "../render/environment.js";
7
+ import type { PointLightingConfig } from "../game/playableGame.js";
8
+ /** Runtime point-light shape reused by authored editor light markers. */
9
+ export type { PointLightingConfig } from "../game/playableGame.js";
6
10
  export type { EditorUiDocument, EditorUiPanelLayout, HudResizeAxes, HudPanelTypeDef } from "../ui/hudDocument.js";
7
11
  /** A world-space point used across editor markers, volumes, and paths. */
8
12
  export type EditorVec3 = {
@@ -177,6 +181,12 @@ export interface EditorMinimapBake {
177
181
  /** World-space rectangle the baked image spans. */
178
182
  bounds: MinimapBakeBounds;
179
183
  }
184
+ /** A named baked artifact produced by an editor or build tool. */
185
+ export interface EditorBake {
186
+ kind: "nav" | "minimap";
187
+ id: string;
188
+ data: unknown;
189
+ }
180
190
  /** Named sky look stored on the scene document; matches runtime `SkyEnvironmentConfig.preset`. */
181
191
  export type EditorSkyPreset = "day" | "dusk" | "night";
182
192
  /**
@@ -197,6 +207,10 @@ export interface EditorFogConfig {
197
207
  * @capability editor-environment persist sky/fog/lighting knobs on the scene document
198
208
  */
199
209
  export interface EditorEnvironment {
210
+ /** Optional runtime environment map source; absent keeps the procedural gradient. */
211
+ source?: EnvironmentSource;
212
+ /** Authored local point lights; the runtime clamps these to its bounded light budget. */
213
+ pointLights?: readonly PointLightingConfig[];
200
214
  /** Fixed sky look when `timeOfDay` is off (or no clock is available). */
201
215
  preset?: EditorSkyPreset;
202
216
  /** Drive sun/sky from the world clock's day fraction instead of the fixed `preset`. */
@@ -205,6 +219,10 @@ export interface EditorEnvironment {
205
219
  zenithColor?: string;
206
220
  sunIntensity?: number;
207
221
  ambientIntensity?: number;
222
+ /** Sun compass bearing in degrees (0 = -Z, 90 = +X); runtime `SkySunConfig.azimuth`. */
223
+ sunAzimuth?: number;
224
+ /** Sun height above the horizon in degrees; runtime `SkySunConfig.elevation`. */
225
+ sunElevation?: number;
208
226
  fog?: EditorFogConfig;
209
227
  }
210
228
  /** The full authored scene: every marker, volume, path, note, and sculpted terrain for a game. */
@@ -246,6 +264,8 @@ export interface EditorDocument {
246
264
  * until baked, so old docs and unbaked games load unchanged.
247
265
  */
248
266
  minimap?: EditorMinimapBake;
267
+ /** Named baked artifacts; `minimap` remains as a compatible legacy alias. */
268
+ bakes?: EditorBake[];
249
269
  /**
250
270
  * Scene-document sky/fog/lighting (#1110). Absent until authored in the lighting workspace or
251
271
  * seeded via `editorLayers`, so old docs load unchanged and world.ts can keep a fallback sky.
@@ -17,6 +17,10 @@ export interface ReputationLedgerConfig {
17
17
  min?: number;
18
18
  max?: number;
19
19
  }
20
+ /** Plain JSON standings of a {@link ReputationLedger}: stored values only, `initial` stays config. */
21
+ export interface ReputationLedgerState {
22
+ records: Record<string, Record<string, number>>;
23
+ }
20
24
  export interface ReputationLedger {
21
25
  standing(actorId: string, factionId: string): number;
22
26
  hasStanding(actorId: string, factionId: string): boolean;
@@ -26,6 +30,8 @@ export interface ReputationLedger {
26
30
  relation(actorId: string, factionId: string): FactionRelation;
27
31
  standings(actorId: string): Record<string, number>;
28
32
  reset(actorId: string, factionId?: string): void;
33
+ snapshot(): ReputationLedgerState;
34
+ restore(next: ReputationLedgerState): void;
29
35
  }
30
36
  export declare function createReputationLedger(config?: ReputationLedgerConfig): ReputationLedger;
31
37
  export interface EffectiveRelationInput {
@@ -77,6 +77,17 @@ export function createReputationLedger(config = {}) {
77
77
  else
78
78
  records.get(actorId)?.delete(factionId);
79
79
  },
80
+ snapshot() {
81
+ const out = {};
82
+ for (const [actorId, row] of records)
83
+ out[actorId] = Object.fromEntries(row);
84
+ return { records: out };
85
+ },
86
+ restore(next) {
87
+ records.clear();
88
+ for (const [actorId, row] of Object.entries(next.records))
89
+ records.set(actorId, new Map(Object.entries(row)));
90
+ },
80
91
  };
81
92
  }
82
93
  export function effectiveRelation(input) {
@@ -1,3 +1,5 @@
1
+ import type { WeaponHandlingFrame } from "../combat/weaponHandling.js";
2
+ import type { WeaponPresentationTuning } from "../combat/weaponPresentation.js";
1
3
  export interface CameraFollowState {
2
4
  entityId: string;
3
5
  target: {
@@ -12,6 +14,11 @@ export interface CameraFollowState {
12
14
  };
13
15
  distance: number;
14
16
  }
17
+ /** What a camera rig reads about the held weapon each frame; see `GameCameraConfig.weapon`. */
18
+ export interface CameraWeaponView {
19
+ handling: WeaponHandlingFrame;
20
+ presentation?: WeaponPresentationTuning;
21
+ }
15
22
  export interface FirstPersonCameraConfig {
16
23
  /** Camera height above the followed entity's origin. Default 1.6. */
17
24
  eyeHeight?: number;
@@ -45,6 +52,12 @@ export interface FirstPersonCameraConfig {
45
52
  export type CameraRigKind = "orbit" | "first" | "topDown" | "rts" | "shoulder" | "lockOn" | "chase" | "observer" | "turntable" | "sideScroll" | "inspection" | "none";
46
53
  /** Canvas camera projection. "orthographic" renders a flat 2D-style view (side-scrollers, falling-block puzzles) — pair with `rig: "sideScroll"`; default "perspective". */
47
54
  export type CameraProjection = "perspective" | "orthographic";
55
+ /** Pixel-art orthographic camera tuning. `pixelsPerUnit` is the authored pixel density. */
56
+ export interface PixelPerfectCameraConfig {
57
+ pixelsPerUnit: number;
58
+ /** Increase density in whole-number steps to use the largest scale that fits the viewport. */
59
+ integerScale?: boolean;
60
+ }
48
61
  /** Fixed lateral 2.5D follow (side-on platformer cam): the camera sits perpendicular to the travel axis, tracks the followed entity, and never reads player look input. */
49
62
  export interface SideScrollCameraConfig {
50
63
  /** World axis the action travels along; the camera watches from the perpendicular side. Default "x". */
@@ -164,11 +177,47 @@ export interface ChaseCameraConfig {
164
177
  lookHeight?: number;
165
178
  /** Spring-arm damping (higher = stiffer, less lag). Default 6. */
166
179
  springDamping?: number;
167
- /** Speed→FOV curve: FOV lerps from `base` to `max` as speed climbs to `speedForMax`. */
180
+ /**
181
+ * Speed→FOV curve: FOV lerps from `base` to `max` as speed climbs to `speedForMax`. Speed is the
182
+ * followed entity's sim `velocity` when it publishes one. `response` (default 6) eases the FOV
183
+ * toward the curve with `1-exp(-response*dt)`; `Infinity` applies it unsmoothed.
184
+ */
168
185
  fov?: {
169
186
  base?: number;
170
187
  max?: number;
171
188
  speedForMax?: number;
189
+ response?: number;
190
+ };
191
+ /** Pull the boom back by up to `extra` world units as speed climbs to `speedForMax` (default the FOV curve's `speedForMax`). */
192
+ distanceBySpeed?: {
193
+ extra: number;
194
+ speedForMax?: number;
195
+ };
196
+ /**
197
+ * Tilt the boom with the target's body pitch (`rotationX`) so the camera stays behind it on
198
+ * slopes and ramps. `blend` (default 1) is the fraction of the pitch followed, clamped to `max`
199
+ * radians (default 0.6) and eased by `response` (default 5). Omit the block to keep a level boom.
200
+ */
201
+ pitchFollow?: {
202
+ blend?: number;
203
+ max?: number;
204
+ response?: number;
205
+ };
206
+ /** How `ctx.camera.kickFov(degrees)` impulses feel: the kick decays at `decay` per second (default 7) and the summed kick is clamped to `max` degrees (default 20). */
207
+ fovKick?: {
208
+ decay?: number;
209
+ max?: number;
210
+ };
211
+ /** Input action that, while held, swings the chase view to look back at what is behind the target. */
212
+ lookBackAction?: string;
213
+ /**
214
+ * Pull the boom in past scene colliders, objects, terrain and walls via `ctx.scene.raycast`
215
+ * (entities never block). `radius` (default 0.3) keeps the lens off the surface; `minDistance`
216
+ * (default 0.8) is the closest the camera gets to the look pivot. Default on; `false` disables.
217
+ */
218
+ collision?: boolean | {
219
+ radius?: number;
220
+ minDistance?: number;
172
221
  };
173
222
  /** Procedural shake amplitude per unit of speed (adds to the trauma channel). Default 0.0. */
174
223
  shakePerSpeed?: number;
@@ -378,6 +427,8 @@ export interface GameCameraConfig {
378
427
  rig?: CameraRigKind;
379
428
  /** Canvas camera projection. "orthographic" renders a flat 2D-style view (side-scrollers, falling-block puzzles) — pair with `rig: "sideScroll"`; default perspective. */
380
429
  projection?: CameraProjection;
430
+ /** Pixel-perfect orthographic rendering; ignored for perspective cameras. */
431
+ pixelPerfect?: PixelPerfectCameraConfig;
381
432
  /** Render frustum overrides applied to the canvas camera. `far` defaults to 300 — raise it for worlds whose content spans more than a few hundred units, or distant scenery silently clips. `zoom` is the orthographic zoom in canvas pixels per world unit, read only when `projection` is "orthographic"; default 50. */
382
433
  frustum?: {
383
434
  fov?: number;
@@ -438,6 +489,13 @@ export interface GameCameraConfig {
438
489
  followEntityId?: string | null;
439
490
  /** Fired each frame after cameraFollow lock is applied. */
440
491
  onCameraFollow?: (state: CameraFollowState) => void;
492
+ /**
493
+ * The followed entity's held weapon, read every frame by the `first` and `shoulder` rigs. Return the
494
+ * weapon's `createWeaponHandling` frame and its presentation tuning, or `null` for none. First person
495
+ * poses the viewmodel from it (offset, ADS pose, viewmodel FOV, sway, bob, kick) and adds the recoil to
496
+ * the look; over-the-shoulder takes its ADS blend from `adsProgress` and adds the same recoil.
497
+ */
498
+ weapon?: (entityId: string) => CameraWeaponView | null;
441
499
  rotateSpeed?: number;
442
500
  zoomSpeed?: number;
443
501
  dampingFactor?: number;
@@ -30,11 +30,18 @@ export type ChatSendResult = {
30
30
  export interface ChatSnapshot {
31
31
  messages: Record<string, ChatMessage[]>;
32
32
  counter: number;
33
+ rateWindows?: Record<string, Record<string, number[]>>;
33
34
  }
34
35
  export interface Chat {
35
36
  register(def: ChatChannelDef): void;
36
37
  channels(): ChatChannelDef[];
38
+ send(fromUserId: string, body: string): ChatSendResult;
37
39
  send(fromUserId: string, channelId: string, body: string): ChatSendResult;
40
+ recent(options?: {
41
+ limit?: number;
42
+ channelId?: string;
43
+ viewerUserId?: string;
44
+ }): ChatMessage[];
38
45
  whisper(fromUserId: string, toUserId: string, body: string): ChatSendResult;
39
46
  history(channelId: string, options?: {
40
47
  limit?: number;
@@ -57,12 +64,26 @@ export interface ChatDeps {
57
64
  }
58
65
  export declare const DEFAULT_CHAT_RATE_LIMIT: ChatRateLimit;
59
66
  export declare const DEFAULT_CHAT_HISTORY_LIMIT = 100;
60
- export declare const DEFAULT_CHAT_BODY_LENGTH = 500;
67
+ export declare const DEFAULT_CHAT_BODY_LENGTH = 240;
61
68
  export declare const DEFAULT_PROXIMITY_CHAT_RADIUS = 20;
62
69
  export declare const WHISPER_CHANNEL_PREFIX = "whisper:";
63
70
  export declare function whisperChannelId(a: string, b: string): string;
64
71
  export interface ChatRateLimiter {
65
72
  allow(key: string, atMs: number): boolean;
73
+ snapshot(): Record<string, number[]>;
74
+ restore(state: Record<string, number[]>): void;
66
75
  }
67
76
  export declare function createChatRateLimiter(limit: ChatRateLimit): ChatRateLimiter;
68
77
  export declare function createChat(deps: ChatDeps): Chat;
78
+ /** Sanitized chat text or a displayable validation failure. */
79
+ export type ChatValidation = {
80
+ ok: true;
81
+ text: string;
82
+ } | {
83
+ ok: false;
84
+ reason: string;
85
+ };
86
+ /** Strip control characters and enforce the shared chat message policy before storage or broadcast. */
87
+ export declare function validateChatMessage(value: unknown, options?: {
88
+ maxLength?: number;
89
+ }): ChatValidation;
package/dist/game/chat.js CHANGED
@@ -1,6 +1,7 @@
1
- export const DEFAULT_CHAT_RATE_LIMIT = { count: 10, perMs: 10_000 };
1
+ import { decideRateWindow } from "../time/rateWindow.js";
2
+ export const DEFAULT_CHAT_RATE_LIMIT = { count: 1, perMs: 2_000 };
2
3
  export const DEFAULT_CHAT_HISTORY_LIMIT = 100;
3
- export const DEFAULT_CHAT_BODY_LENGTH = 500;
4
+ export const DEFAULT_CHAT_BODY_LENGTH = 240;
4
5
  export const DEFAULT_PROXIMITY_CHAT_RADIUS = 20;
5
6
  export const WHISPER_CHANNEL_PREFIX = "whisper:";
6
7
  export function whisperChannelId(a, b) {
@@ -11,15 +12,17 @@ export function createChatRateLimiter(limit) {
11
12
  const windows = new Map();
12
13
  return {
13
14
  allow(key, atMs) {
14
- const cutoff = atMs - limit.perMs;
15
- const stamps = (windows.get(key) ?? []).filter((stamp) => stamp > cutoff);
16
- if (stamps.length >= limit.count) {
17
- windows.set(key, stamps);
18
- return false;
19
- }
20
- stamps.push(atMs);
21
- windows.set(key, stamps);
22
- return true;
15
+ const result = decideRateWindow(windows.get(key) ?? [], atMs, { windowMs: limit.perMs, max: limit.count });
16
+ windows.set(key, result.timestamps);
17
+ return result.allowed;
18
+ },
19
+ snapshot() {
20
+ return Object.fromEntries(Array.from(windows, ([key, stamps]) => [key, [...stamps]]));
21
+ },
22
+ restore(state) {
23
+ windows.clear();
24
+ for (const [key, stamps] of Object.entries(state))
25
+ windows.set(key, [...stamps]);
23
26
  },
24
27
  };
25
28
  }
@@ -93,11 +96,10 @@ export function createChat(deps) {
93
96
  messagesByChannel.set(channelId, ring);
94
97
  }
95
98
  function sendResolved(fromUserId, channelId, body, def) {
96
- const trimmed = body.trim();
97
- if (trimmed.length === 0)
98
- return { reason: "empty message" };
99
- if (trimmed.length > maxBodyLength)
100
- return { reason: "message too long" };
99
+ const validation = validateChatMessage(body, { maxLength: maxBodyLength });
100
+ if (!validation.ok)
101
+ return { reason: validation.reason };
102
+ const trimmed = validation.text;
101
103
  let filteredBody = trimmed;
102
104
  if (filter !== undefined) {
103
105
  const filtered = filter.apply(trimmed);
@@ -136,10 +138,18 @@ export function createChat(deps) {
136
138
  channels() {
137
139
  return Array.from(channelDefs.values(), (def) => ({ ...def }));
138
140
  },
139
- send(fromUserId, channelId, body) {
141
+ send(fromUserId, channelOrBody, suppliedBody) {
142
+ const channelId = suppliedBody === undefined ? "global" : channelOrBody;
143
+ const body = suppliedBody ?? channelOrBody;
140
144
  const def = channelDefs.get(channelId) ?? null;
141
145
  return sendResolved(fromUserId, channelId, body, def);
142
146
  },
147
+ recent(options) {
148
+ return this.history(options?.channelId ?? "global", {
149
+ limit: Math.max(0, Math.min(100, Math.floor(options?.limit ?? 50) || 0)),
150
+ viewerUserId: options?.viewerUserId,
151
+ });
152
+ },
143
153
  whisper(fromUserId, toUserId, body) {
144
154
  if (fromUserId === toUserId)
145
155
  return { reason: "cannot whisper yourself" };
@@ -153,14 +163,15 @@ export function createChat(deps) {
153
163
  ? ring
154
164
  : ring.filter((message) => !hasBlocked(viewerUserId, message.fromUserId));
155
165
  const limit = options?.limit;
156
- return limit === undefined ? visible.slice() : visible.slice(-limit);
166
+ return limit === undefined ? visible.slice() : limit <= 0 ? [] : visible.slice(-limit);
157
167
  },
158
168
  snapshot() {
159
169
  const messages = {};
160
170
  for (const [channelId, ring] of messagesByChannel) {
161
171
  messages[channelId] = ring.map((message) => ({ ...message }));
162
172
  }
163
- return { messages, counter };
173
+ const rateWindows = Object.fromEntries(Array.from(limiters, ([id, limiter]) => [id, limiter.snapshot()]));
174
+ return { messages, counter, rateWindows };
164
175
  },
165
176
  hydrate(data) {
166
177
  messagesByChannel.clear();
@@ -168,6 +179,24 @@ export function createChat(deps) {
168
179
  messagesByChannel.set(channelId, ring.map((message) => ({ ...message })));
169
180
  }
170
181
  counter = data.counter;
182
+ limiters.clear();
183
+ for (const [channelId, state] of Object.entries(data.rateWindows ?? {})) {
184
+ limiterFor(channelId, channelDefs.get(channelId)?.rateLimit ?? defaultRateLimit).restore(state);
185
+ }
171
186
  },
172
187
  };
173
188
  }
189
+ /** Strip control characters and enforce the shared chat message policy before storage or broadcast. */
190
+ export function validateChatMessage(value, options = {}) {
191
+ const maxLength = options.maxLength ?? DEFAULT_CHAT_BODY_LENGTH;
192
+ if (!Number.isSafeInteger(maxLength) || maxLength < 1)
193
+ throw new RangeError("Invalid chat length limit");
194
+ if (typeof value !== "string")
195
+ return { ok: false, reason: "invalid message" };
196
+ const text = value.replace(/[\u0000-\u001f\u007f-\u009f]/g, "").trim();
197
+ if (!text.length)
198
+ return { ok: false, reason: "empty message" };
199
+ if (text.length > maxLength)
200
+ return { ok: false, reason: "message too long" };
201
+ return { ok: true, text };
202
+ }
@@ -1,4 +1,10 @@
1
1
  import type { GameContext } from "../runtime/gameContext.js";
2
+ import { type ActionContextStack } from "../input/actionContexts.js";
3
+ import type { ActionCodesMap } from "../input/actionBindings.js";
2
4
  export declare const PLAY_CONTROLS_STORE_KEY = "jg.playControls";
5
+ /** Returns the context stack associated with a game context. */
6
+ export declare function actionContextStack(ctx: GameContext): ActionContextStack;
7
+ /** Applies active contexts to a base action map for shell input tracking. */
8
+ export declare function activeActionCodes(ctx: GameContext, base: ActionCodesMap): ActionCodesMap;
3
9
  export declare function setPlayControlsActive(ctx: GameContext, active: boolean): void;
4
10
  export declare function playControlsActive(ctx: GameContext): boolean;
@@ -1,7 +1,36 @@
1
+ import { createActionContextStack } from "../input/actionContexts.js";
1
2
  export const PLAY_CONTROLS_STORE_KEY = "jg.playControls";
3
+ const ACTION_CONTEXTS = new WeakMap();
4
+ function contextsFor(ctx) {
5
+ let stack = ACTION_CONTEXTS.get(ctx);
6
+ if (stack === undefined) {
7
+ stack = createActionContextStack();
8
+ ACTION_CONTEXTS.set(ctx, stack);
9
+ }
10
+ return stack;
11
+ }
12
+ /** Returns the context stack associated with a game context. */
13
+ export function actionContextStack(ctx) {
14
+ return contextsFor(ctx);
15
+ }
16
+ /** Applies active contexts to a base action map for shell input tracking. */
17
+ export function activeActionCodes(ctx, base) {
18
+ const stack = contextsFor(ctx);
19
+ const layered = createActionContextStack();
20
+ layered.push({ id: "__jg_base_actions", codes: base, passthrough: true });
21
+ for (const context of stack.snapshot().contexts)
22
+ layered.push(context);
23
+ return layered.active();
24
+ }
2
25
  export function setPlayControlsActive(ctx, active) {
26
+ const stack = contextsFor(ctx);
27
+ if (active)
28
+ stack.pop("menu");
29
+ else
30
+ stack.push({ id: "menu", codes: {}, passthrough: false });
3
31
  ctx.game.store.set(PLAY_CONTROLS_STORE_KEY, active);
4
32
  }
5
33
  export function playControlsActive(ctx) {
6
- return ctx.game.store.get(PLAY_CONTROLS_STORE_KEY) !== false;
34
+ return ctx.game.store.get(PLAY_CONTROLS_STORE_KEY) !== false &&
35
+ !actionContextStack(ctx).snapshot().contexts.some((context) => context.id === "menu");
7
36
  }
@@ -12,7 +12,10 @@ import { type AssetCatalog, type ModelAssetRef } from "../scene/assetCatalog.js"
12
12
  import { type EntityStore } from "../scene/entityStore.js";
13
13
  import type { StoreHandle } from "../store/defineStore.js";
14
14
  import type { TimeConfig } from "../time/simClock.js";
15
+ import type { SimulationConfig } from "../runtime/simLoop.js";
15
16
  import type { WorldFeature } from "../world/features.js";
17
+ import type { PhysicsBackend } from "../physics/physicsBackend.js";
18
+ import type { CharacterControllerConfig } from "../movement/characterController.js";
16
19
  /** Tunes offline whole-world save (`defineGameDefinition({ persist })`). Offline games already autosave to `localStorage` by default — pass this only to tune it (or `persist: false` to opt out). Defaults: continuous `autosave` to `localStorage`, one slot, no version. */
17
20
  export interface PersistConfig {
18
21
  /** `"autosave"` (default) writes on a trailing timer while the world changes — at most once per `autosaveMs`, so a never-idle world (AI, physics, day/night) still persists; `"manual"` writes only on `ctx.game.save.checkpoint()` (save points / quest triggers). */
@@ -24,10 +27,14 @@ export interface PersistConfig {
24
27
  /** Autosave debounce in ms (autosave mode). Default 3000. */
25
28
  autosaveMs?: number;
26
29
  }
27
- /** World gravity and jump tuning, plus scene-object collision opt-ins, for the game's physics step. */
30
+ /** World gravity and jump tuning, plus the optional rigid-body backend and character capsule. */
28
31
  export interface PhysicsConfig {
29
32
  gravity?: number;
30
33
  jumpVelocity?: number;
34
+ /** Physics solver used by the shell movement controller and the context's physics stage. */
35
+ backend?: PhysicsBackend;
36
+ /** Capsule dimensions for players using the configured physics backend. */
37
+ controller?: CharacterControllerConfig;
31
38
  /** Opt placed scene objects into projectile raycasts as blocking cover AABBs. Default false — enabling changes where shots land. */
32
39
  projectileObstacles?: boolean;
33
40
  }
@@ -180,6 +187,8 @@ export interface GameDefinition<TAssetRef extends ModelAssetRef = ModelAssetRef,
180
187
  physics?: PhysicsConfig;
181
188
  /** Simulation clock: real→game time scale, selectable speeds, calendar. Exposed as `ctx.time`; the shell feeds its scaled dt to `loop.onTick`. */
182
189
  time?: TimeConfig;
190
+ /** How the simulation is stepped — a fixed `hz` with render interpolation, or `"variable"` (default) once per frame. Exposed as `ctx.sim`. */
191
+ simulation?: SimulationConfig;
183
192
  /** Per-action ring-buffer capacity for `ctx.game.feed`. Default 20. */
184
193
  feed?: GameFeedOptions;
185
194
  inventories?: Record<string, InventoryDeclaration>;
@@ -12,11 +12,17 @@ export interface EntityDiedEvent {
12
12
  position: [number, number, number];
13
13
  serverId?: string;
14
14
  }
15
- /** Request that an entity's rig play a one-shot animation clip bound to `event` in its `animation.oneShots` (e.g. an "attack" swing); the shell resolves the clip and plays it once over the locomotion state. */
15
+ /** Request that an entity's rig play a one-shot animation clip bound to `event` in its `animation.oneShots` (e.g. an "attack" swing); the shell resolves the clip and plays it once over the locomotion state. With an `animation.graph`, `event` arms the trigger of that name. */
16
16
  export interface EntityAnimationEvent {
17
17
  instanceId: string;
18
18
  event: string;
19
19
  }
20
+ /** A named clip moment (`AnimGraph.events`) the rig just crossed — foot plant, hit frame, reload point. Audio and combat listen by `name`. */
21
+ export interface AnimationClipEvent {
22
+ instanceId: string;
23
+ name: string;
24
+ clip: string;
25
+ }
20
26
  /**
21
27
  * One granted reward: an inventory `item` id or a `currency` id, plus how many. Named because every
22
28
  * consumer of `loot.granted` needs it — a toast renderer, a loot log, a session summary — and each
@@ -230,6 +236,12 @@ export interface AudioLoopSetEvent {
230
236
  rate?: number;
231
237
  gain?: number;
232
238
  at?: readonly [number, number, number];
239
+ /** Emitter velocity (world units/s) for doppler on sounds that declare `doppler`. */
240
+ velocity?: readonly [number, number, number];
241
+ /** Lowpass cutoff in Hz. */
242
+ lowpass?: number;
243
+ /** Highpass cutoff in Hz. */
244
+ highpass?: number;
233
245
  }
234
246
  /** Stop and dispose the retained loop `id`; an unknown `id` is ignored (#1051). */
235
247
  export interface AudioLoopStopEvent {
@@ -269,6 +281,7 @@ export interface GameEventMap {
269
281
  "audio.loopSet": AudioLoopSetEvent;
270
282
  "audio.loopStop": AudioLoopStopEvent;
271
283
  "entity.animation": EntityAnimationEvent;
284
+ "animation.event": AnimationClipEvent;
272
285
  }
273
286
  export type GameEventHandler<TPayload> = (payload: TPayload) => void;
274
287
  export interface GameEvents<TMap extends GameEventMap = GameEventMap> {
@@ -38,6 +38,10 @@ export interface LootRegistry {
38
38
  register(def: LootTableDef): void;
39
39
  has(id: string): boolean;
40
40
  roll(id: string, rng?: () => number): Drop[];
41
+ /** Registered tables in registration order; plain JSON unless an entry uses `generate`. */
42
+ snapshot(): LootTableDef[];
43
+ /** Replaces every registered table with `next`, validating each. */
44
+ restore(next: readonly LootTableDef[]): void;
41
45
  }
42
46
  /** Options for {@link createLootRegistry} — inject a default RNG so bare `roll(id)` uses the world stream. */
43
47
  export interface LootRegistryOptions {
@@ -1,4 +1,10 @@
1
1
  import { resolveRng } from "../random/resolveRng.js";
2
+ function copyDef(def) {
3
+ return {
4
+ ...def,
5
+ entries: def.entries.map((entry) => ({ ...entry, count: typeof entry.count === "number" ? entry.count : [entry.count[0], entry.count[1]] })),
6
+ };
7
+ }
2
8
  function assertValidEntry(entry, mode) {
3
9
  const kinds = [entry.item !== undefined, entry.currency !== undefined, entry.generate !== undefined].filter(Boolean).length;
4
10
  if (kinds !== 1) {
@@ -88,6 +94,16 @@ export function createLootRegistry(options = {}) {
88
94
  }
89
95
  return drops;
90
96
  },
97
+ snapshot() {
98
+ return [...tables.values()].map(copyDef);
99
+ },
100
+ restore(next) {
101
+ for (const def of next)
102
+ assertValidDef(def);
103
+ tables.clear();
104
+ for (const def of next)
105
+ tables.set(def.id, copyDef(def));
106
+ },
91
107
  };
92
108
  }
93
109
  /**
@@ -0,0 +1,25 @@
1
+ /** Viewport dimensions in device-independent canvas pixels. */
2
+ export interface PixelPerfectViewport {
3
+ width: number;
4
+ height: number;
5
+ }
6
+ /** The orthographic bounds and scale derived from a pixel-perfect camera config. */
7
+ export interface PixelPerfectFrustum {
8
+ left: number;
9
+ right: number;
10
+ top: number;
11
+ bottom: number;
12
+ zoom: number;
13
+ scale: number;
14
+ }
15
+ /**
16
+ * Computes an orthographic frustum whose world units map to the requested pixel density.
17
+ * Integer scaling chooses the largest whole-number density multiplier available in the viewport.
18
+ */
19
+ export declare function pixelPerfectFrustum(viewport: PixelPerfectViewport, pixelsPerUnit: number, integerScale?: boolean): PixelPerfectFrustum;
20
+ /** Snaps a camera position to the authored pixel grid without changing its orientation. */
21
+ export declare function snapPixelPerfectPosition<T extends {
22
+ x: number;
23
+ y: number;
24
+ z: number;
25
+ }>(position: T, pixelsPerUnit: number): T;