@jgengine/shell 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/CHANGELOG.md +39 -2
  2. package/LICENSE +203 -661
  3. package/README.md +1 -1
  4. package/dist/CombatPresentation.d.ts +5 -0
  5. package/dist/CombatPresentation.js +8 -0
  6. package/dist/GameHost.d.ts +25 -1
  7. package/dist/GameHost.js +60 -4
  8. package/dist/GamePhaseStamp.d.ts +1 -0
  9. package/dist/GamePhaseStamp.js +9 -0
  10. package/dist/GamePlayer.d.ts +5 -0
  11. package/dist/GamePlayer.js +5 -0
  12. package/dist/GamePlayerShell.d.ts +5 -12
  13. package/dist/GamePlayerShell.js +67 -1332
  14. package/dist/GameUiPreview.js +3 -2
  15. package/dist/Shell3dPresentation.d.ts +63 -0
  16. package/dist/Shell3dPresentation.js +302 -0
  17. package/dist/ShellChrome.d.ts +51 -0
  18. package/dist/ShellChrome.js +51 -0
  19. package/dist/ShellHudPresentation.d.ts +41 -0
  20. package/dist/ShellHudPresentation.js +22 -0
  21. package/dist/audio/audioEngine.d.ts +4 -0
  22. package/dist/audio/audioEngine.js +128 -21
  23. package/dist/audio/audioWire.d.ts +40 -0
  24. package/dist/audio/audioWire.js +62 -0
  25. package/dist/audio/loopParams.d.ts +20 -0
  26. package/dist/audio/loopParams.js +28 -0
  27. package/dist/behaviour.d.ts +2 -1
  28. package/dist/behaviour.js +2 -1
  29. package/dist/behaviourAttach.d.ts +1 -0
  30. package/dist/behaviourAttach.js +1 -0
  31. package/dist/behaviourDriver.d.ts +1 -0
  32. package/dist/behaviourDriver.js +1 -0
  33. package/dist/boundActionDispatch.d.ts +13 -0
  34. package/dist/boundActionDispatch.js +37 -0
  35. package/dist/camera/GameCameraRig.d.ts +5 -2
  36. package/dist/camera/GameCameraRig.js +8 -3
  37. package/dist/camera/GameFirstPersonCamera.d.ts +9 -2
  38. package/dist/camera/GameFirstPersonCamera.js +8 -3
  39. package/dist/camera/GameInspectionCamera.d.ts +1 -1
  40. package/dist/camera/GameInspectionCamera.js +2 -2
  41. package/dist/camera/GameOrbitCamera.js +24 -9
  42. package/dist/camera/cameraBlendMath.d.ts +3 -0
  43. package/dist/camera/cameraBlendMath.js +3 -0
  44. package/dist/camera/cameraRigs.d.ts +3 -1
  45. package/dist/camera/cameraRigs.js +21 -3
  46. package/dist/camera/fovPreference.d.ts +6 -1
  47. package/dist/camera/fovPreference.js +6 -1
  48. package/dist/camera/index.d.ts +4 -7
  49. package/dist/camera/index.js +4 -7
  50. package/dist/camera/inspectionCameraMath.d.ts +7 -2
  51. package/dist/camera/inspectionCameraMath.js +7 -2
  52. package/dist/camera/orbitCameraMath.d.ts +36 -6
  53. package/dist/camera/orbitCameraMath.js +56 -6
  54. package/dist/camera/rigMath.d.ts +114 -24
  55. package/dist/camera/rigMath.js +129 -24
  56. package/dist/camera/rigResolve.d.ts +4 -2
  57. package/dist/camera/rigResolve.js +4 -2
  58. package/dist/camera/shakeChannel.d.ts +6 -2
  59. package/dist/camera/shakeChannel.js +6 -2
  60. package/dist/camera/shakeChannelMath.d.ts +1 -0
  61. package/dist/camera/shakeChannelMath.js +1 -0
  62. package/dist/commandSink.d.ts +8 -3
  63. package/dist/commandSink.js +8 -3
  64. package/dist/defineGame.d.ts +15 -1
  65. package/dist/defineGame.js +50 -9
  66. package/dist/devtools/ColPanel.d.ts +1 -0
  67. package/dist/devtools/ColPanel.js +14 -0
  68. package/dist/devtools/DevtoolsOverlay.d.ts +11 -7
  69. package/dist/devtools/DevtoolsOverlay.js +47 -462
  70. package/dist/devtools/KeysPanel.d.ts +4 -0
  71. package/dist/devtools/KeysPanel.js +31 -0
  72. package/dist/devtools/LogsPanel.d.ts +1 -0
  73. package/dist/devtools/LogsPanel.js +12 -0
  74. package/dist/devtools/NetPanel.d.ts +4 -0
  75. package/dist/devtools/NetPanel.js +10 -0
  76. package/dist/devtools/PerfPanel.d.ts +4 -0
  77. package/dist/devtools/PerfPanel.js +53 -0
  78. package/dist/devtools/TunePanel.d.ts +3 -0
  79. package/dist/devtools/TunePanel.js +309 -0
  80. package/dist/devtools/agentBridge.d.ts +41 -0
  81. package/dist/devtools/agentBridge.js +266 -0
  82. package/dist/devtools/collisionDebug.d.ts +6 -0
  83. package/dist/devtools/collisionDebug.js +6 -0
  84. package/dist/devtools/collisionDebugMath.d.ts +8 -2
  85. package/dist/devtools/collisionDebugMath.js +24 -5
  86. package/dist/devtools/devtoolsOverrides.d.ts +6 -0
  87. package/dist/devtools/devtoolsOverrides.js +48 -0
  88. package/dist/devtools/panelAtoms.d.ts +9 -0
  89. package/dist/devtools/panelAtoms.js +10 -0
  90. package/dist/devtools/perfDiagnose.d.ts +2 -0
  91. package/dist/devtools/perfDiagnose.js +43 -0
  92. package/dist/diagnostics/RuntimeDiagnostics.d.ts +36 -0
  93. package/dist/diagnostics/RuntimeDiagnostics.js +80 -0
  94. package/dist/drivers/FrameDriver.d.ts +37 -0
  95. package/dist/drivers/FrameDriver.js +232 -0
  96. package/dist/drivers/HudOnlyDriver.d.ts +16 -0
  97. package/dist/drivers/HudOnlyDriver.js +60 -0
  98. package/dist/environment/Daylight.d.ts +23 -7
  99. package/dist/environment/Daylight.js +106 -13
  100. package/dist/environment/EnvironmentScene.js +44 -9
  101. package/dist/environment/VolumetricClouds.d.ts +17 -0
  102. package/dist/environment/VolumetricClouds.js +202 -0
  103. package/dist/environment/daylightCycle.d.ts +3 -1
  104. package/dist/environment/daylightCycle.js +3 -1
  105. package/dist/environment/groundPadMath.d.ts +3 -0
  106. package/dist/environment/groundPadMath.js +3 -0
  107. package/dist/environment/index.d.ts +1 -0
  108. package/dist/environment/index.js +1 -0
  109. package/dist/environment/skyLightingPolicy.d.ts +2 -0
  110. package/dist/environment/skyLightingPolicy.js +2 -0
  111. package/dist/environment/terrainGroundColor.d.ts +9 -0
  112. package/dist/environment/terrainGroundColor.js +22 -0
  113. package/dist/gameKit.d.ts +27 -0
  114. package/dist/gameKit.js +29 -0
  115. package/dist/hotbarActions.d.ts +26 -0
  116. package/dist/hotbarActions.js +27 -0
  117. package/dist/inputSink.d.ts +16 -4
  118. package/dist/inputSink.js +57 -7
  119. package/dist/map/MapMarkerBeacons.d.ts +3 -2
  120. package/dist/materialOverride.d.ts +10 -0
  121. package/dist/materialOverride.js +15 -3
  122. package/dist/multiplayer.js +8 -2
  123. package/dist/postfx/PostProcessing.d.ts +9 -1
  124. package/dist/postfx/PostProcessing.js +56 -11
  125. package/dist/postfx/gradeShader.js +9 -1
  126. package/dist/presentationResolve.d.ts +35 -0
  127. package/dist/presentationResolve.js +51 -0
  128. package/dist/registry.d.ts +4 -2
  129. package/dist/render/EnvironmentLighting.d.ts +10 -0
  130. package/dist/render/EnvironmentLighting.js +34 -0
  131. package/dist/render/SceneLighting.d.ts +7 -0
  132. package/dist/render/SceneLighting.js +18 -0
  133. package/dist/render/SceneModels.d.ts +14 -0
  134. package/dist/render/SceneModels.js +337 -0
  135. package/dist/render/assetBase.d.ts +17 -0
  136. package/dist/render/assetBase.js +35 -0
  137. package/dist/render/defaultSurface.d.ts +29 -0
  138. package/dist/render/defaultSurface.js +101 -0
  139. package/dist/render/modelLoad.d.ts +24 -0
  140. package/dist/render/modelLoad.js +76 -0
  141. package/dist/render/modelRender.d.ts +11 -0
  142. package/dist/render/modelRender.js +15 -0
  143. package/dist/render/resolveModel.d.ts +54 -3
  144. package/dist/render/resolveModel.js +116 -4
  145. package/dist/render/useEntityRenderCues.d.ts +13 -0
  146. package/dist/render/useEntityRenderCues.js +62 -0
  147. package/dist/scatter/InstancedScatter.d.ts +24 -0
  148. package/dist/scatter/InstancedScatter.js +135 -0
  149. package/dist/scatter/scatterModels.d.ts +25 -0
  150. package/dist/scatter/scatterModels.js +61 -0
  151. package/dist/scatter/scatterProxies.d.ts +6 -0
  152. package/dist/scatter/scatterProxies.js +118 -0
  153. package/dist/scatter.d.ts +1 -0
  154. package/dist/scatter.js +1 -0
  155. package/dist/scene/AuthoredScene.d.ts +84 -0
  156. package/dist/scene/AuthoredScene.js +178 -0
  157. package/dist/scene/CityRenderer.d.ts +2 -0
  158. package/dist/scene/CityRenderer.js +1031 -0
  159. package/dist/scene/GeneratedAssetRenderer.d.ts +14 -0
  160. package/dist/scene/GeneratedAssetRenderer.js +59 -0
  161. package/dist/scene/GrassFieldRenderer.d.ts +2 -0
  162. package/dist/scene/GrassFieldRenderer.js +73 -0
  163. package/dist/scene/PoleLineRenderer.d.ts +2 -0
  164. package/dist/scene/PoleLineRenderer.js +287 -0
  165. package/dist/scene/SoilPatchRenderer.d.ts +2 -0
  166. package/dist/scene/SoilPatchRenderer.js +84 -0
  167. package/dist/scene/StudioStage.d.ts +37 -0
  168. package/dist/scene/StudioStage.js +42 -0
  169. package/dist/scene/WaterRenderer.d.ts +2 -0
  170. package/dist/scene/WaterRenderer.js +61 -0
  171. package/dist/scene/builtinSceneKindRenderers.d.ts +2 -0
  172. package/dist/scene/builtinSceneKindRenderers.js +26 -0
  173. package/dist/scene/cityWindowMaterial.d.ts +16 -0
  174. package/dist/scene/cityWindowMaterial.js +81 -0
  175. package/dist/scene/sceneKindRenderers.d.ts +30 -0
  176. package/dist/scene/sceneKindRenderers.js +17 -0
  177. package/dist/scene.d.ts +1 -0
  178. package/dist/scene.js +1 -0
  179. package/dist/settings/appliedSettings.d.ts +7 -1
  180. package/dist/settings/appliedSettings.js +13 -2
  181. package/dist/settings/settingsController.d.ts +2 -0
  182. package/dist/settings/settingsController.js +22 -1
  183. package/dist/shellConstants.d.ts +4 -0
  184. package/dist/shellConstants.js +4 -0
  185. package/dist/shellMovement.d.ts +8 -0
  186. package/dist/shellMovement.js +11 -0
  187. package/dist/shellPointer.d.ts +20 -0
  188. package/dist/shellPointer.js +28 -0
  189. package/dist/structures/PlacementGhost.d.ts +4 -0
  190. package/dist/structures/PlacementGhost.js +4 -0
  191. package/dist/structures/TransformGizmo.d.ts +53 -0
  192. package/dist/structures/TransformGizmo.js +59 -0
  193. package/dist/structures/index.d.ts +1 -0
  194. package/dist/structures/index.js +1 -0
  195. package/dist/terrain/GrassField.d.ts +11 -3
  196. package/dist/terrain/GrassField.js +16 -6
  197. package/dist/terrain/TerraformBrushCursor.js +3 -0
  198. package/dist/terrain/grassBudget.d.ts +11 -2
  199. package/dist/terrain/grassBudget.js +20 -4
  200. package/dist/terrain/grassGeometry.d.ts +35 -0
  201. package/dist/terrain/grassGeometry.js +85 -20
  202. package/dist/terrain/grassMaterial.d.ts +20 -0
  203. package/dist/terrain/grassMaterial.js +68 -13
  204. package/dist/terrain/heightfieldGeometry.d.ts +34 -0
  205. package/dist/terrain/heightfieldGeometry.js +109 -0
  206. package/dist/terrain/index.d.ts +1 -0
  207. package/dist/terrain/index.js +1 -0
  208. package/dist/terrain/random.d.ts +3 -0
  209. package/dist/terrain/random.js +3 -0
  210. package/dist/terrain/soilPatchMaterial.d.ts +23 -0
  211. package/dist/terrain/soilPatchMaterial.js +147 -0
  212. package/dist/terrain/terrainDetailMaterial.d.ts +19 -2
  213. package/dist/terrain/terrainDetailMaterial.js +87 -7
  214. package/dist/terrain/terrainMath.d.ts +8 -1
  215. package/dist/terrain/terrainMath.js +8 -1
  216. package/dist/touch/TouchControlsOverlay.d.ts +9 -5
  217. package/dist/touch/TouchControlsOverlay.js +201 -22
  218. package/dist/useShellMultiplayerSync.d.ts +9 -0
  219. package/dist/useShellMultiplayerSync.js +98 -0
  220. package/dist/visibility/CullingProvider.js +1 -1
  221. package/dist/water/Ocean.d.ts +7 -1
  222. package/dist/water/Ocean.js +16 -3
  223. package/dist/water/OceanConfig.d.ts +11 -0
  224. package/dist/water/OceanConfig.js +8 -0
  225. package/dist/water/OceanMaterial.d.ts +12 -0
  226. package/dist/water/OceanMaterial.js +9 -0
  227. package/dist/water/OceanShader.d.ts +2 -2
  228. package/dist/water/OceanShader.js +78 -10
  229. package/dist/weather/fireSpreadPose.d.ts +1 -0
  230. package/dist/weather/fireSpreadPose.js +1 -0
  231. package/dist/weather/weatherGeometry.d.ts +1 -0
  232. package/dist/weather/weatherGeometry.js +1 -0
  233. package/dist/weather/weatherMath.d.ts +3 -0
  234. package/dist/weather/weatherMath.js +3 -0
  235. package/dist/weather/weatherUniforms.d.ts +3 -0
  236. package/dist/weather/weatherUniforms.js +3 -0
  237. package/dist/world/PlaceScene.d.ts +11 -0
  238. package/dist/world/PlaceScene.js +27 -0
  239. package/dist/world/RetainedVfx.d.ts +10 -0
  240. package/dist/world/RetainedVfx.js +136 -0
  241. package/dist/world/WorldHud.d.ts +49 -2
  242. package/dist/world/WorldHud.js +87 -2
  243. package/dist/world/WorldScene.d.ts +21 -0
  244. package/dist/world/WorldScene.js +186 -0
  245. package/dist/world/WorldVfx.d.ts +9 -0
  246. package/dist/world/WorldVfx.js +184 -0
  247. package/dist/world/entityPose.d.ts +2 -0
  248. package/dist/world/entityPose.js +2 -0
  249. package/dist/world/floatTextStyle.d.ts +1 -0
  250. package/dist/world/floatTextStyle.js +1 -0
  251. package/dist/world/telegraphPulse.d.ts +1 -0
  252. package/dist/world/telegraphPulse.js +1 -0
  253. package/dist/world/worldBarSamples.d.ts +25 -0
  254. package/dist/world/worldBarSamples.js +38 -0
  255. package/dist/worldSky.d.ts +11 -0
  256. package/dist/worldSky.js +21 -0
  257. package/dist/worldSync.d.ts +2 -1
  258. package/dist/worldSync.js +2 -1
  259. package/package.json +6 -6
  260. package/skills/game-design/SKILL.md +28 -0
  261. package/skills/game-design/agents/openai.yaml +4 -0
  262. package/skills/game-design/references/application-playbook.md +170 -0
  263. package/skills/game-design/references/field-guide.md +288 -0
  264. package/skills/jgengine/SKILL.md +54 -0
  265. package/skills/jgengine/api.md +527 -0
  266. package/skills/jgengine/capabilities.md +131 -0
  267. package/skills/jgengine/recipes/minimal-game.md +88 -0
  268. package/skills/jgengine-ui/SKILL.md +41 -0
  269. package/skills/jgengine-ui/api.md +2007 -0
  270. package/skills/jgengine-ui/capabilities.md +211 -0
  271. package/skills/jgengine-ui/recipes/portable-minimap.md +150 -0
  272. package/skills/jgengine-ui/reference.md +513 -0
  273. package/skills/jgengine-ui/references/visual-scorecard.md +73 -0
  274. package/skills/jgengine-world/SKILL.md +51 -0
  275. package/skills/jgengine-world/api.md +3056 -0
  276. package/skills/jgengine-world/capabilities.md +476 -0
  277. package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
  278. package/skills/jgengine-world/reference.md +261 -0
  279. package/skills/level-design/SKILL.md +54 -0
  280. package/skills/level-design/agents/openai.yaml +4 -0
  281. package/skills/level-design/references/application-playbook.md +192 -0
  282. package/skills/level-design/references/field-guide.md +337 -0
  283. package/skills/level-design/references/live-editor-loop.md +179 -0
  284. package/dist/cartridge.d.ts +0 -145
  285. package/dist/cartridge.js +0 -246
  286. package/llms.txt +0 -2761
@@ -5,7 +5,7 @@ import { useEffect, useRef } from "react";
5
5
  import { MOUSE, PerspectiveCamera, Raycaster, Vector3 } from "three";
6
6
  import { useGameContext } from "@jgengine/react/provider";
7
7
  import { usePlayer } from "@jgengine/react/hooks";
8
- import { cameraLookPitch, ORBIT_CAMERA_FRAME_PRIORITY, orbitFollowStep, orbitYawFromCamera, resolveFollowTargetFromPosition, resolveOrbitCameraConfig, seedOrbitFollowState, } from "./orbitCameraMath.js";
8
+ import { cameraLookPitch, compensatedFov, ORBIT_CAMERA_FRAME_PRIORITY, orbitFollowStep, orbitYawFromCamera, resolveFollowTargetFromPosition, resolveOrbitCameraConfig, seedOrbitFollowState, } from "./orbitCameraMath.js";
9
9
  import { usePlayerFov } from "./PlayerFov.js";
10
10
  export function GameOrbitCamera({ yawRef, pitchRef, config: configPatch, followEntityId, resolveFollowTarget, onDragChange, onCameraFollow, pointerControls = false, }) {
11
11
  const config = resolveOrbitCameraConfig(configPatch);
@@ -20,6 +20,12 @@ export function GameOrbitCamera({ yawRef, pitchRef, config: configPatch, followE
20
20
  const raycasterRef = useRef(new Raycaster());
21
21
  const collisionDirRef = useRef(new Vector3());
22
22
  const collisionOriginRef = useRef(new Vector3());
23
+ const followPayloadRef = useRef({
24
+ entityId: "",
25
+ target: { x: 0, y: 0, z: 0 },
26
+ camera: { x: 0, y: 0, z: 0 },
27
+ distance: 0,
28
+ });
23
29
  const followId = followEntityId ?? userId;
24
30
  useEffect(() => {
25
31
  const entity = ctx.scene.entity.get(followId);
@@ -60,12 +66,15 @@ export function GameOrbitCamera({ yawRef, pitchRef, config: configPatch, followE
60
66
  camera.position.set(stepped.camera.x, stepped.camera.y, stepped.camera.z);
61
67
  }
62
68
  controls.update();
69
+ let desiredDistance = 0;
70
+ let pulledDistance = 0;
63
71
  if (config.collision.enabled) {
64
72
  const t = stepped.target;
65
73
  const dx = camera.position.x - t.x;
66
74
  const dy = camera.position.y - t.y;
67
75
  const dz = camera.position.z - t.z;
68
76
  const dist = Math.hypot(dx, dy, dz);
77
+ desiredDistance = dist;
69
78
  if (dist > config.collision.minTargetDistance) {
70
79
  const dir = collisionDirRef.current.set(dx / dist, dy / dist, dz / dist);
71
80
  const ray = raycasterRef.current;
@@ -87,6 +96,7 @@ export function GameOrbitCamera({ yawRef, pitchRef, config: configPatch, followE
87
96
  if (blocked > 0) {
88
97
  const pulled = Math.max(config.collision.minTargetDistance, blocked - config.collision.padding);
89
98
  camera.position.set(t.x + dir.x * pulled, t.y + dir.y * pulled, t.z + dir.z * pulled);
99
+ pulledDistance = pulled;
90
100
  }
91
101
  }
92
102
  }
@@ -102,17 +112,22 @@ export function GameOrbitCamera({ yawRef, pitchRef, config: configPatch, followE
102
112
  }
103
113
  if (camera.isPerspectiveCamera === true) {
104
114
  const perspective = camera;
105
- if (Math.abs(perspective.fov - playerFov.fov) > 0.001) {
106
- perspective.fov = playerFov.fov;
115
+ const targetFov = pulledDistance > 0 && pulledDistance < desiredDistance
116
+ ? compensatedFov(playerFov.fov, desiredDistance, pulledDistance)
117
+ : playerFov.fov;
118
+ if (Math.abs(perspective.fov - targetFov) > 0.001) {
119
+ perspective.fov = targetFov;
107
120
  perspective.updateProjectionMatrix();
108
121
  }
109
122
  }
110
- onCameraFollow?.({
111
- entityId: followId,
112
- target: stepped.target,
113
- camera: runtimeRef.current.camera,
114
- distance: runtimeRef.current.lockedDistance ?? stepped.distance,
115
- });
123
+ if (onCameraFollow !== undefined) {
124
+ const payload = followPayloadRef.current;
125
+ payload.entityId = followId;
126
+ payload.target = stepped.target;
127
+ payload.camera = runtimeRef.current.camera;
128
+ payload.distance = runtimeRef.current.lockedDistance ?? stepped.distance;
129
+ onCameraFollow(payload);
130
+ }
116
131
  }, ORBIT_CAMERA_FRAME_PRIORITY);
117
132
  return (_jsx(OrbitControls, { ref: controlsRef, enableDamping: true, dampingFactor: config.dampingFactor, rotateSpeed: config.rotateSpeed, zoomSpeed: config.zoomSpeed, enablePan: false, enableRotate: true, enableZoom: true, minDistance: config.minDistance, maxDistance: config.maxDistance, maxPolarAngle: config.maxPolarAngle, minPolarAngle: config.minPolarAngle, screenSpacePanning: false, mouseButtons: pointerControls
118
133
  ? { LEFT: undefined, MIDDLE: MOUSE.ROTATE, RIGHT: undefined }
@@ -8,6 +8,9 @@ export interface CameraBlendScratch {
8
8
  elapsed: number;
9
9
  duration: number;
10
10
  }
11
+ /** @internal */
11
12
  export declare function createCameraBlendScratch(Vector3Ctor: new () => Vector3, QuaternionCtor: new () => Quaternion): CameraBlendScratch;
13
+ /** @internal */
12
14
  export declare function captureCameraBlendFrom(scratch: CameraBlendScratch, position: Vector3, quaternion: Quaternion, fov: number, duration: number): void;
15
+ /** @internal */
13
16
  export declare function applyCameraBlendStep(scratch: CameraBlendScratch, camera: Camera, targetFov: number, dt: number): boolean;
@@ -1,4 +1,5 @@
1
1
  import { smoothstep } from "./rigMath.js";
2
+ /** @internal */
2
3
  export function createCameraBlendScratch(Vector3Ctor, QuaternionCtor) {
3
4
  return {
4
5
  fromPos: new Vector3Ctor(),
@@ -10,6 +11,7 @@ export function createCameraBlendScratch(Vector3Ctor, QuaternionCtor) {
10
11
  duration: 0,
11
12
  };
12
13
  }
14
+ /** @internal */
13
15
  export function captureCameraBlendFrom(scratch, position, quaternion, fov, duration) {
14
16
  scratch.fromPos.copy(position);
15
17
  scratch.fromQuat.copy(quaternion);
@@ -20,6 +22,7 @@ export function captureCameraBlendFrom(scratch, position, quaternion, fov, durat
20
22
  function isPerspective(camera) {
21
23
  return camera.isPerspectiveCamera === true;
22
24
  }
25
+ /** @internal */
23
26
  export function applyCameraBlendStep(scratch, camera, targetFov, dt) {
24
27
  scratch.elapsed += dt;
25
28
  const t = scratch.duration <= 0 ? 1 : Math.min(scratch.elapsed / scratch.duration, 1);
@@ -26,4 +26,6 @@ export declare function ChaseRig(props: RigProps): null;
26
26
  * kill-cam rig. Distinct from every other rig, which drives from mouse/keys.
27
27
  */
28
28
  export declare function ObserverRig(props: RigProps): null;
29
- export declare function CinematicRig(props: RigProps): null;
29
+ export declare function CinematicRig(props: RigProps & {
30
+ onComplete?: () => void;
31
+ }): null;
@@ -4,7 +4,7 @@ import { PerspectiveCamera, Quaternion, Vector3 } from "three";
4
4
  import { usePlayer } from "@jgengine/react/hooks";
5
5
  import { useGameContext } from "@jgengine/react/provider";
6
6
  import { ORBIT_CAMERA_FRAME_PRIORITY } from "./orbitCameraMath.js";
7
- import { bankRollStep, chaseDesiredPosition, chaseLookAt, cinematicSample, clamp, leadFollowPoint, lockOnPose, observerPose, resolveChase, resolveObserver, resolveShoulder, resolveSideScroll, resolveSideScrollPose, resolveTopDown, seatPose, shoulderPose, sideScrollFollowBlend, smoothYaw, speedToFov, springArmStep, topDownPose, } from "./rigMath.js";
7
+ import { bankRollStep, chaseDesiredPosition, chaseLookAt, cinematicSample, clamp, leadFollowPoint, lockOnPose, observerPose, resolveChase, resolveObserver, resolveShoulder, resolveSideScroll, resolveSideScrollPose, resolveTopDown, seatPose, shoulderPose, sideScrollFollowBlend, smoothYaw, speedToFov, springArmStep, topDownPose, velocityYawTarget, } from "./rigMath.js";
8
8
  import { usePlayerFov } from "./PlayerFov.js";
9
9
  import { useCameraShake } from "./shakeChannel.js";
10
10
  import { applyCameraBlendStep, captureCameraBlendFrom, createCameraBlendScratch, } from "./cameraBlendMath.js";
@@ -424,6 +424,7 @@ export function ChaseRig(props) {
424
424
  const posRef = useRef(null);
425
425
  const lastFollowRef = useRef(null);
426
426
  const lastYawRef = useRef(null);
427
+ const anchorYawRef = useRef(null);
427
428
  const rollRef = useRef(0);
428
429
  const view = config?.view ?? "chase";
429
430
  useEffect(beginTransition, [view]);
@@ -454,15 +455,24 @@ export function ChaseRig(props) {
454
455
  commit(pose, dt);
455
456
  return;
456
457
  }
458
+ let anchorYaw = yaw;
459
+ if (resolved.velocityYawBlend > 0) {
460
+ const velocity = dt > 0
461
+ ? { x: (follow.x - last.x) / dt, y: 0, z: (follow.z - last.z) / dt }
462
+ : { x: 0, y: 0, z: 0 };
463
+ const targetYaw = velocityYawTarget(yaw, velocity, resolved);
464
+ anchorYaw = smoothYaw(anchorYawRef.current ?? targetYaw, targetYaw, resolved.velocityYawResponse, dt);
465
+ anchorYawRef.current = anchorYaw;
466
+ }
457
467
  const led = leadFollowPoint(follow, last, dt, resolved);
458
- const desired = chaseDesiredPosition(led, yaw, resolved);
468
+ const desired = chaseDesiredPosition(led, anchorYaw, resolved);
459
469
  const prev = posRef.current ?? desired;
460
470
  const smoothed = springArmStep(prev, desired, resolved.springDamping, dt);
461
471
  posRef.current = smoothed;
462
472
  if (resolved.shakePerSpeed > 0 && speed > 0) {
463
473
  shake.shake(Math.min(resolved.shakePerSpeed * speed * dt, 0.1));
464
474
  }
465
- const pose = { position: smoothed, lookAt: chaseLookAt(led, yaw, resolved), fov };
475
+ const pose = { position: smoothed, lookAt: chaseLookAt(led, anchorYaw, resolved), fov };
466
476
  commit(pose, dt);
467
477
  if (rollRef.current !== 0)
468
478
  camera.rotateZ(rollRef.current);
@@ -510,8 +520,12 @@ export function CinematicRig(props) {
510
520
  const cinematic = props.config?.cinematic;
511
521
  const { camera, commit, beginTransition } = useCameraCommit(props, followId);
512
522
  const elapsedRef = useRef(0);
523
+ const completedRef = useRef(false);
524
+ const onCompleteRef = useRef(props.onComplete);
525
+ onCompleteRef.current = props.onComplete;
513
526
  useEffect(() => {
514
527
  elapsedRef.current = 0;
528
+ completedRef.current = false;
515
529
  beginTransition();
516
530
  }, [cinematic]);
517
531
  useFrame((_, dt) => {
@@ -520,6 +534,10 @@ export function CinematicRig(props) {
520
534
  elapsedRef.current += dt;
521
535
  const sample = cinematicSample(cinematic.keyframes, elapsedRef.current, cinematic.loop ?? false, currentFov(camera));
522
536
  commit(sample.pose, dt);
537
+ if (sample.done && !completedRef.current) {
538
+ completedRef.current = true;
539
+ onCompleteRef.current?.();
540
+ }
523
541
  }, CAMERA_RIG_FRAME_PRIORITY);
524
542
  return null;
525
543
  }
@@ -7,13 +7,17 @@ export interface PlayerFovBounds {
7
7
  max: number;
8
8
  defaultFov: number;
9
9
  }
10
+ /** @internal */
10
11
  export declare function resolvePlayerFovBounds(options?: {
11
12
  min?: number;
12
13
  max?: number;
13
14
  default?: number;
14
15
  }): PlayerFovBounds;
16
+ /** @internal */
15
17
  export declare function clampPlayerFov(value: unknown, min?: number, max?: number): number;
18
+ /** @internal */
16
19
  export declare function loadPlayerFov(bounds?: PlayerFovBounds, storage?: Pick<Storage, "getItem"> | null | undefined): number;
20
+ /** @internal */
17
21
  export declare function savePlayerFov(value: number, bounds?: PlayerFovBounds, storage?: Pick<Storage, "setItem"> | null | undefined): number;
18
22
  /**
19
23
  * Composition model for perspective rigs:
@@ -21,5 +25,6 @@ export declare function savePlayerFov(value: number, bounds?: PlayerFovBounds, s
21
25
  * - poseFov is the rig's authored FOV (includes chase-speed modulation, ADS zoom, transitions)
22
26
  * - relative mode shifts the authored FOV by (preference − default) so mods still stack
23
27
  * - absolute mode (cinematic keyframes) keeps the authored FOV as-is
24
- */
28
+ * @internal
29
+ */
25
30
  export declare function composePlayerFov(preference: number, poseFov: number, mode?: "relative" | "absolute", bounds?: PlayerFovBounds): number;
@@ -3,6 +3,7 @@ export const PLAYER_FOV_DEFAULT = CAMERA_FRUSTUM_DEFAULTS.fov;
3
3
  export const PLAYER_FOV_MIN = 40;
4
4
  export const PLAYER_FOV_MAX = 120;
5
5
  export const PLAYER_FOV_STORAGE_KEY = "jgengine:player-fov";
6
+ /** @internal */
6
7
  export function resolvePlayerFovBounds(options) {
7
8
  const min = Number.isFinite(options?.min) ? options.min : PLAYER_FOV_MIN;
8
9
  const max = Number.isFinite(options?.max) ? options.max : PLAYER_FOV_MAX;
@@ -11,6 +12,7 @@ export function resolvePlayerFovBounds(options) {
11
12
  const defaultFov = clampPlayerFov(options?.default ?? PLAYER_FOV_DEFAULT, lo, hi);
12
13
  return { min: lo, max: hi, defaultFov };
13
14
  }
15
+ /** @internal */
14
16
  export function clampPlayerFov(value, min = PLAYER_FOV_MIN, max = PLAYER_FOV_MAX) {
15
17
  if (typeof value !== "number" || !Number.isFinite(value))
16
18
  return clampPlayerFov(PLAYER_FOV_DEFAULT, min, max);
@@ -20,6 +22,7 @@ export function clampPlayerFov(value, min = PLAYER_FOV_MIN, max = PLAYER_FOV_MAX
20
22
  return max;
21
23
  return value;
22
24
  }
25
+ /** @internal */
23
26
  export function loadPlayerFov(bounds = resolvePlayerFovBounds(), storage = defaultStorage()) {
24
27
  if (storage == null)
25
28
  return bounds.defaultFov;
@@ -33,6 +36,7 @@ export function loadPlayerFov(bounds = resolvePlayerFovBounds(), storage = defau
33
36
  return bounds.defaultFov;
34
37
  }
35
38
  }
39
+ /** @internal */
36
40
  export function savePlayerFov(value, bounds = resolvePlayerFovBounds(), storage = defaultStorage()) {
37
41
  const next = clampPlayerFov(value, bounds.min, bounds.max);
38
42
  if (storage == null)
@@ -51,7 +55,8 @@ export function savePlayerFov(value, bounds = resolvePlayerFovBounds(), storage
51
55
  * - poseFov is the rig's authored FOV (includes chase-speed modulation, ADS zoom, transitions)
52
56
  * - relative mode shifts the authored FOV by (preference − default) so mods still stack
53
57
  * - absolute mode (cinematic keyframes) keeps the authored FOV as-is
54
- */
58
+ * @internal
59
+ */
55
60
  export function composePlayerFov(preference, poseFov, mode = "relative", bounds = resolvePlayerFovBounds()) {
56
61
  if (mode === "absolute")
57
62
  return clampPlayerFov(poseFov, bounds.min, bounds.max);
@@ -1,12 +1,9 @@
1
+ /** Public camera surface for games. Pure rig math lives in sibling modules (package-internal). */
1
2
  export { GameOrbitCamera, type CameraFollowListener, type GameOrbitCameraProps } from "./GameOrbitCamera.js";
2
- export { GameFirstPersonCamera, type GameFirstPersonCameraProps } from "./GameFirstPersonCamera.js";
3
+ export { GameFirstPersonCamera, readFirstPersonMuzzle, type GameFirstPersonCameraProps, type ViewmodelProps, } from "./GameFirstPersonCamera.js";
3
4
  export { GameInspectionCamera, type GameInspectionCameraProps } from "./GameInspectionCamera.js";
4
5
  export { GameCameraRig, resolveRigKind, type GameCameraRigProps } from "./GameCameraRig.js";
5
6
  export { PlayerFovProvider, PlayerFovSlider, usePlayerFov, type PlayerFovState, } from "./PlayerFov.js";
6
- export { PLAYER_FOV_DEFAULT, PLAYER_FOV_MAX, PLAYER_FOV_MIN, PLAYER_FOV_STORAGE_KEY, clampPlayerFov, composePlayerFov, loadPlayerFov, resolvePlayerFovBounds, savePlayerFov, type PlayerFovBounds, } from "./fovPreference.js";
7
- export { CAMERA_POST_FRAME_PRIORITY, CAMERA_RIG_FRAME_PRIORITY, ChaseRig, CinematicRig, LockOnRig, ObserverRig, RtsRig, ShoulderRig, SideScrollRig, TopDownRig, type RigProps, } from "./cameraRigs.js";
8
- export { applyCameraBlendStep, captureCameraBlendFrom, createCameraBlendScratch, type CameraBlendScratch, } from "./cameraBlendMath.js";
9
7
  export { CameraShakeContext, cameraShake, createCameraShakeChannel, defaultCameraShakeChannel, useCameraShake, type CameraShakeChannel, } from "./shakeChannel.js";
10
- export { addTrauma, angleDelta, blendShoulder, chaseDesiredPosition, chaseLookAt, cinematicSample, clamp, createTrauma, crossfadePose, forwardVector, lerp, lockOnPose, observerPose, resolveChase, resolveDirectedCamera, resolveObserver, resolveShoulder, resolveSideScroll, resolveSideScrollPose, resolveTopDown, rightVector, rtsPanKeysConflict, seatPose, shakeOffset, shoulderPose, sideScrollFollowBlend, smoothstep, smoothYaw, speedToFov, springArmStep, stepTrauma, topDownPose, yawTo, type CameraPose, type DirectorCameraValues, type ResolvedChase, type ResolvedDirectedCamera, type ResolvedObserver, type ResolvedShoulder, type ResolvedSideScroll, type ResolvedTopDown, type ShakeOffset, type StaticCameraValues, type TraumaState, } from "./rigMath.js";
11
- export { cameraFollowStep, cameraLookPitch, DEFAULT_ORBIT_CAMERA, distanceBetween, GAME_SIM_FRAME_PRIORITY, lerpVec3, ORBIT_CAMERA_FRAME_PRIORITY, orbitFollowStep, orbitYawFromCamera, resolveFollowTargetFromPosition, resolveOrbitCameraConfig, resolveTargetSmoothing, seedOrbitFollowState, smoothBlend, type CameraFollowState, type OrbitCameraConfig, type OrbitFollowRuntimeState, type ResolvedOrbitCameraConfig, type Vec3, } from "./orbitCameraMath.js";
12
- export { resolveInspectionCameraConfig, resolveInspectionZoomToCursor, seedInspectionCamera, type InspectionCameraConfig, type InspectionZoomAnchor, type ResolvedInspectionCameraConfig, } from "./inspectionCameraMath.js";
8
+ export { rtsPanKeysConflict } from "./rigMath.js";
9
+ export { GAME_SIM_FRAME_PRIORITY } from "./orbitCameraMath.js";
@@ -1,12 +1,9 @@
1
+ /** Public camera surface for games. Pure rig math lives in sibling modules (package-internal). */
1
2
  export { GameOrbitCamera } from "./GameOrbitCamera.js";
2
- export { GameFirstPersonCamera } from "./GameFirstPersonCamera.js";
3
+ export { GameFirstPersonCamera, readFirstPersonMuzzle, } from "./GameFirstPersonCamera.js";
3
4
  export { GameInspectionCamera } from "./GameInspectionCamera.js";
4
5
  export { GameCameraRig, resolveRigKind } from "./GameCameraRig.js";
5
6
  export { PlayerFovProvider, PlayerFovSlider, usePlayerFov, } from "./PlayerFov.js";
6
- export { PLAYER_FOV_DEFAULT, PLAYER_FOV_MAX, PLAYER_FOV_MIN, PLAYER_FOV_STORAGE_KEY, clampPlayerFov, composePlayerFov, loadPlayerFov, resolvePlayerFovBounds, savePlayerFov, } from "./fovPreference.js";
7
- export { CAMERA_POST_FRAME_PRIORITY, CAMERA_RIG_FRAME_PRIORITY, ChaseRig, CinematicRig, LockOnRig, ObserverRig, RtsRig, ShoulderRig, SideScrollRig, TopDownRig, } from "./cameraRigs.js";
8
- export { applyCameraBlendStep, captureCameraBlendFrom, createCameraBlendScratch, } from "./cameraBlendMath.js";
9
7
  export { CameraShakeContext, cameraShake, createCameraShakeChannel, defaultCameraShakeChannel, useCameraShake, } from "./shakeChannel.js";
10
- export { addTrauma, angleDelta, blendShoulder, chaseDesiredPosition, chaseLookAt, cinematicSample, clamp, createTrauma, crossfadePose, forwardVector, lerp, lockOnPose, observerPose, resolveChase, resolveDirectedCamera, resolveObserver, resolveShoulder, resolveSideScroll, resolveSideScrollPose, resolveTopDown, rightVector, rtsPanKeysConflict, seatPose, shakeOffset, shoulderPose, sideScrollFollowBlend, smoothstep, smoothYaw, speedToFov, springArmStep, stepTrauma, topDownPose, yawTo, } from "./rigMath.js";
11
- export { cameraFollowStep, cameraLookPitch, DEFAULT_ORBIT_CAMERA, distanceBetween, GAME_SIM_FRAME_PRIORITY, lerpVec3, ORBIT_CAMERA_FRAME_PRIORITY, orbitFollowStep, orbitYawFromCamera, resolveFollowTargetFromPosition, resolveOrbitCameraConfig, resolveTargetSmoothing, seedOrbitFollowState, smoothBlend, } from "./orbitCameraMath.js";
12
- export { resolveInspectionCameraConfig, resolveInspectionZoomToCursor, seedInspectionCamera, } from "./inspectionCameraMath.js";
8
+ export { rtsPanKeysConflict } from "./rigMath.js";
9
+ export { GAME_SIM_FRAME_PRIORITY } from "./orbitCameraMath.js";
@@ -15,11 +15,16 @@ export interface ResolvedInspectionCameraConfig {
15
15
  zoomSpeed: number;
16
16
  dampingFactor: number;
17
17
  }
18
+ /** @internal */
18
19
  export declare function resolveInspectionCameraConfig(config?: InspectionCameraConfig): ResolvedInspectionCameraConfig;
19
- /** Seeds the camera/target world position before OrbitControls mounts. Falls back to `initialDistance` behind `target` on the -Z axis, raised by 40% of that distance, when `initialPosition` is unset. */
20
+ /** Seeds the camera/target world position before OrbitControls mounts. Falls back to `initialDistance` behind `target` on the -Z axis, raised by 40% of that distance, when `initialPosition` is unset.
21
+ * @internal
22
+ */
20
23
  export declare function seedInspectionCamera(config: ResolvedInspectionCameraConfig): {
21
24
  camera: Vec3;
22
25
  target: Vec3;
23
26
  };
24
- /** Maps the anchor mode onto three-stdlib OrbitControls' native `zoomToCursor` flag. */
27
+ /** Maps the anchor mode onto three-stdlib OrbitControls' native `zoomToCursor` flag.
28
+ * @internal
29
+ */
25
30
  export declare function resolveInspectionZoomToCursor(anchor: InspectionZoomAnchor): boolean;
@@ -1,3 +1,4 @@
1
+ /** @internal */
1
2
  export function resolveInspectionCameraConfig(config) {
2
3
  return {
3
4
  anchor: config?.anchor ?? "target",
@@ -24,7 +25,9 @@ export function resolveInspectionCameraConfig(config) {
24
25
  dampingFactor: config?.dampingFactor ?? 0.08,
25
26
  };
26
27
  }
27
- /** Seeds the camera/target world position before OrbitControls mounts. Falls back to `initialDistance` behind `target` on the -Z axis, raised by 40% of that distance, when `initialPosition` is unset. */
28
+ /** Seeds the camera/target world position before OrbitControls mounts. Falls back to `initialDistance` behind `target` on the -Z axis, raised by 40% of that distance, when `initialPosition` is unset.
29
+ * @internal
30
+ */
28
31
  export function seedInspectionCamera(config) {
29
32
  if (config.initialPosition !== null) {
30
33
  return { camera: config.initialPosition, target: config.target };
@@ -38,7 +41,9 @@ export function seedInspectionCamera(config) {
38
41
  target: config.target,
39
42
  };
40
43
  }
41
- /** Maps the anchor mode onto three-stdlib OrbitControls' native `zoomToCursor` flag. */
44
+ /** Maps the anchor mode onto three-stdlib OrbitControls' native `zoomToCursor` flag.
45
+ * @internal
46
+ */
42
47
  export function resolveInspectionZoomToCursor(anchor) {
43
48
  return anchor === "cursor";
44
49
  }
@@ -1,6 +1,10 @@
1
- /** Horizontal facing derived from an orbit camera orbiting a target on the XZ plane. */
1
+ /** Horizontal facing derived from an orbit camera orbiting a target on the XZ plane.
2
+ * @internal
3
+ */
2
4
  export declare function orbitYawFromCamera(cameraX: number, cameraZ: number, targetX: number, targetZ: number): number;
3
- /** Elevation the camera looks along (radians): negative = aiming down, positive = aiming up, 0 = level. Feeds aim.pitch so vertical aim tracks the camera. */
5
+ /** Elevation the camera looks along (radians): negative = aiming down, positive = aiming up, 0 = level. Feeds aim.pitch so vertical aim tracks the camera.
6
+ * @internal
7
+ */
4
8
  export declare function cameraLookPitch(camera: Vec3, target: Vec3): number;
5
9
  export interface Vec3 {
6
10
  x: number;
@@ -19,6 +23,12 @@ export interface OrbitCameraConfig {
19
23
  targetHeight?: number;
20
24
  initialDistance?: number;
21
25
  initialHeight?: number;
26
+ /** Initial horizontal facing of the boom (radians): 0 = camera behind on -Z, PI = camera on +Z. Seeds the rig when set; unset keeps the legacy -Z placement. */
27
+ initialYaw?: number;
28
+ /** Initial boom elevation (radians): 0 = level, positive = camera above the target looking down. Seeds the rig when set; unset derives elevation from `initialHeight`/`initialDistance`. */
29
+ initialPitch?: number;
30
+ /** Signed boom-elevation clamp `[min, max]` (radians): min < 0 dips below the target, max > 0 rises overhead. Maps onto `minPolarAngle`/`maxPolarAngle` (polar = PI/2 − pitch); explicit polar fields win. */
31
+ pitchClamp?: readonly [number, number];
22
32
  /** Extra offset applied on top of entity position for the orbit target. */
23
33
  targetOffset?: Partial<Vec3>;
24
34
  /** Keep orbit radius locked while the follow target moves. */
@@ -64,6 +74,8 @@ export interface ResolvedOrbitCameraConfig {
64
74
  targetHeight: number;
65
75
  initialDistance: number;
66
76
  initialHeight: number;
77
+ initialYaw?: number;
78
+ initialPitch?: number;
67
79
  targetOffset?: Partial<Vec3>;
68
80
  followLock: boolean;
69
81
  followEnabled: boolean;
@@ -87,22 +99,38 @@ export interface OrbitFollowRuntimeState {
87
99
  camera: Vec3;
88
100
  lockedDistance: number | null;
89
101
  }
90
- /** Frame-rate independent exponential smoothing factor in [0, 1]. */
102
+ /** Frame-rate independent exponential smoothing factor in [0, 1].
103
+ * @internal
104
+ */
91
105
  export declare function smoothBlend(deltaSeconds: number, speed: number): number;
106
+ /** @internal */
92
107
  export declare function resolveOrbitCameraConfig(patch?: OrbitCameraConfig): ResolvedOrbitCameraConfig;
108
+ /**
109
+ * FOV that keeps a target's on-screen size constant as the spring-arm pulls the
110
+ * camera in. Widens the base FOV by the desired/pulled distance ratio; a no-op
111
+ * (returns the base) whenever the camera sits at or beyond its desired distance.
112
+ * @internal
113
+ */
114
+ export declare function compensatedFov(baseFovDegrees: number, desiredDistance: number, pulledDistance: number): number;
115
+ /** @internal */
93
116
  export declare function resolveTargetSmoothing(config: ResolvedOrbitCameraConfig, dragging: boolean): number;
117
+ /** @internal */
94
118
  export declare function resolveFollowTargetFromPosition(position: readonly [number, number, number], config: Pick<ResolvedOrbitCameraConfig, "targetHeight" | "targetOffset">): Vec3;
119
+ /** @internal */
95
120
  export declare function lerpVec3(from: Vec3, to: Vec3, blend: number): Vec3;
121
+ /** @internal */
96
122
  export declare function distanceBetween(a: Vec3, b: Vec3): number;
123
+ /** @internal */
97
124
  export declare function seedOrbitFollowState(input: {
98
125
  entityPosition: readonly [number, number, number];
99
- config: Pick<ResolvedOrbitCameraConfig, "targetHeight" | "targetOffset" | "initialDistance" | "initialHeight">;
126
+ config: Pick<ResolvedOrbitCameraConfig, "targetHeight" | "targetOffset" | "initialDistance" | "initialHeight" | "initialYaw" | "initialPitch">;
100
127
  }): OrbitFollowRuntimeState;
101
128
  /**
102
129
  * Pure orbit follow step — smooth target tracking, camera carries target delta
103
130
  * (OrbitControls alone keeps camera fixed when only target moves), optional
104
131
  * distance lock. Call each frame before OrbitControls.update() in the shell.
105
- */
132
+ * @internal
133
+ */
106
134
  export declare function orbitFollowStep(input: {
107
135
  state: OrbitFollowRuntimeState;
108
136
  desiredTarget: Vec3;
@@ -112,7 +140,9 @@ export declare function orbitFollowStep(input: {
112
140
  }): OrbitFollowRuntimeState & {
113
141
  distance: number;
114
142
  };
115
- /** @deprecated Use orbitFollowStep — kept for older call sites. */
143
+ /** @deprecated Use orbitFollowStep — kept for older call sites.
144
+ * @internal
145
+ */
116
146
  export declare function cameraFollowStep(input: {
117
147
  camera: Vec3;
118
148
  target: Vec3;
@@ -1,10 +1,14 @@
1
- /** Horizontal facing derived from an orbit camera orbiting a target on the XZ plane. */
1
+ /** Horizontal facing derived from an orbit camera orbiting a target on the XZ plane.
2
+ * @internal
3
+ */
2
4
  export function orbitYawFromCamera(cameraX, cameraZ, targetX, targetZ) {
3
5
  const dx = targetX - cameraX;
4
6
  const dz = targetZ - cameraZ;
5
7
  return Math.atan2(dx, dz);
6
8
  }
7
- /** Elevation the camera looks along (radians): negative = aiming down, positive = aiming up, 0 = level. Feeds aim.pitch so vertical aim tracks the camera. */
9
+ /** Elevation the camera looks along (radians): negative = aiming down, positive = aiming up, 0 = level. Feeds aim.pitch so vertical aim tracks the camera.
10
+ * @internal
11
+ */
8
12
  export function cameraLookPitch(camera, target) {
9
13
  const horizontal = Math.hypot(target.x - camera.x, target.z - camera.z);
10
14
  return Math.atan2(target.y - camera.y, horizontal);
@@ -31,23 +35,48 @@ export const DEFAULT_ORBIT_CAMERA = {
31
35
  export const GAME_SIM_FRAME_PRIORITY = 0;
32
36
  /** Orbit follow reads the latest entity pose after GAME_SIM_FRAME_PRIORITY. */
33
37
  export const ORBIT_CAMERA_FRAME_PRIORITY = -1;
34
- /** Frame-rate independent exponential smoothing factor in [0, 1]. */
38
+ /** Frame-rate independent exponential smoothing factor in [0, 1].
39
+ * @internal
40
+ */
35
41
  export function smoothBlend(deltaSeconds, speed) {
36
42
  return 1 - Math.exp(-speed * deltaSeconds);
37
43
  }
44
+ /** @internal */
38
45
  export function resolveOrbitCameraConfig(patch) {
39
- return {
46
+ const resolved = {
40
47
  ...DEFAULT_ORBIT_CAMERA,
41
48
  ...patch,
42
49
  collision: { ...DEFAULT_ORBIT_CAMERA.collision, ...patch?.collision },
43
50
  };
51
+ if (patch?.pitchClamp !== undefined) {
52
+ const [minPitch, maxPitch] = patch.pitchClamp;
53
+ resolved.minPolarAngle = patch.minPolarAngle ?? Math.PI / 2 - maxPitch;
54
+ resolved.maxPolarAngle = patch.maxPolarAngle ?? Math.PI / 2 - minPitch;
55
+ }
56
+ return resolved;
44
57
  }
58
+ /**
59
+ * FOV that keeps a target's on-screen size constant as the spring-arm pulls the
60
+ * camera in. Widens the base FOV by the desired/pulled distance ratio; a no-op
61
+ * (returns the base) whenever the camera sits at or beyond its desired distance.
62
+ * @internal
63
+ */
64
+ export function compensatedFov(baseFovDegrees, desiredDistance, pulledDistance) {
65
+ if (pulledDistance <= 0 || desiredDistance <= 0 || pulledDistance >= desiredDistance) {
66
+ return baseFovDegrees;
67
+ }
68
+ const half = (baseFovDegrees * Math.PI) / 360;
69
+ const widened = 2 * Math.atan(Math.tan(half) * (desiredDistance / pulledDistance));
70
+ return Math.min((widened * 180) / Math.PI, 170);
71
+ }
72
+ /** @internal */
45
73
  export function resolveTargetSmoothing(config, dragging) {
46
74
  if (dragging) {
47
75
  return config.dragTargetSmoothing;
48
76
  }
49
77
  return config.targetSmoothing;
50
78
  }
79
+ /** @internal */
51
80
  export function resolveFollowTargetFromPosition(position, config) {
52
81
  const offset = config.targetOffset;
53
82
  return {
@@ -56,6 +85,7 @@ export function resolveFollowTargetFromPosition(position, config) {
56
85
  z: position[2] + (offset?.z ?? 0),
57
86
  };
58
87
  }
88
+ /** @internal */
59
89
  export function lerpVec3(from, to, blend) {
60
90
  return {
61
91
  x: from.x + (to.x - from.x) * blend,
@@ -63,14 +93,31 @@ export function lerpVec3(from, to, blend) {
63
93
  z: from.z + (to.z - from.z) * blend,
64
94
  };
65
95
  }
96
+ /** @internal */
66
97
  export function distanceBetween(a, b) {
67
98
  const dx = a.x - b.x;
68
99
  const dy = a.y - b.y;
69
100
  const dz = a.z - b.z;
70
101
  return Math.sqrt(dx * dx + dy * dy + dz * dz);
71
102
  }
103
+ /** @internal */
72
104
  export function seedOrbitFollowState(input) {
73
105
  const target = resolveFollowTargetFromPosition(input.entityPosition, input.config);
106
+ const { initialDistance, initialYaw, initialPitch } = input.config;
107
+ if (initialYaw !== undefined || initialPitch !== undefined) {
108
+ const yaw = initialYaw ?? 0;
109
+ const pitch = initialPitch ?? Math.atan2(input.config.initialHeight - input.config.targetHeight, initialDistance);
110
+ const horizontal = initialDistance * Math.cos(pitch);
111
+ return {
112
+ target,
113
+ camera: {
114
+ x: target.x - horizontal * Math.sin(yaw),
115
+ y: target.y + initialDistance * Math.sin(pitch),
116
+ z: target.z - horizontal * Math.cos(yaw),
117
+ },
118
+ lockedDistance: initialDistance,
119
+ };
120
+ }
74
121
  return {
75
122
  target,
76
123
  camera: {
@@ -85,7 +132,8 @@ export function seedOrbitFollowState(input) {
85
132
  * Pure orbit follow step — smooth target tracking, camera carries target delta
86
133
  * (OrbitControls alone keeps camera fixed when only target moves), optional
87
134
  * distance lock. Call each frame before OrbitControls.update() in the shell.
88
- */
135
+ * @internal
136
+ */
89
137
  export function orbitFollowStep(input) {
90
138
  const target = { ...input.state.target };
91
139
  const camera = { ...input.state.camera };
@@ -124,7 +172,9 @@ export function orbitFollowStep(input) {
124
172
  }
125
173
  return { target, camera, lockedDistance, distance: distanceBetween(camera, target) };
126
174
  }
127
- /** @deprecated Use orbitFollowStep — kept for older call sites. */
175
+ /** @deprecated Use orbitFollowStep — kept for older call sites.
176
+ * @internal
177
+ */
128
178
  export function cameraFollowStep(input) {
129
179
  const state = {
130
180
  target: input.previousTarget ?? input.target,