@jgengine/shell 0.9.0 → 0.11.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 (248) hide show
  1. package/CHANGELOG.md +34 -3
  2. package/LICENSE +203 -661
  3. package/README.md +1 -1
  4. package/dist/GamePhaseStamp.d.ts +1 -0
  5. package/dist/GamePhaseStamp.js +9 -0
  6. package/dist/GamePlayerShell.d.ts +4 -31
  7. package/dist/GamePlayerShell.js +162 -932
  8. package/dist/GameUiPreview.js +0 -2
  9. package/dist/audio/audioEngine.d.ts +8 -0
  10. package/dist/audio/audioEngine.js +63 -8
  11. package/dist/audio/audioWire.d.ts +22 -0
  12. package/dist/audio/audioWire.js +19 -0
  13. package/dist/audio/musicDirector.d.ts +31 -0
  14. package/dist/audio/musicDirector.js +125 -0
  15. package/dist/audio/musicVoices.d.ts +10 -0
  16. package/dist/audio/musicVoices.js +338 -0
  17. package/dist/audio/synthEngine.d.ts +9 -0
  18. package/dist/audio/synthEngine.js +57 -0
  19. package/dist/behaviour.d.ts +2 -1
  20. package/dist/behaviour.js +2 -1
  21. package/dist/behaviourAttach.d.ts +1 -0
  22. package/dist/behaviourAttach.js +1 -0
  23. package/dist/behaviourDriver.d.ts +1 -0
  24. package/dist/behaviourDriver.js +1 -0
  25. package/dist/boundActionDispatch.d.ts +13 -0
  26. package/dist/boundActionDispatch.js +37 -0
  27. package/dist/camera/GameCameraRig.d.ts +5 -2
  28. package/dist/camera/GameCameraRig.js +2 -2
  29. package/dist/camera/GameFirstPersonCamera.d.ts +12 -2
  30. package/dist/camera/GameFirstPersonCamera.js +29 -7
  31. package/dist/camera/GameInspectionCamera.d.ts +1 -1
  32. package/dist/camera/GameInspectionCamera.js +13 -2
  33. package/dist/camera/GameOrbitCamera.js +59 -10
  34. package/dist/camera/cameraBlendMath.d.ts +3 -0
  35. package/dist/camera/cameraBlendMath.js +3 -0
  36. package/dist/camera/fovPreference.d.ts +6 -1
  37. package/dist/camera/fovPreference.js +6 -1
  38. package/dist/camera/index.d.ts +4 -7
  39. package/dist/camera/index.js +4 -7
  40. package/dist/camera/inspectionCameraMath.d.ts +7 -2
  41. package/dist/camera/inspectionCameraMath.js +7 -2
  42. package/dist/camera/orbitCameraMath.d.ts +54 -6
  43. package/dist/camera/orbitCameraMath.js +61 -6
  44. package/dist/camera/rigMath.d.ts +94 -24
  45. package/dist/camera/rigMath.js +100 -24
  46. package/dist/camera/rigResolve.d.ts +4 -2
  47. package/dist/camera/rigResolve.js +4 -2
  48. package/dist/camera/shakeChannel.d.ts +6 -2
  49. package/dist/camera/shakeChannel.js +6 -2
  50. package/dist/camera/shakeChannelMath.d.ts +1 -0
  51. package/dist/camera/shakeChannelMath.js +1 -0
  52. package/dist/cartridge/runtime.d.ts +15 -0
  53. package/dist/cartridge/runtime.js +556 -0
  54. package/dist/cartridge/spec.d.ts +188 -0
  55. package/dist/cartridge/spec.js +19 -0
  56. package/dist/cartridge/validate.d.ts +2 -0
  57. package/dist/cartridge/validate.js +78 -0
  58. package/dist/cartridge.d.ts +1 -1
  59. package/dist/cartridge.js +4 -3
  60. package/dist/commandSink.d.ts +25 -0
  61. package/dist/commandSink.js +32 -0
  62. package/dist/defineGame.js +26 -5
  63. package/dist/devtools/ColPanel.d.ts +1 -0
  64. package/dist/devtools/ColPanel.js +14 -0
  65. package/dist/devtools/CollisionDebugWorld.js +1 -0
  66. package/dist/devtools/DevtoolsOverlay.d.ts +7 -3
  67. package/dist/devtools/DevtoolsOverlay.js +19 -450
  68. package/dist/devtools/KeysPanel.d.ts +4 -0
  69. package/dist/devtools/KeysPanel.js +31 -0
  70. package/dist/devtools/LogsPanel.d.ts +1 -0
  71. package/dist/devtools/LogsPanel.js +12 -0
  72. package/dist/devtools/NetPanel.d.ts +4 -0
  73. package/dist/devtools/NetPanel.js +10 -0
  74. package/dist/devtools/PerfPanel.d.ts +4 -0
  75. package/dist/devtools/PerfPanel.js +53 -0
  76. package/dist/devtools/TunePanel.d.ts +3 -0
  77. package/dist/devtools/TunePanel.js +309 -0
  78. package/dist/devtools/agentBridge.d.ts +41 -0
  79. package/dist/devtools/agentBridge.js +262 -0
  80. package/dist/devtools/collisionDebug.d.ts +6 -0
  81. package/dist/devtools/collisionDebug.js +6 -0
  82. package/dist/devtools/collisionDebugMath.d.ts +9 -2
  83. package/dist/devtools/collisionDebugMath.js +25 -5
  84. package/dist/devtools/devtoolsOverrides.d.ts +6 -0
  85. package/dist/devtools/devtoolsOverrides.js +48 -0
  86. package/dist/devtools/panelAtoms.d.ts +9 -0
  87. package/dist/devtools/panelAtoms.js +10 -0
  88. package/dist/devtools/perfDiagnose.d.ts +2 -0
  89. package/dist/devtools/perfDiagnose.js +43 -0
  90. package/dist/diagnostics/RuntimeDiagnostics.d.ts +29 -0
  91. package/dist/diagnostics/RuntimeDiagnostics.js +69 -0
  92. package/dist/drivers/FrameDriver.d.ts +37 -0
  93. package/dist/drivers/FrameDriver.js +232 -0
  94. package/dist/drivers/HudOnlyDriver.d.ts +16 -0
  95. package/dist/drivers/HudOnlyDriver.js +60 -0
  96. package/dist/environment/Daylight.d.ts +29 -7
  97. package/dist/environment/Daylight.js +153 -13
  98. package/dist/environment/EnvironmentScene.js +42 -7
  99. package/dist/environment/RoadRibbons.d.ts +7 -0
  100. package/dist/environment/RoadRibbons.js +57 -0
  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/hotbarActions.d.ts +26 -0
  112. package/dist/hotbarActions.js +27 -0
  113. package/dist/inputSink.d.ts +30 -0
  114. package/dist/inputSink.js +85 -0
  115. package/dist/materialOverride.d.ts +10 -0
  116. package/dist/materialOverride.js +15 -3
  117. package/dist/multiplayer.js +14 -4
  118. package/dist/postfx/PostProcessing.d.ts +10 -0
  119. package/dist/postfx/PostProcessing.js +120 -0
  120. package/dist/postfx/gradeShader.d.ts +4 -0
  121. package/dist/postfx/gradeShader.js +72 -0
  122. package/dist/registry.d.ts +3 -2
  123. package/dist/render/EnvironmentLighting.d.ts +10 -0
  124. package/dist/render/EnvironmentLighting.js +34 -0
  125. package/dist/render/SceneLighting.d.ts +7 -0
  126. package/dist/render/SceneLighting.js +18 -0
  127. package/dist/render/SceneModels.d.ts +14 -0
  128. package/dist/render/SceneModels.js +337 -0
  129. package/dist/render/defaultSurface.d.ts +29 -0
  130. package/dist/render/defaultSurface.js +101 -0
  131. package/dist/render/modelLoad.d.ts +24 -0
  132. package/dist/render/modelLoad.js +73 -0
  133. package/dist/render/modelRender.d.ts +11 -0
  134. package/dist/render/modelRender.js +15 -0
  135. package/dist/render/resolveModel.d.ts +39 -3
  136. package/dist/render/resolveModel.js +79 -2
  137. package/dist/render/useEntityRenderCues.d.ts +13 -0
  138. package/dist/render/useEntityRenderCues.js +62 -0
  139. package/dist/scatter/InstancedScatter.d.ts +24 -0
  140. package/dist/scatter/InstancedScatter.js +130 -0
  141. package/dist/scatter/scatterModels.d.ts +25 -0
  142. package/dist/scatter/scatterModels.js +61 -0
  143. package/dist/scatter/scatterProxies.d.ts +6 -0
  144. package/dist/scatter/scatterProxies.js +94 -0
  145. package/dist/scatter.d.ts +1 -0
  146. package/dist/scatter.js +1 -0
  147. package/dist/scene/AuthoredScene.d.ts +82 -0
  148. package/dist/scene/AuthoredScene.js +173 -0
  149. package/dist/scene/GeneratedAssetRenderer.d.ts +14 -0
  150. package/dist/scene/GeneratedAssetRenderer.js +59 -0
  151. package/dist/scene/GrassFieldRenderer.d.ts +2 -0
  152. package/dist/scene/GrassFieldRenderer.js +25 -0
  153. package/dist/scene/SoilPatchRenderer.d.ts +2 -0
  154. package/dist/scene/SoilPatchRenderer.js +21 -0
  155. package/dist/scene/StudioStage.d.ts +37 -0
  156. package/dist/scene/StudioStage.js +42 -0
  157. package/dist/scene/WaterRenderer.d.ts +2 -0
  158. package/dist/scene/WaterRenderer.js +37 -0
  159. package/dist/scene/builtinSceneKindRenderers.d.ts +2 -0
  160. package/dist/scene/builtinSceneKindRenderers.js +22 -0
  161. package/dist/scene/sceneKindRenderers.d.ts +28 -0
  162. package/dist/scene/sceneKindRenderers.js +17 -0
  163. package/dist/scene.d.ts +1 -0
  164. package/dist/scene.js +1 -0
  165. package/dist/settings/appliedSettings.d.ts +5 -0
  166. package/dist/settings/appliedSettings.js +9 -0
  167. package/dist/settings/settingsController.d.ts +2 -0
  168. package/dist/settings/settingsController.js +22 -1
  169. package/dist/shellConstants.d.ts +4 -0
  170. package/dist/shellConstants.js +4 -0
  171. package/dist/shellMovement.d.ts +8 -0
  172. package/dist/shellMovement.js +11 -0
  173. package/dist/shellPointer.d.ts +20 -0
  174. package/dist/shellPointer.js +28 -0
  175. package/dist/structures/PlacementGhost.d.ts +4 -0
  176. package/dist/structures/PlacementGhost.js +4 -0
  177. package/dist/structures/TransformGizmo.d.ts +43 -0
  178. package/dist/structures/TransformGizmo.js +55 -0
  179. package/dist/structures/index.d.ts +1 -0
  180. package/dist/structures/index.js +1 -0
  181. package/dist/terrain/CarvedTerrain.d.ts +4 -1
  182. package/dist/terrain/CarvedTerrain.js +4 -3
  183. package/dist/terrain/GrassField.js +1 -1
  184. package/dist/terrain/TerraformBrushCursor.js +3 -0
  185. package/dist/terrain/grassBudget.d.ts +11 -2
  186. package/dist/terrain/grassBudget.js +20 -4
  187. package/dist/terrain/grassGeometry.d.ts +3 -0
  188. package/dist/terrain/grassGeometry.js +3 -0
  189. package/dist/terrain/grassMaterial.d.ts +2 -0
  190. package/dist/terrain/grassMaterial.js +2 -0
  191. package/dist/terrain/random.d.ts +3 -0
  192. package/dist/terrain/random.js +3 -0
  193. package/dist/terrain/soilPatchMaterial.d.ts +10 -0
  194. package/dist/terrain/soilPatchMaterial.js +109 -0
  195. package/dist/terrain/terrainDetailMaterial.d.ts +31 -0
  196. package/dist/terrain/terrainDetailMaterial.js +157 -0
  197. package/dist/terrain/terrainMath.d.ts +8 -1
  198. package/dist/terrain/terrainMath.js +8 -1
  199. package/dist/touch/TouchControlsOverlay.d.ts +9 -5
  200. package/dist/touch/TouchControlsOverlay.js +211 -22
  201. package/dist/useShellMultiplayerSync.d.ts +9 -0
  202. package/dist/useShellMultiplayerSync.js +98 -0
  203. package/dist/visibility/CullingProvider.js +1 -1
  204. package/dist/water/OceanConfig.d.ts +2 -0
  205. package/dist/water/OceanConfig.js +2 -0
  206. package/dist/weather/fireSpreadPose.d.ts +1 -0
  207. package/dist/weather/fireSpreadPose.js +1 -0
  208. package/dist/weather/weatherGeometry.d.ts +1 -0
  209. package/dist/weather/weatherGeometry.js +1 -0
  210. package/dist/weather/weatherMath.d.ts +3 -0
  211. package/dist/weather/weatherMath.js +3 -0
  212. package/dist/weather/weatherUniforms.d.ts +3 -0
  213. package/dist/weather/weatherUniforms.js +3 -0
  214. package/dist/world/RetainedVfx.d.ts +10 -0
  215. package/dist/world/RetainedVfx.js +136 -0
  216. package/dist/world/WorldHud.d.ts +52 -3
  217. package/dist/world/WorldHud.js +94 -9
  218. package/dist/world/WorldScene.d.ts +21 -0
  219. package/dist/world/WorldScene.js +159 -0
  220. package/dist/world/WorldVfx.d.ts +9 -0
  221. package/dist/world/WorldVfx.js +184 -0
  222. package/dist/world/entityPose.d.ts +2 -0
  223. package/dist/world/entityPose.js +2 -0
  224. package/dist/world/floatTextStyle.d.ts +1 -0
  225. package/dist/world/floatTextStyle.js +1 -0
  226. package/dist/world/telegraphPulse.d.ts +1 -0
  227. package/dist/world/telegraphPulse.js +1 -0
  228. package/dist/world/worldBarSamples.d.ts +26 -1
  229. package/dist/world/worldBarSamples.js +45 -1
  230. package/dist/worldSky.d.ts +5 -0
  231. package/dist/worldSky.js +11 -0
  232. package/dist/worldSync.d.ts +11 -0
  233. package/dist/worldSync.js +20 -0
  234. package/package.json +6 -6
  235. package/skills/jgengine/SKILL.md +51 -0
  236. package/skills/jgengine/api.md +434 -0
  237. package/skills/jgengine/capabilities.md +29 -0
  238. package/skills/jgengine/reference-cartridge.md +72 -0
  239. package/skills/jgengine-ui/SKILL.md +36 -0
  240. package/skills/jgengine-ui/api.md +1852 -0
  241. package/skills/jgengine-ui/capabilities.md +142 -0
  242. package/skills/jgengine-ui/reference.md +500 -0
  243. package/skills/jgengine-ui/references/visual-scorecard.md +73 -0
  244. package/skills/jgengine-world/SKILL.md +38 -0
  245. package/skills/jgengine-world/api.md +2652 -0
  246. package/skills/jgengine-world/capabilities.md +370 -0
  247. package/skills/jgengine-world/reference.md +222 -0
  248. package/llms.txt +0 -2643
@@ -4,7 +4,7 @@ import { useThree } from "@react-three/fiber";
4
4
  import { useEffect, useRef } from "react";
5
5
  import { MOUSE } from "three";
6
6
  import { resolveInspectionCameraConfig, resolveInspectionZoomToCursor, seedInspectionCamera, } from "./inspectionCameraMath.js";
7
- /** Model-viewer style rig (#207.7): left-drag orbit, middle/right-drag pan, scroll zoom toward a configurable anchor. Orbits a fixed `target`; never reads player/entity state. */
7
+ /** Model-viewer / editor rig (#207.7, #866): left-click selects (editor), middle-drag pans, right-drag orbits, scroll zooms toward a configurable anchor. Orbits a fixed `target`; never reads player/entity state. */
8
8
  export function GameInspectionCamera({ config: configPatch }) {
9
9
  const config = resolveInspectionCameraConfig(configPatch);
10
10
  const controlsRef = useRef(null);
@@ -20,5 +20,16 @@ export function GameInspectionCamera({ config: configPatch }) {
20
20
  controlsRef.current?.update();
21
21
  seededRef.current = true;
22
22
  }, [camera, config]);
23
- return (_jsx(OrbitControls, { ref: controlsRef, enableDamping: true, dampingFactor: config.dampingFactor, rotateSpeed: config.rotateSpeed, zoomSpeed: config.zoomSpeed, zoomToCursor: resolveInspectionZoomToCursor(config.anchor), enablePan: config.pan, enableRotate: true, enableZoom: true, minDistance: config.minDistance, maxDistance: config.maxDistance, minPolarAngle: config.minPolarAngle, maxPolarAngle: config.maxPolarAngle, screenSpacePanning: true, mouseButtons: { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.PAN, RIGHT: MOUSE.PAN } }));
23
+ useEffect(() => {
24
+ if (!seededRef.current)
25
+ return;
26
+ const controls = controlsRef.current;
27
+ if (controls === null)
28
+ return;
29
+ const previous = controls.target.clone();
30
+ controls.target.set(config.target.x, config.target.y, config.target.z);
31
+ camera.position.add(controls.target.clone().sub(previous));
32
+ controls.update();
33
+ }, [camera, config.target.x, config.target.y, config.target.z]);
34
+ return (_jsx(OrbitControls, { ref: controlsRef, makeDefault: true, enableDamping: true, dampingFactor: config.dampingFactor, rotateSpeed: config.rotateSpeed, zoomSpeed: config.zoomSpeed, zoomToCursor: resolveInspectionZoomToCursor(config.anchor), enablePan: config.pan, enableRotate: true, enableZoom: true, minDistance: config.minDistance, maxDistance: config.maxDistance, minPolarAngle: config.minPolarAngle, maxPolarAngle: config.maxPolarAngle, screenSpacePanning: true, mouseButtons: { LEFT: undefined, MIDDLE: MOUSE.PAN, RIGHT: MOUSE.ROTATE } }));
24
35
  }
@@ -2,10 +2,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { OrbitControls } from "@react-three/drei";
3
3
  import { useFrame, useThree } from "@react-three/fiber";
4
4
  import { useEffect, useRef } from "react";
5
- import { MOUSE, PerspectiveCamera, Vector3 } from "three";
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);
@@ -16,6 +16,16 @@ export function GameOrbitCamera({ yawRef, pitchRef, config: configPatch, followE
16
16
  const ctx = useGameContext();
17
17
  const playerFov = usePlayerFov();
18
18
  const camera = useThree((state) => state.camera);
19
+ const scene = useThree((state) => state.scene);
20
+ const raycasterRef = useRef(new Raycaster());
21
+ const collisionDirRef = useRef(new Vector3());
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
+ });
19
29
  const followId = followEntityId ?? userId;
20
30
  useEffect(() => {
21
31
  const entity = ctx.scene.entity.get(followId);
@@ -56,6 +66,40 @@ export function GameOrbitCamera({ yawRef, pitchRef, config: configPatch, followE
56
66
  camera.position.set(stepped.camera.x, stepped.camera.y, stepped.camera.z);
57
67
  }
58
68
  controls.update();
69
+ let desiredDistance = 0;
70
+ let pulledDistance = 0;
71
+ if (config.collision.enabled) {
72
+ const t = stepped.target;
73
+ const dx = camera.position.x - t.x;
74
+ const dy = camera.position.y - t.y;
75
+ const dz = camera.position.z - t.z;
76
+ const dist = Math.hypot(dx, dy, dz);
77
+ desiredDistance = dist;
78
+ if (dist > config.collision.minTargetDistance) {
79
+ const dir = collisionDirRef.current.set(dx / dist, dy / dist, dz / dist);
80
+ const ray = raycasterRef.current;
81
+ ray.set(collisionOriginRef.current.set(t.x, t.y, t.z), dir);
82
+ ray.near = config.collision.minTargetDistance;
83
+ ray.far = dist;
84
+ let blocked = 0;
85
+ for (const hit of ray.intersectObjects(scene.children, true)) {
86
+ const obj = hit.object;
87
+ if (!obj.visible)
88
+ continue;
89
+ if (obj.isSprite === true)
90
+ continue;
91
+ if (obj.userData.jgCameraTransparent === true)
92
+ continue;
93
+ blocked = hit.distance;
94
+ break;
95
+ }
96
+ if (blocked > 0) {
97
+ const pulled = Math.max(config.collision.minTargetDistance, blocked - config.collision.padding);
98
+ camera.position.set(t.x + dir.x * pulled, t.y + dir.y * pulled, t.z + dir.z * pulled);
99
+ pulledDistance = pulled;
100
+ }
101
+ }
102
+ }
59
103
  runtimeRef.current = {
60
104
  target: stepped.target,
61
105
  camera: { x: camera.position.x, y: camera.position.y, z: camera.position.z },
@@ -68,17 +112,22 @@ export function GameOrbitCamera({ yawRef, pitchRef, config: configPatch, followE
68
112
  }
69
113
  if (camera.isPerspectiveCamera === true) {
70
114
  const perspective = camera;
71
- if (Math.abs(perspective.fov - playerFov.fov) > 0.001) {
72
- 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;
73
120
  perspective.updateProjectionMatrix();
74
121
  }
75
122
  }
76
- onCameraFollow?.({
77
- entityId: followId,
78
- target: stepped.target,
79
- camera: runtimeRef.current.camera,
80
- distance: runtimeRef.current.lockedDistance ?? stepped.distance,
81
- });
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
+ }
82
131
  }, ORBIT_CAMERA_FRAME_PRIORITY);
83
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
84
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);
@@ -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. */
@@ -39,6 +49,23 @@ export interface OrbitCameraConfig {
39
49
  /** Vertical clamp (three.js polar angle, radians): 0 = top-down over the head, PI/2 = level, >PI/2 = look up from below the target. Widen to allow top-down or vertical aim; leave unset for the standard chase feel. */
40
50
  minPolarAngle?: number;
41
51
  maxPolarAngle?: number;
52
+ /** Spring-arm collision: raycast target→camera each frame and pull the boom in past occluders (walls, terrain) so the camera never clips inside geometry. */
53
+ collision?: OrbitCollisionConfig;
54
+ }
55
+ /** Spring-arm occlusion config for the orbit rig. */
56
+ export interface OrbitCollisionConfig {
57
+ /** Enable the pull-in raycast. Default false (unchanged chase camera). */
58
+ enabled?: boolean;
59
+ /** Gap kept between the camera and the hit surface, world units. Default 0.35. */
60
+ padding?: number;
61
+ /** Ignore hits closer than this to the target — the player's own body. Default 1.2. */
62
+ minTargetDistance?: number;
63
+ }
64
+ /** An {@link OrbitCollisionConfig} with every field resolved to a concrete value. */
65
+ export interface ResolvedOrbitCollision {
66
+ enabled: boolean;
67
+ padding: number;
68
+ minTargetDistance: number;
42
69
  }
43
70
  /** Fully resolved shell config after merging with DEFAULT_ORBIT_CAMERA. */
44
71
  export interface ResolvedOrbitCameraConfig {
@@ -47,6 +74,8 @@ export interface ResolvedOrbitCameraConfig {
47
74
  targetHeight: number;
48
75
  initialDistance: number;
49
76
  initialHeight: number;
77
+ initialYaw?: number;
78
+ initialPitch?: number;
50
79
  targetOffset?: Partial<Vec3>;
51
80
  followLock: boolean;
52
81
  followEnabled: boolean;
@@ -58,6 +87,7 @@ export interface ResolvedOrbitCameraConfig {
58
87
  distanceSmoothing: number;
59
88
  minPolarAngle: number;
60
89
  maxPolarAngle: number;
90
+ collision: ResolvedOrbitCollision;
61
91
  }
62
92
  export declare const DEFAULT_ORBIT_CAMERA: ResolvedOrbitCameraConfig;
63
93
  /** Run simulation/movement before orbit follow so poses are current. */
@@ -69,22 +99,38 @@ export interface OrbitFollowRuntimeState {
69
99
  camera: Vec3;
70
100
  lockedDistance: number | null;
71
101
  }
72
- /** Frame-rate independent exponential smoothing factor in [0, 1]. */
102
+ /** Frame-rate independent exponential smoothing factor in [0, 1].
103
+ * @internal
104
+ */
73
105
  export declare function smoothBlend(deltaSeconds: number, speed: number): number;
106
+ /** @internal */
74
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 */
75
116
  export declare function resolveTargetSmoothing(config: ResolvedOrbitCameraConfig, dragging: boolean): number;
117
+ /** @internal */
76
118
  export declare function resolveFollowTargetFromPosition(position: readonly [number, number, number], config: Pick<ResolvedOrbitCameraConfig, "targetHeight" | "targetOffset">): Vec3;
119
+ /** @internal */
77
120
  export declare function lerpVec3(from: Vec3, to: Vec3, blend: number): Vec3;
121
+ /** @internal */
78
122
  export declare function distanceBetween(a: Vec3, b: Vec3): number;
123
+ /** @internal */
79
124
  export declare function seedOrbitFollowState(input: {
80
125
  entityPosition: readonly [number, number, number];
81
- config: Pick<ResolvedOrbitCameraConfig, "targetHeight" | "targetOffset" | "initialDistance" | "initialHeight">;
126
+ config: Pick<ResolvedOrbitCameraConfig, "targetHeight" | "targetOffset" | "initialDistance" | "initialHeight" | "initialYaw" | "initialPitch">;
82
127
  }): OrbitFollowRuntimeState;
83
128
  /**
84
129
  * Pure orbit follow step — smooth target tracking, camera carries target delta
85
130
  * (OrbitControls alone keeps camera fixed when only target moves), optional
86
131
  * distance lock. Call each frame before OrbitControls.update() in the shell.
87
- */
132
+ * @internal
133
+ */
88
134
  export declare function orbitFollowStep(input: {
89
135
  state: OrbitFollowRuntimeState;
90
136
  desiredTarget: Vec3;
@@ -94,7 +140,9 @@ export declare function orbitFollowStep(input: {
94
140
  }): OrbitFollowRuntimeState & {
95
141
  distance: number;
96
142
  };
97
- /** @deprecated Use orbitFollowStep — kept for older call sites. */
143
+ /** @deprecated Use orbitFollowStep — kept for older call sites.
144
+ * @internal
145
+ */
98
146
  export declare function cameraFollowStep(input: {
99
147
  camera: Vec3;
100
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);
@@ -25,24 +29,54 @@ export const DEFAULT_ORBIT_CAMERA = {
25
29
  distanceSmoothing: 5,
26
30
  minPolarAngle: 0.12,
27
31
  maxPolarAngle: Math.PI / 2.05,
32
+ collision: { enabled: false, padding: 0.35, minTargetDistance: 1.2 },
28
33
  };
29
34
  /** Run simulation/movement before orbit follow so poses are current. */
30
35
  export const GAME_SIM_FRAME_PRIORITY = 0;
31
36
  /** Orbit follow reads the latest entity pose after GAME_SIM_FRAME_PRIORITY. */
32
37
  export const ORBIT_CAMERA_FRAME_PRIORITY = -1;
33
- /** Frame-rate independent exponential smoothing factor in [0, 1]. */
38
+ /** Frame-rate independent exponential smoothing factor in [0, 1].
39
+ * @internal
40
+ */
34
41
  export function smoothBlend(deltaSeconds, speed) {
35
42
  return 1 - Math.exp(-speed * deltaSeconds);
36
43
  }
44
+ /** @internal */
37
45
  export function resolveOrbitCameraConfig(patch) {
38
- return { ...DEFAULT_ORBIT_CAMERA, ...patch };
46
+ const resolved = {
47
+ ...DEFAULT_ORBIT_CAMERA,
48
+ ...patch,
49
+ collision: { ...DEFAULT_ORBIT_CAMERA.collision, ...patch?.collision },
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;
39
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 */
40
73
  export function resolveTargetSmoothing(config, dragging) {
41
74
  if (dragging) {
42
75
  return config.dragTargetSmoothing;
43
76
  }
44
77
  return config.targetSmoothing;
45
78
  }
79
+ /** @internal */
46
80
  export function resolveFollowTargetFromPosition(position, config) {
47
81
  const offset = config.targetOffset;
48
82
  return {
@@ -51,6 +85,7 @@ export function resolveFollowTargetFromPosition(position, config) {
51
85
  z: position[2] + (offset?.z ?? 0),
52
86
  };
53
87
  }
88
+ /** @internal */
54
89
  export function lerpVec3(from, to, blend) {
55
90
  return {
56
91
  x: from.x + (to.x - from.x) * blend,
@@ -58,14 +93,31 @@ export function lerpVec3(from, to, blend) {
58
93
  z: from.z + (to.z - from.z) * blend,
59
94
  };
60
95
  }
96
+ /** @internal */
61
97
  export function distanceBetween(a, b) {
62
98
  const dx = a.x - b.x;
63
99
  const dy = a.y - b.y;
64
100
  const dz = a.z - b.z;
65
101
  return Math.sqrt(dx * dx + dy * dy + dz * dz);
66
102
  }
103
+ /** @internal */
67
104
  export function seedOrbitFollowState(input) {
68
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
+ }
69
121
  return {
70
122
  target,
71
123
  camera: {
@@ -80,7 +132,8 @@ export function seedOrbitFollowState(input) {
80
132
  * Pure orbit follow step — smooth target tracking, camera carries target delta
81
133
  * (OrbitControls alone keeps camera fixed when only target moves), optional
82
134
  * distance lock. Call each frame before OrbitControls.update() in the shell.
83
- */
135
+ * @internal
136
+ */
84
137
  export function orbitFollowStep(input) {
85
138
  const target = { ...input.state.target };
86
139
  const camera = { ...input.state.camera };
@@ -119,7 +172,9 @@ export function orbitFollowStep(input) {
119
172
  }
120
173
  return { target, camera, lockedDistance, distance: distanceBetween(camera, target) };
121
174
  }
122
- /** @deprecated Use orbitFollowStep — kept for older call sites. */
175
+ /** @deprecated Use orbitFollowStep — kept for older call sites.
176
+ * @internal
177
+ */
123
178
  export function cameraFollowStep(input) {
124
179
  const state = {
125
180
  target: input.previousTarget ?? input.target,