@jgengine/shell 0.18.1 → 0.21.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 (261) hide show
  1. package/CHANGELOG.md +504 -7
  2. package/dist/GameHost.d.ts +12 -1
  3. package/dist/GameHost.js +14 -1
  4. package/dist/GamePlayerShell.js +132 -17
  5. package/dist/Shell3dPresentation.d.ts +5 -11
  6. package/dist/Shell3dPresentation.js +73 -11
  7. package/dist/ShellChrome.d.ts +2 -1
  8. package/dist/ShellChrome.js +22 -6
  9. package/dist/ShellHudPresentation.d.ts +20 -2
  10. package/dist/ShellHudPresentation.js +41 -6
  11. package/dist/audio/AudioComponents.js +6 -2
  12. package/dist/audio/audioEngine.d.ts +24 -0
  13. package/dist/audio/audioEngine.js +231 -83
  14. package/dist/audio/audioWire.d.ts +3 -0
  15. package/dist/audio/audioWire.js +9 -3
  16. package/dist/audio/loopParams.d.ts +39 -0
  17. package/dist/audio/loopParams.js +43 -0
  18. package/dist/audio/synthEngine.d.ts +7 -1
  19. package/dist/audio/synthEngine.js +103 -38
  20. package/dist/camera/GameCameraRig.js +3 -2
  21. package/dist/camera/GameFirstPersonCamera.d.ts +6 -4
  22. package/dist/camera/GameFirstPersonCamera.js +68 -27
  23. package/dist/camera/Viewports.d.ts +34 -0
  24. package/dist/camera/Viewports.js +123 -0
  25. package/dist/camera/cameraRigs.js +76 -62
  26. package/dist/camera/chaseHeading.d.ts +9 -0
  27. package/dist/camera/chaseHeading.js +10 -0
  28. package/dist/camera/customMuzzle.d.ts +14 -0
  29. package/dist/camera/customMuzzle.js +68 -0
  30. package/dist/camera/index.d.ts +1 -0
  31. package/dist/camera/index.js +1 -0
  32. package/dist/camera/orbitCameraMath.js +2 -2
  33. package/dist/camera/rigMath.d.ts +82 -4
  34. package/dist/camera/rigMath.js +133 -15
  35. package/dist/camera/rigResolve.d.ts +3 -0
  36. package/dist/camera/rigResolve.js +11 -0
  37. package/dist/combat/ProjectileModels.d.ts +31 -0
  38. package/dist/combat/ProjectileModels.js +70 -0
  39. package/dist/commandSink.d.ts +1 -2
  40. package/dist/commandSink.js +3 -2
  41. package/dist/defineGame.d.ts +19 -3
  42. package/dist/defineGame.js +39 -7
  43. package/dist/devtools/DevtoolsOverlay.d.ts +2 -69
  44. package/dist/devtools/DevtoolsOverlay.js +15 -167
  45. package/dist/devtools/DevtoolsRuntime.d.ts +7 -0
  46. package/dist/devtools/DevtoolsRuntime.js +61 -0
  47. package/dist/devtools/agentBridge.d.ts +6 -0
  48. package/dist/devtools/agentBridge.js +16 -2
  49. package/dist/devtools/devtoolsReports.d.ts +68 -0
  50. package/dist/devtools/devtoolsReports.js +100 -0
  51. package/dist/devtools/latencyInstrumentation.d.ts +3 -0
  52. package/dist/devtools/latencyInstrumentation.js +11 -0
  53. package/dist/devtools/playtestProbe.d.ts +4 -0
  54. package/dist/devtools/playtestProbe.js +22 -0
  55. package/dist/diagnostics/FpsCounter.d.ts +17 -0
  56. package/dist/diagnostics/FpsCounter.js +32 -0
  57. package/dist/drivers/FrameDriver.js +35 -20
  58. package/dist/drivers/FrameRateLimiter.d.ts +9 -0
  59. package/dist/drivers/FrameRateLimiter.js +39 -0
  60. package/dist/drivers/HudOnlyDriver.d.ts +6 -1
  61. package/dist/drivers/HudOnlyDriver.js +32 -14
  62. package/dist/environment/DayNightSky.js +5 -5
  63. package/dist/environment/Daylight.d.ts +35 -1
  64. package/dist/environment/Daylight.js +98 -28
  65. package/dist/environment/EnvironmentScene.js +11 -10
  66. package/dist/environment/VolumetricClouds.js +20 -2
  67. package/dist/environment/daylightCycle.d.ts +20 -0
  68. package/dist/environment/daylightCycle.js +59 -7
  69. package/dist/environment/sunShadowMath.d.ts +45 -0
  70. package/dist/environment/sunShadowMath.js +50 -0
  71. package/dist/input/gamepadPoll.d.ts +47 -0
  72. package/dist/input/gamepadPoll.js +142 -0
  73. package/dist/input/gamepadSource.d.ts +11 -1
  74. package/dist/input/gamepadSource.js +117 -71
  75. package/dist/input/mouseLook.js +2 -1
  76. package/dist/input/padHaptics.d.ts +17 -0
  77. package/dist/input/padHaptics.js +32 -0
  78. package/dist/input/pointerLock.d.ts +11 -0
  79. package/dist/input/pointerLock.js +19 -0
  80. package/dist/inputSink.d.ts +6 -6
  81. package/dist/inputSink.js +60 -21
  82. package/dist/lazyShell3dPresentation.d.ts +9 -0
  83. package/dist/lazyShell3dPresentation.js +24 -0
  84. package/dist/materialOverride.d.ts +42 -0
  85. package/dist/materialOverride.js +95 -36
  86. package/dist/multiplayer.d.ts +33 -3
  87. package/dist/multiplayer.js +105 -22
  88. package/dist/pointer/pointerService.d.ts +2 -0
  89. package/dist/pointer/pointerService.js +12 -2
  90. package/dist/postfx/PostProcessing.d.ts +11 -1
  91. package/dist/postfx/PostProcessing.js +14 -42
  92. package/dist/postfx/gradeShader.d.ts +1 -1
  93. package/dist/postfx/gradeShader.js +30 -2
  94. package/dist/postfx/shadowPrepass.d.ts +3 -0
  95. package/dist/postfx/shadowPrepass.js +18 -0
  96. package/dist/postfx/stylizePass.d.ts +23 -0
  97. package/dist/postfx/stylizePass.js +216 -0
  98. package/dist/presentationRecovery.d.ts +75 -0
  99. package/dist/presentationRecovery.js +204 -0
  100. package/dist/presentationRecovery3d.d.ts +4 -0
  101. package/dist/presentationRecovery3d.js +32 -0
  102. package/dist/presentationResolve.d.ts +8 -1
  103. package/dist/presentationResolve.js +5 -2
  104. package/dist/render/CascadedShadows.d.ts +7 -2
  105. package/dist/render/CascadedShadows.js +98 -20
  106. package/dist/render/EnvironmentLighting.js +74 -35
  107. package/dist/render/HairCards.d.ts +7 -0
  108. package/dist/render/HairCards.js +23 -0
  109. package/dist/render/SceneModels.js +52 -78
  110. package/dist/render/SkinnedInstances.d.ts +91 -0
  111. package/dist/render/SkinnedInstances.js +249 -0
  112. package/dist/render/TextureFiltering.d.ts +16 -0
  113. package/dist/render/TextureFiltering.js +64 -0
  114. package/dist/render/authoredSurfaceMaterial.d.ts +43 -0
  115. package/dist/render/authoredSurfaceMaterial.js +108 -0
  116. package/dist/render/displayColor.d.ts +33 -0
  117. package/dist/render/displayColor.js +138 -0
  118. package/dist/render/materialAppearanceSignals.d.ts +23 -0
  119. package/dist/render/materialAppearanceSignals.js +58 -0
  120. package/dist/render/materialAsset.d.ts +98 -0
  121. package/dist/render/materialAsset.js +454 -0
  122. package/dist/render/measureBounds.js +16 -3
  123. package/dist/render/measureCollisionMesh.js +8 -1
  124. package/dist/render/modelAssets.d.ts +8 -0
  125. package/dist/render/modelAssets.js +43 -0
  126. package/dist/render/modelBindPose.d.ts +5 -0
  127. package/dist/render/modelBindPose.js +35 -0
  128. package/dist/render/modelLoad.d.ts +32 -0
  129. package/dist/render/modelLoad.js +70 -1
  130. package/dist/render/modelNodeVisibility.d.ts +6 -0
  131. package/dist/render/modelNodeVisibility.js +18 -0
  132. package/dist/render/modelRender.d.ts +18 -1
  133. package/dist/render/modelRender.js +81 -3
  134. package/dist/render/projectedOverdraw.d.ts +35 -0
  135. package/dist/render/projectedOverdraw.js +204 -0
  136. package/dist/render/resolveModel.d.ts +12 -2
  137. package/dist/render/resolveModel.js +25 -4
  138. package/dist/render/rigNode.d.ts +26 -0
  139. package/dist/render/rigNode.js +32 -0
  140. package/dist/render/spriteRender.d.ts +4 -0
  141. package/dist/render/spriteRender.js +5 -0
  142. package/dist/render/staticShapeInstances.d.ts +34 -0
  143. package/dist/render/staticShapeInstances.js +78 -0
  144. package/dist/render/useFootIk.d.ts +53 -4
  145. package/dist/render/useFootIk.js +262 -70
  146. package/dist/render/useModelAnimation.d.ts +50 -0
  147. package/dist/render/useModelAnimation.js +245 -99
  148. package/dist/render/useModelInstance.d.ts +25 -0
  149. package/dist/render/useModelInstance.js +43 -0
  150. package/dist/scatter/scatterModels.d.ts +3 -2
  151. package/dist/scatter/scatterModels.js +15 -7
  152. package/dist/scene/AuthoredScene.d.ts +23 -6
  153. package/dist/scene/AuthoredScene.js +46 -9
  154. package/dist/scene/CityRenderer.js +6 -6
  155. package/dist/scene/GeneratedAssetRenderer.js +11 -42
  156. package/dist/settings/QuickControls.js +1 -1
  157. package/dist/settings/SettingsMenu.js +11 -18
  158. package/dist/settings/SettingsRuntime.d.ts +5 -0
  159. package/dist/settings/SettingsRuntime.js +29 -2
  160. package/dist/settings/appliedSettings.d.ts +9 -3
  161. package/dist/settings/appliedSettings.js +6 -5
  162. package/dist/settings/settingsController.d.ts +3 -0
  163. package/dist/settings/settingsController.js +47 -3
  164. package/dist/shellControlSuspension.d.ts +18 -0
  165. package/dist/shellControlSuspension.js +28 -0
  166. package/dist/shellInputPublication.d.ts +28 -0
  167. package/dist/shellInputPublication.js +49 -0
  168. package/dist/shellPointerInput.d.ts +18 -0
  169. package/dist/shellPointerInput.js +49 -0
  170. package/dist/structures/BuildingKitBatch.d.ts +3 -1
  171. package/dist/structures/BuildingKitBatch.js +35 -56
  172. package/dist/structures/BuildingRenderBudget.d.ts +23 -0
  173. package/dist/structures/BuildingRenderBudget.js +54 -0
  174. package/dist/structures/GeneratedBuilding.d.ts +3 -15
  175. package/dist/structures/GeneratedBuilding.js +33 -22
  176. package/dist/structures/buildingKitMaterials.d.ts +20 -0
  177. package/dist/structures/buildingKitMaterials.js +70 -0
  178. package/dist/structures/buildingSpatialBatch.d.ts +11 -0
  179. package/dist/structures/buildingSpatialBatch.js +45 -0
  180. package/dist/structures/buildingSurface.d.ts +21 -0
  181. package/dist/structures/buildingSurface.js +134 -0
  182. package/dist/terrain/GrassField.d.ts +2 -1
  183. package/dist/terrain/GrassField.js +59 -5
  184. package/dist/terrain/grassGeometry.d.ts +10 -0
  185. package/dist/terrain/grassGeometry.js +55 -0
  186. package/dist/terrain/grassMaterial.js +2 -2
  187. package/dist/terrain/terrainDetailMaterial.d.ts +3 -3
  188. package/dist/terrain/terrainDetailMaterial.js +7 -7
  189. package/dist/touch/TouchControlsOverlay.js +4 -4
  190. package/dist/useShellMultiplayerSync.d.ts +1 -1
  191. package/dist/useShellMultiplayerSync.js +9 -3
  192. package/dist/vfx/ParticleField.d.ts +23 -20
  193. package/dist/vfx/ParticleField.js +168 -50
  194. package/dist/visibility/CullingProvider.js +2 -62
  195. package/dist/visibility/cullingDriver.d.ts +9 -0
  196. package/dist/visibility/cullingDriver.js +68 -0
  197. package/dist/weather/AuthoredWeatherLayer.d.ts +16 -0
  198. package/dist/weather/AuthoredWeatherLayer.js +31 -0
  199. package/dist/weather/DustField.d.ts +2 -1
  200. package/dist/weather/DustField.js +17 -3
  201. package/dist/weather/FireSpreadLayer.d.ts +4 -2
  202. package/dist/weather/FireSpreadLayer.js +5 -11
  203. package/dist/weather/LightningStrike.d.ts +4 -1
  204. package/dist/weather/LightningStrike.js +13 -17
  205. package/dist/weather/RainField.d.ts +2 -1
  206. package/dist/weather/RainField.js +17 -3
  207. package/dist/weather/RainImpactField.d.ts +16 -0
  208. package/dist/weather/RainImpactField.js +89 -0
  209. package/dist/weather/SnowField.d.ts +2 -1
  210. package/dist/weather/SnowField.js +17 -3
  211. package/dist/weather/WeatherLayer.d.ts +11 -2
  212. package/dist/weather/WeatherLayer.js +30 -6
  213. package/dist/weather/index.d.ts +2 -0
  214. package/dist/weather/index.js +2 -0
  215. package/dist/weather/weatherMath.d.ts +2 -0
  216. package/dist/weather/weatherMath.js +8 -0
  217. package/dist/weather/weatherShelter.d.ts +18 -0
  218. package/dist/weather/weatherShelter.js +75 -0
  219. package/dist/weather/weatherUniforms.d.ts +40 -1
  220. package/dist/weather/weatherUniforms.js +29 -9
  221. package/dist/world/AuthoredFlocks.d.ts +22 -0
  222. package/dist/world/AuthoredFlocks.js +87 -0
  223. package/dist/world/StaticObjectModels.d.ts +8 -0
  224. package/dist/world/StaticObjectModels.js +75 -0
  225. package/dist/world/WorldHud.d.ts +14 -6
  226. package/dist/world/WorldHud.js +25 -12
  227. package/dist/world/WorldParticles.d.ts +10 -23
  228. package/dist/world/WorldParticles.js +210 -62
  229. package/dist/world/WorldScene.d.ts +3 -1
  230. package/dist/world/WorldScene.js +41 -26
  231. package/dist/world/staticObjectBatches.d.ts +35 -0
  232. package/dist/world/staticObjectBatches.js +163 -0
  233. package/dist/world/worldBarSamples.d.ts +37 -33
  234. package/dist/world/worldBarSamples.js +117 -75
  235. package/package.json +4 -4
  236. package/skills/ce-handoff/SKILL.md +2 -2
  237. package/skills/game-design/SKILL.md +2 -2
  238. package/skills/jgengine/SKILL.md +28 -6
  239. package/skills/jgengine/api.md +139 -99
  240. package/skills/jgengine/capabilities.md +28 -0
  241. package/skills/jgengine/recipes/minimal-game.md +10 -2
  242. package/skills/jgengine-ui/SKILL.md +16 -17
  243. package/skills/jgengine-ui/api.md +603 -365
  244. package/skills/jgengine-ui/capabilities.md +131 -1
  245. package/skills/jgengine-ui/reference-primitives.md +179 -0
  246. package/skills/jgengine-ui/reference.md +4 -81
  247. package/skills/jgengine-world/SKILL.md +25 -4
  248. package/skills/jgengine-world/api.md +1045 -655
  249. package/skills/jgengine-world/capabilities.md +246 -9
  250. package/skills/jgengine-world/recipes/character-animation.md +62 -0
  251. package/skills/jgengine-world/recipes/controls.md +48 -0
  252. package/skills/jgengine-world/recipes/flight-feel.md +76 -0
  253. package/skills/jgengine-world/recipes/npc-senses.md +55 -0
  254. package/skills/jgengine-world/recipes/vehicle-feel.md +87 -0
  255. package/skills/jgengine-world/reference.md +180 -4
  256. package/skills/jgengine-world/visibility.md +139 -0
  257. package/skills/level-design/SKILL.md +1 -1
  258. package/dist/render/testFixtures/proceduralSpriteAtlas.d.ts +0 -15
  259. package/dist/render/testFixtures/proceduralSpriteAtlas.js +0 -30
  260. package/dist/render/testFixtures/proceduralTileLayer.d.ts +0 -13
  261. package/dist/render/testFixtures/proceduralTileLayer.js +0 -30
@@ -1,21 +1,32 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { lazy, Suspense, useEffect, useState } from "react";
2
3
  import { HudViewportProvider } from "@jgengine/react/hudViewport";
3
4
  import { GameProvider } from "@jgengine/react/provider";
4
- import { DevtoolsOverlay } from "./devtools/DevtoolsOverlay.js";
5
5
  import { DiagnosticOverlay, GameUiErrorBoundary, } from "./diagnostics/RuntimeDiagnostics.js";
6
+ import { DevtoolsRuntime } from "./devtools/DevtoolsRuntime.js";
6
7
  import { GamePhaseStamp } from "./GamePhaseStamp.js";
8
+ import { SettingsPlayControlGate } from "./settings/SettingsRuntime.js";
9
+ const DevtoolsOverlay = lazy(() => import("./devtools/DevtoolsOverlay.js").then((module) => ({ default: module.DevtoolsOverlay })));
7
10
  /** Shared GameUI mount: error boundary → GameProvider → phase stamp → HudViewport. @internal */
8
11
  export function ShellGameUiChrome({ ctx, playable, GameUI, uiScale, orientationGate, onRuntimeError, }) {
9
- return (_jsx(GameUiErrorBoundary, { onRuntimeError: onRuntimeError, children: _jsxs(GameProvider, { context: ctx, children: [_jsx(GamePhaseStamp, {}), _jsx(HudViewportProvider, { platforms: playable.platforms, config: playable.hudFit, userScale: uiScale, children: orientationGate ? null : _jsx(GameUI, {}) })] }) }));
12
+ return (_jsx(GameUiErrorBoundary, { onRuntimeError: onRuntimeError, children: _jsxs(GameProvider, { context: ctx, children: [_jsx(SettingsPlayControlGate, { ctx: ctx }), _jsx(GamePhaseStamp, {}), _jsx(HudViewportProvider, { platforms: playable.platforms, config: playable.hudFit, userScale: uiScale, children: orientationGate ? null : _jsx(GameUI, {}) })] }) }));
10
13
  }
11
14
  /** Devtools + diagnostic overlays shared by HUD and 3D presentation paths. @internal */
12
15
  export function ShellDebugOverlays({ ctx, playable, multiplayer, diagnostics, devtoolsEnabled, devtoolsOpen, hideDiagnostics = false, }) {
13
- return (_jsxs(_Fragment, { children: [devtoolsEnabled ? (_jsx(DevtoolsOverlay, { open: devtoolsOpen, ctx: ctx, playable: playable, multiplayer: multiplayer })) : null, hideDiagnostics ? null : (_jsx(DiagnosticOverlay, { diagnostics: diagnostics, gameName: playable.game.name }))] }));
16
+ const [openedOnce, setOpenedOnce] = useState(false);
17
+ useEffect(() => { if (devtoolsOpen)
18
+ setOpenedOnce(true); }, [devtoolsOpen]);
19
+ return (_jsxs(_Fragment, { children: [devtoolsEnabled ? _jsx(DevtoolsRuntime, { ctx: ctx, playable: playable }) : null, devtoolsEnabled && (devtoolsOpen || openedOnce) ? (_jsx(Suspense, { fallback: devtoolsOpen ? _jsx("div", { role: "status", style: { position: "absolute", right: 16, top: 16, zIndex: 90, color: "#e2e8f0", background: "#0f172a", padding: "10px 14px", borderRadius: 8 }, children: "Loading developer tools\u2026" }) : null, children: _jsx(DevtoolsOverlay, { open: devtoolsOpen, ctx: ctx, playable: playable, multiplayer: multiplayer }) })) : null, hideDiagnostics ? null : (_jsx(DiagnosticOverlay, { diagnostics: diagnostics, gameName: playable.game.name }))] }));
14
20
  }
15
21
  /** F2 chord + gameplay action tracker key handlers shared by both presentation paths. @internal */
16
22
  export function createShellKeyHandlers({ f2HeldRef, tracker, devtoolsEnabled, setDevtoolsOpen, controlsActive, exitPointerLockOnDevtools = false, }) {
17
23
  return {
18
24
  onKeyDown: (event) => {
25
+ if (event.defaultPrevented) {
26
+ f2HeldRef.current = false;
27
+ tracker.reset();
28
+ return;
29
+ }
19
30
  const target = event.target;
20
31
  if (target?.closest?.("input, textarea, select, [contenteditable]:not([contenteditable=false])")) {
21
32
  f2HeldRef.current = false;
@@ -36,10 +47,15 @@ export function createShellKeyHandlers({ f2HeldRef, tracker, devtoolsEnabled, se
36
47
  }
37
48
  return;
38
49
  }
39
- if (event.code === "Tab" || event.code === "Space")
40
- event.preventDefault();
41
- if (controlsActive())
50
+ if (target?.closest?.('button, a[href], [role="button"], [role="link"], [role="dialog"], [role="menu"], [role="listbox"], [role="grid"], [role="gridcell"], [role="toolbar"]')) {
51
+ tracker.reset();
52
+ return;
53
+ }
54
+ if (controlsActive()) {
55
+ if (event.code === "Tab" || event.code === "Space")
56
+ event.preventDefault();
42
57
  tracker.handleDown(event.code);
58
+ }
43
59
  },
44
60
  onKeyUp: (event) => {
45
61
  if (event.code === "F2") {
@@ -1,16 +1,28 @@
1
- import type { Dispatch, MutableRefObject, RefObject, SetStateAction } from "react";
1
+ import { type Dispatch, type MutableRefObject, type RefObject, type SetStateAction } from "react";
2
2
  import type { PointerAxisState } from "@jgengine/core/input/pointerAxis";
3
3
  import type { ActionStateTracker } from "@jgengine/core/input/actionBindings";
4
4
  import type { GameContext } from "@jgengine/core/runtime/gameContext";
5
+ import type { BindingOverrides } from "@jgengine/core/input/bindingOverrides";
6
+ import { type SettingsStore } from "@jgengine/core/settings/settingsModel";
5
7
  import type { TouchScheme } from "@jgengine/core/input/touchScheme";
6
8
  import type { RuntimeDiagnostic } from "./diagnostics/RuntimeDiagnostics.js";
7
9
  import type { ShellMultiplayer } from "./multiplayer.js";
8
10
  import type { PlayableGame } from "./registry.js";
11
+ import type { AudioEngine } from "./audio/audioEngine.js";
12
+ /**
13
+ * Marks a HUD-only page ready for capture (`data-jg-capture="ready"`) unless a capture host already
14
+ * owns the flag. A HUD game has no `<canvas>`, so capture tools that wait for one would time out.
15
+ * @internal
16
+ */
17
+ export declare function markHudCaptureReady(root: {
18
+ dataset: DOMStringMap;
19
+ }): void;
9
20
  /** HUD-only play surface for non-3D shells. @internal */
10
- export declare function ShellHudPresentation({ playable, ctx, multiplayer, tracker, pointerAxisRef, gateRef, wrapperRef, f2HeldRef, yawRef, pitchRef, touchScheme, touchSink, orientationGate, orientationGateEl, coarsePointer, uiScale, diagnostics, devtoolsEnabled, devtoolsOpen, setDevtoolsOpen, reportRuntimeError, trackPointerAxis, deactivatePointerAxis, onPointerResumeAudio, }: {
21
+ export declare function ShellHudPresentation({ playable, ctx, multiplayer, serverIdRef, tracker, pointerAxisRef, gateRef, wrapperRef, f2HeldRef, yawRef, pitchRef, touchScheme, touchSink, orientationGate, orientationGateEl, coarsePointer, uiScale, diagnostics, devtoolsEnabled, devtoolsOpen, setDevtoolsOpen, reportRuntimeError, trackPointerAxis, deactivatePointerAxis, onPointerResumeAudio, settingsStore, bindingOverrides, rebindAction, resetActionBinding, audioEngine, poster, }: {
11
22
  playable: PlayableGame;
12
23
  ctx: GameContext;
13
24
  multiplayer: ShellMultiplayer | null;
25
+ serverIdRef: MutableRefObject<string | null>;
14
26
  tracker: ActionStateTracker<string>;
15
27
  pointerAxisRef: MutableRefObject<PointerAxisState | null>;
16
28
  gateRef: MutableRefObject<boolean>;
@@ -38,4 +50,10 @@ export declare function ShellHudPresentation({ playable, ctx, multiplayer, track
38
50
  }) => void;
39
51
  deactivatePointerAxis: () => void;
40
52
  onPointerResumeAudio: () => void;
53
+ settingsStore: SettingsStore;
54
+ bindingOverrides: BindingOverrides;
55
+ rebindAction: (action: string, code: string) => void;
56
+ resetActionBinding: (action: string) => void;
57
+ audioEngine: AudioEngine;
58
+ poster: boolean;
41
59
  }): import("react").JSX.Element;
@@ -1,12 +1,38 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect } from "react";
2
3
  import { playControlsActive } from "@jgengine/core/game/controlGate";
4
+ import { BUILT_IN_SETTING_CATEGORIES } from "@jgengine/core/settings/settingsModel";
5
+ import { SettingsProvider } from "@jgengine/react/settings";
3
6
  import { GameViewportProvider } from "@jgengine/react/gameViewport";
4
7
  import { HudOnlyDriver } from "./drivers/HudOnlyDriver.js";
5
8
  import { createShellKeyHandlers, ShellDebugOverlays, ShellGameUiChrome } from "./ShellChrome.js";
9
+ import { AudioSettingsBridge } from "./settings/appliedSettings.js";
10
+ import { SettingsRuntime } from "./settings/SettingsRuntime.js";
11
+ import { SettingsChrome } from "./settings/SettingsChrome.js";
6
12
  import { TouchPlaySurface } from "./touch/TouchControlsOverlay.js";
13
+ /**
14
+ * Marks a HUD-only page ready for capture (`data-jg-capture="ready"`) unless a capture host already
15
+ * owns the flag. A HUD game has no `<canvas>`, so capture tools that wait for one would time out.
16
+ * @internal
17
+ */
18
+ export function markHudCaptureReady(root) {
19
+ if (root.dataset.jgCapture === undefined)
20
+ root.dataset.jgCapture = "ready";
21
+ }
7
22
  /** HUD-only play surface for non-3D shells. @internal */
8
- export function ShellHudPresentation({ playable, ctx, multiplayer, tracker, pointerAxisRef, gateRef, wrapperRef, f2HeldRef, yawRef, pitchRef, touchScheme, touchSink, orientationGate, orientationGateEl, coarsePointer, uiScale, diagnostics, devtoolsEnabled, devtoolsOpen, setDevtoolsOpen, reportRuntimeError, trackPointerAxis, deactivatePointerAxis, onPointerResumeAudio, }) {
23
+ export function ShellHudPresentation({ playable, ctx, multiplayer, serverIdRef, tracker, pointerAxisRef, gateRef, wrapperRef, f2HeldRef, yawRef, pitchRef, touchScheme, touchSink, orientationGate, orientationGateEl, coarsePointer, uiScale, diagnostics, devtoolsEnabled, devtoolsOpen, setDevtoolsOpen, reportRuntimeError, trackPointerAxis, deactivatePointerAxis, onPointerResumeAudio, settingsStore, bindingOverrides, rebindAction, resetActionBinding, audioEngine, poster, }) {
9
24
  const GameUI = playable.GameUI;
25
+ useEffect(() => {
26
+ // Two frames so the first GameUI paint has landed before tools capture.
27
+ let second = 0;
28
+ const first = requestAnimationFrame(() => {
29
+ second = requestAnimationFrame(() => markHudCaptureReady(document.documentElement));
30
+ });
31
+ return () => {
32
+ cancelAnimationFrame(first);
33
+ cancelAnimationFrame(second);
34
+ };
35
+ }, []);
10
36
  const keys = createShellKeyHandlers({
11
37
  f2HeldRef,
12
38
  tracker,
@@ -14,9 +40,18 @@ export function ShellHudPresentation({ playable, ctx, multiplayer, tracker, poin
14
40
  setDevtoolsOpen,
15
41
  controlsActive: () => playControlsActive(ctx),
16
42
  });
17
- return (_jsx("div", { ref: wrapperRef, tabIndex: 0, className: "relative h-full w-full bg-neutral-950 outline-none", onKeyDown: keys.onKeyDown, onKeyUp: keys.onKeyUp, onBlur: keys.onBlur, onPointerDown: onPointerResumeAudio, onPointerMove: trackPointerAxis, onPointerLeave: deactivatePointerAxis, onPointerCancel: deactivatePointerAxis, children: _jsxs(GameViewportProvider, { platforms: playable.platforms, children: [_jsx(HudOnlyDriver, { ctx: ctx, playable: playable, tracker: tracker, pointerAxisRef: pointerAxisRef, gateRef: gateRef, onRuntimeError: reportRuntimeError }), !orientationGate &&
18
- coarsePointer &&
19
- touchScheme !== null &&
20
- touchScheme.gestures !== null &&
21
- playControlsActive(ctx) ? (_jsx(TouchPlaySurface, { scheme: touchScheme, sink: touchSink, yawRef: yawRef, pitchRef: pitchRef, maxPitch: 0, onPrimaryTap: () => undefined })) : null, _jsx(ShellGameUiChrome, { ctx: ctx, playable: playable, GameUI: GameUI, uiScale: uiScale, orientationGate: orientationGate, onRuntimeError: reportRuntimeError }), orientationGateEl, _jsx(ShellDebugOverlays, { ctx: ctx, playable: playable, multiplayer: multiplayer, diagnostics: diagnostics, devtoolsEnabled: devtoolsEnabled, devtoolsOpen: devtoolsOpen })] }) }));
43
+ const settingsDisabled = playable.settings === false;
44
+ const settingsConfig = playable.settings === false || playable.settings === undefined ? {} : playable.settings;
45
+ const settingsActions = (settingsConfig.actions ?? []).map((action) => ({
46
+ id: action.id,
47
+ label: action.label,
48
+ kind: action.kind ?? "default",
49
+ description: action.description,
50
+ run: () => action.run(ctx),
51
+ }));
52
+ return (_jsxs(SettingsProvider, { store: settingsStore, children: [_jsx(AudioSettingsBridge, { store: settingsStore, engine: audioEngine, buses: playable.audio?.buses }), _jsx(SettingsRuntime, { variant: settingsConfig.variant ?? "panel", surface: settingsDisabled ? false : settingsConfig.surface ?? false, actions: settingsActions, input: playable.game.input ?? {}, buses: playable.audio?.buses, extra: settingsConfig.extra ?? [], categories: settingsConfig.categories ?? [], hide: settingsDisabled ? BUILT_IN_SETTING_CATEGORIES : settingsConfig.hide ?? [], fovEnabled: false, graphics: playable.graphics, hideBindings: settingsConfig.hideBindings ?? [], touchStyle: coarsePointer && touchScheme !== null && (touchScheme.joystick !== null || touchScheme.buttons.length > 0), overrides: bindingOverrides, rebind: rebindAction, resetBinding: resetActionBinding, children: _jsx("div", { ref: wrapperRef, tabIndex: 0, className: "relative h-full w-full bg-neutral-950 outline-none", onKeyDown: keys.onKeyDown, onKeyUp: keys.onKeyUp, onBlur: keys.onBlur, onPointerDown: onPointerResumeAudio, onPointerMove: trackPointerAxis, onPointerLeave: deactivatePointerAxis, onPointerCancel: deactivatePointerAxis, children: _jsxs(GameViewportProvider, { platforms: playable.platforms, children: [_jsx(HudOnlyDriver, { ctx: ctx, multiplayer: multiplayer, serverIdRef: serverIdRef, playable: playable, tracker: tracker, pointerAxisRef: pointerAxisRef, gateRef: gateRef, onRuntimeError: reportRuntimeError }), !orientationGate &&
53
+ coarsePointer &&
54
+ touchScheme !== null &&
55
+ touchScheme.gestures !== null &&
56
+ playControlsActive(ctx) ? (_jsx(TouchPlaySurface, { scheme: touchScheme, sink: touchSink, yawRef: yawRef, pitchRef: pitchRef, maxPitch: 0, onPrimaryTap: () => undefined })) : null, _jsx(ShellGameUiChrome, { ctx: ctx, playable: playable, GameUI: GameUI, uiScale: uiScale, orientationGate: orientationGate, onRuntimeError: reportRuntimeError }), orientationGateEl, _jsx(ShellDebugOverlays, { ctx: ctx, playable: playable, multiplayer: multiplayer, diagnostics: diagnostics, devtoolsEnabled: devtoolsEnabled, devtoolsOpen: devtoolsOpen }), poster ? null : _jsx(SettingsChrome, {})] }) }) })] }));
22
57
  }
@@ -2,15 +2,19 @@ import { useFrame, useThree } from "@react-three/fiber";
2
2
  import { useEffect, useRef } from "react";
3
3
  import { Quaternion, Vector3 } from "three";
4
4
  import { useGameContext } from "@jgengine/react/provider";
5
+ import { createListenerVelocityTrack, stepListenerVelocity } from "./loopParams.js";
5
6
  export function AudioListener({ engine }) {
6
7
  const camera = useThree((state) => state.camera);
7
- useFrame(() => {
8
+ const trackRef = useRef(createListenerVelocityTrack());
9
+ useFrame((_, dt) => {
8
10
  const direction = camera.getWorldDirection(new Vector3());
9
11
  const up = camera.up.clone().applyQuaternion(camera.getWorldQuaternion(new Quaternion()));
12
+ const position = { x: camera.position.x, y: camera.position.y, z: camera.position.z };
10
13
  engine.setListenerPose({
11
- position: { x: camera.position.x, y: camera.position.y, z: camera.position.z },
14
+ position,
12
15
  forward: { x: direction.x, y: direction.y, z: direction.z },
13
16
  up: { x: up.x, y: up.y, z: up.z },
17
+ velocity: stepListenerVelocity(trackRef.current, position, dt),
14
18
  });
15
19
  });
16
20
  return null;
@@ -1,5 +1,6 @@
1
1
  import { type AudioBusDef, type SoundDef } from "@jgengine/core/audio/audioFalloff";
2
2
  import type { MusicTheme } from "@jgengine/core/audio/music";
3
+ import { type VoiceAllocator, type VoiceAllocatorConfig } from "@jgengine/core/audio/voiceAllocator";
3
4
  import { type CrossfadeOptions } from "./musicDirector.js";
4
5
  export interface Vec3 {
5
6
  x: number;
@@ -11,9 +12,17 @@ export interface ListenerPose {
11
12
  position: Vec3;
12
13
  forward: Vec3;
13
14
  up: Vec3;
15
+ /** World velocity of the listener, for doppler on loops whose sound declares `doppler`. Default zero. */
16
+ velocity?: Vec3;
14
17
  }
15
18
  export interface AudioSceneConfig {
16
19
  sounds?: Record<string, SoundDef>;
20
+ /** SFX playback/patch slots, including pending loads; individual synth oscillators share one slot. Default 64; zero disables SFX. */
21
+ maxVoices?: number;
22
+ /** Budget overflow policy. Default "steal-lowest"; lower priorities never steal higher ones. */
23
+ voiceOverflow?: VoiceAllocatorConfig["overflow"];
24
+ /** Dedicated caller-owned allocator for inspectable reservations and injected storage. Music has its own lifecycle. */
25
+ voiceAllocator?: VoiceAllocator;
17
26
  buses?: Record<string, AudioBusDef>;
18
27
  /** Procedural music themes, crossfaded by {@link AudioEngine.playMusic}. Mixed through the `musicBus` (default "music") so the settings volume applies. */
19
28
  music?: Record<string, MusicTheme>;
@@ -21,15 +30,26 @@ export interface AudioSceneConfig {
21
30
  musicBus?: string;
22
31
  }
23
32
  export interface AudioEmitterHandle {
33
+ /** Whether playback is reserved or playing; false after stop, stealing, failed load, or natural end. Optional for legacy hosts. */
34
+ isPlaying?(): boolean;
24
35
  setPosition(position: Vec3): void;
25
36
  /** Live pitch of a retained loop: `rate` multiplies the authored playback rate (1 = authored), clamped to 0.25–4 and ramped ~20 ms to avoid zipper noise (#1051). */
26
37
  setRate(rate: number): void;
27
38
  /** Live volume of a retained loop: `gain` scales the source (0–1), clamped and ramped ~20 ms (#1051). */
28
39
  setGain(gain: number): void;
40
+ /** Live lowpass cutoff of a retained loop in Hz, clamped and ramped like rate. */
41
+ setLowpass(hz: number): void;
42
+ /** Live highpass cutoff of a retained loop in Hz, clamped and ramped like rate. */
43
+ setHighpass(hz: number): void;
44
+ /** Emitter world velocity for doppler; ignored unless the sound declares `doppler`. */
45
+ setVelocity(velocity: Vec3): void;
29
46
  stop(): void;
30
47
  }
31
48
  export interface AudioEngine {
49
+ /** Full listener orientation, or a legacy position using forward -Z and up +Y. */
32
50
  setListenerPose(pose: ListenerPose | Vec3): void;
51
+ /** Retune the SFX budget and optional admission policy, stopping the lowest-priority oldest voices first. */
52
+ setVoiceLimit(maxTotal: number, overflow?: VoiceAllocatorConfig["overflow"]): void;
33
53
  playOneShot(soundId: string, position?: Vec3): void;
34
54
  playLoop(soundId: string, position?: Vec3): AudioEmitterHandle | null;
35
55
  /** Crossfade the procedural soundtrack to `themeId` (null fades out). No-op when no `music` catalog is configured. */
@@ -39,4 +59,8 @@ export interface AudioEngine {
39
59
  resume(): void;
40
60
  dispose(): void;
41
61
  }
62
+ /**
63
+ * Game-owned sound catalog playback with bounded SFX reservations, spatial panning, and shared buses.
64
+ * @capability bounded-spatial-audio play positional or flat cues with listener orientation, voice budgets, and per-source cleanup
65
+ */
42
66
  export declare function createAudioEngine(config?: AudioSceneConfig): AudioEngine;