@jgengine/shell 0.10.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 (234) hide show
  1. package/CHANGELOG.md +13 -2
  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 +0 -12
  7. package/dist/GamePlayerShell.js +88 -1043
  8. package/dist/GameUiPreview.js +0 -2
  9. package/dist/audio/audioEngine.js +30 -8
  10. package/dist/audio/audioWire.d.ts +22 -0
  11. package/dist/audio/audioWire.js +19 -0
  12. package/dist/behaviour.d.ts +2 -1
  13. package/dist/behaviour.js +2 -1
  14. package/dist/behaviourAttach.d.ts +1 -0
  15. package/dist/behaviourAttach.js +1 -0
  16. package/dist/behaviourDriver.d.ts +1 -0
  17. package/dist/behaviourDriver.js +1 -0
  18. package/dist/boundActionDispatch.d.ts +13 -0
  19. package/dist/boundActionDispatch.js +37 -0
  20. package/dist/camera/GameCameraRig.d.ts +5 -2
  21. package/dist/camera/GameCameraRig.js +2 -2
  22. package/dist/camera/GameFirstPersonCamera.d.ts +9 -2
  23. package/dist/camera/GameFirstPersonCamera.js +8 -3
  24. package/dist/camera/GameInspectionCamera.d.ts +1 -1
  25. package/dist/camera/GameInspectionCamera.js +2 -2
  26. package/dist/camera/GameOrbitCamera.js +24 -9
  27. package/dist/camera/cameraBlendMath.d.ts +3 -0
  28. package/dist/camera/cameraBlendMath.js +3 -0
  29. package/dist/camera/fovPreference.d.ts +6 -1
  30. package/dist/camera/fovPreference.js +6 -1
  31. package/dist/camera/index.d.ts +4 -7
  32. package/dist/camera/index.js +4 -7
  33. package/dist/camera/inspectionCameraMath.d.ts +7 -2
  34. package/dist/camera/inspectionCameraMath.js +7 -2
  35. package/dist/camera/orbitCameraMath.d.ts +36 -6
  36. package/dist/camera/orbitCameraMath.js +56 -6
  37. package/dist/camera/rigMath.d.ts +94 -24
  38. package/dist/camera/rigMath.js +100 -24
  39. package/dist/camera/rigResolve.d.ts +4 -2
  40. package/dist/camera/rigResolve.js +4 -2
  41. package/dist/camera/shakeChannel.d.ts +6 -2
  42. package/dist/camera/shakeChannel.js +6 -2
  43. package/dist/camera/shakeChannelMath.d.ts +1 -0
  44. package/dist/camera/shakeChannelMath.js +1 -0
  45. package/dist/cartridge/runtime.d.ts +15 -0
  46. package/dist/cartridge/runtime.js +556 -0
  47. package/dist/cartridge/spec.d.ts +188 -0
  48. package/dist/cartridge/spec.js +19 -0
  49. package/dist/cartridge/validate.d.ts +2 -0
  50. package/dist/cartridge/validate.js +78 -0
  51. package/dist/cartridge.d.ts +1 -1
  52. package/dist/cartridge.js +3 -3
  53. package/dist/commandSink.d.ts +8 -3
  54. package/dist/commandSink.js +8 -3
  55. package/dist/defineGame.js +25 -6
  56. package/dist/devtools/ColPanel.d.ts +1 -0
  57. package/dist/devtools/ColPanel.js +14 -0
  58. package/dist/devtools/DevtoolsOverlay.d.ts +7 -3
  59. package/dist/devtools/DevtoolsOverlay.js +19 -450
  60. package/dist/devtools/KeysPanel.d.ts +4 -0
  61. package/dist/devtools/KeysPanel.js +31 -0
  62. package/dist/devtools/LogsPanel.d.ts +1 -0
  63. package/dist/devtools/LogsPanel.js +12 -0
  64. package/dist/devtools/NetPanel.d.ts +4 -0
  65. package/dist/devtools/NetPanel.js +10 -0
  66. package/dist/devtools/PerfPanel.d.ts +4 -0
  67. package/dist/devtools/PerfPanel.js +53 -0
  68. package/dist/devtools/TunePanel.d.ts +3 -0
  69. package/dist/devtools/TunePanel.js +309 -0
  70. package/dist/devtools/agentBridge.d.ts +41 -0
  71. package/dist/devtools/agentBridge.js +262 -0
  72. package/dist/devtools/collisionDebug.d.ts +6 -0
  73. package/dist/devtools/collisionDebug.js +6 -0
  74. package/dist/devtools/collisionDebugMath.d.ts +8 -2
  75. package/dist/devtools/collisionDebugMath.js +24 -5
  76. package/dist/devtools/devtoolsOverrides.d.ts +6 -0
  77. package/dist/devtools/devtoolsOverrides.js +48 -0
  78. package/dist/devtools/panelAtoms.d.ts +9 -0
  79. package/dist/devtools/panelAtoms.js +10 -0
  80. package/dist/devtools/perfDiagnose.d.ts +2 -0
  81. package/dist/devtools/perfDiagnose.js +43 -0
  82. package/dist/diagnostics/RuntimeDiagnostics.d.ts +29 -0
  83. package/dist/diagnostics/RuntimeDiagnostics.js +69 -0
  84. package/dist/drivers/FrameDriver.d.ts +37 -0
  85. package/dist/drivers/FrameDriver.js +232 -0
  86. package/dist/drivers/HudOnlyDriver.d.ts +16 -0
  87. package/dist/drivers/HudOnlyDriver.js +60 -0
  88. package/dist/environment/Daylight.d.ts +23 -7
  89. package/dist/environment/Daylight.js +106 -13
  90. package/dist/environment/EnvironmentScene.js +32 -6
  91. package/dist/environment/VolumetricClouds.d.ts +17 -0
  92. package/dist/environment/VolumetricClouds.js +202 -0
  93. package/dist/environment/daylightCycle.d.ts +3 -1
  94. package/dist/environment/daylightCycle.js +3 -1
  95. package/dist/environment/groundPadMath.d.ts +3 -0
  96. package/dist/environment/groundPadMath.js +3 -0
  97. package/dist/environment/index.d.ts +1 -0
  98. package/dist/environment/index.js +1 -0
  99. package/dist/environment/skyLightingPolicy.d.ts +2 -0
  100. package/dist/environment/skyLightingPolicy.js +2 -0
  101. package/dist/hotbarActions.d.ts +26 -0
  102. package/dist/hotbarActions.js +27 -0
  103. package/dist/inputSink.d.ts +16 -4
  104. package/dist/inputSink.js +57 -7
  105. package/dist/materialOverride.d.ts +10 -0
  106. package/dist/materialOverride.js +15 -3
  107. package/dist/multiplayer.js +8 -2
  108. package/dist/postfx/PostProcessing.js +47 -9
  109. package/dist/postfx/gradeShader.js +9 -1
  110. package/dist/registry.d.ts +3 -2
  111. package/dist/render/EnvironmentLighting.d.ts +10 -0
  112. package/dist/render/EnvironmentLighting.js +34 -0
  113. package/dist/render/SceneLighting.d.ts +7 -0
  114. package/dist/render/SceneLighting.js +18 -0
  115. package/dist/render/SceneModels.d.ts +14 -0
  116. package/dist/render/SceneModels.js +337 -0
  117. package/dist/render/defaultSurface.d.ts +29 -0
  118. package/dist/render/defaultSurface.js +101 -0
  119. package/dist/render/modelLoad.d.ts +24 -0
  120. package/dist/render/modelLoad.js +73 -0
  121. package/dist/render/modelRender.d.ts +11 -0
  122. package/dist/render/modelRender.js +15 -0
  123. package/dist/render/resolveModel.d.ts +39 -3
  124. package/dist/render/resolveModel.js +79 -2
  125. package/dist/render/useEntityRenderCues.d.ts +13 -0
  126. package/dist/render/useEntityRenderCues.js +62 -0
  127. package/dist/scatter/InstancedScatter.d.ts +24 -0
  128. package/dist/scatter/InstancedScatter.js +130 -0
  129. package/dist/scatter/scatterModels.d.ts +25 -0
  130. package/dist/scatter/scatterModels.js +61 -0
  131. package/dist/scatter/scatterProxies.d.ts +6 -0
  132. package/dist/scatter/scatterProxies.js +94 -0
  133. package/dist/scatter.d.ts +1 -0
  134. package/dist/scatter.js +1 -0
  135. package/dist/scene/AuthoredScene.d.ts +82 -0
  136. package/dist/scene/AuthoredScene.js +173 -0
  137. package/dist/scene/GeneratedAssetRenderer.d.ts +14 -0
  138. package/dist/scene/GeneratedAssetRenderer.js +59 -0
  139. package/dist/scene/GrassFieldRenderer.d.ts +2 -0
  140. package/dist/scene/GrassFieldRenderer.js +25 -0
  141. package/dist/scene/SoilPatchRenderer.d.ts +2 -0
  142. package/dist/scene/SoilPatchRenderer.js +21 -0
  143. package/dist/scene/StudioStage.d.ts +37 -0
  144. package/dist/scene/StudioStage.js +42 -0
  145. package/dist/scene/WaterRenderer.d.ts +2 -0
  146. package/dist/scene/WaterRenderer.js +37 -0
  147. package/dist/scene/builtinSceneKindRenderers.d.ts +2 -0
  148. package/dist/scene/builtinSceneKindRenderers.js +22 -0
  149. package/dist/scene/sceneKindRenderers.d.ts +28 -0
  150. package/dist/scene/sceneKindRenderers.js +17 -0
  151. package/dist/scene.d.ts +1 -0
  152. package/dist/scene.js +1 -0
  153. package/dist/settings/appliedSettings.d.ts +5 -0
  154. package/dist/settings/appliedSettings.js +9 -0
  155. package/dist/settings/settingsController.d.ts +2 -0
  156. package/dist/settings/settingsController.js +22 -1
  157. package/dist/shellConstants.d.ts +4 -0
  158. package/dist/shellConstants.js +4 -0
  159. package/dist/shellMovement.d.ts +8 -0
  160. package/dist/shellMovement.js +11 -0
  161. package/dist/shellPointer.d.ts +20 -0
  162. package/dist/shellPointer.js +28 -0
  163. package/dist/structures/PlacementGhost.d.ts +4 -0
  164. package/dist/structures/PlacementGhost.js +4 -0
  165. package/dist/structures/TransformGizmo.d.ts +43 -0
  166. package/dist/structures/TransformGizmo.js +55 -0
  167. package/dist/structures/index.d.ts +1 -0
  168. package/dist/structures/index.js +1 -0
  169. package/dist/terrain/GrassField.js +1 -1
  170. package/dist/terrain/TerraformBrushCursor.js +3 -0
  171. package/dist/terrain/grassBudget.d.ts +11 -2
  172. package/dist/terrain/grassBudget.js +20 -4
  173. package/dist/terrain/grassGeometry.d.ts +3 -0
  174. package/dist/terrain/grassGeometry.js +3 -0
  175. package/dist/terrain/grassMaterial.d.ts +2 -0
  176. package/dist/terrain/grassMaterial.js +2 -0
  177. package/dist/terrain/random.d.ts +3 -0
  178. package/dist/terrain/random.js +3 -0
  179. package/dist/terrain/soilPatchMaterial.d.ts +10 -0
  180. package/dist/terrain/soilPatchMaterial.js +109 -0
  181. package/dist/terrain/terrainDetailMaterial.d.ts +19 -2
  182. package/dist/terrain/terrainDetailMaterial.js +72 -5
  183. package/dist/terrain/terrainMath.d.ts +8 -1
  184. package/dist/terrain/terrainMath.js +8 -1
  185. package/dist/touch/TouchControlsOverlay.d.ts +9 -5
  186. package/dist/touch/TouchControlsOverlay.js +201 -22
  187. package/dist/useShellMultiplayerSync.d.ts +9 -0
  188. package/dist/useShellMultiplayerSync.js +98 -0
  189. package/dist/visibility/CullingProvider.js +1 -1
  190. package/dist/water/OceanConfig.d.ts +2 -0
  191. package/dist/water/OceanConfig.js +2 -0
  192. package/dist/weather/fireSpreadPose.d.ts +1 -0
  193. package/dist/weather/fireSpreadPose.js +1 -0
  194. package/dist/weather/weatherGeometry.d.ts +1 -0
  195. package/dist/weather/weatherGeometry.js +1 -0
  196. package/dist/weather/weatherMath.d.ts +3 -0
  197. package/dist/weather/weatherMath.js +3 -0
  198. package/dist/weather/weatherUniforms.d.ts +3 -0
  199. package/dist/weather/weatherUniforms.js +3 -0
  200. package/dist/world/RetainedVfx.d.ts +10 -0
  201. package/dist/world/RetainedVfx.js +136 -0
  202. package/dist/world/WorldHud.d.ts +49 -2
  203. package/dist/world/WorldHud.js +87 -2
  204. package/dist/world/WorldScene.d.ts +21 -0
  205. package/dist/world/WorldScene.js +159 -0
  206. package/dist/world/WorldVfx.d.ts +9 -0
  207. package/dist/world/WorldVfx.js +184 -0
  208. package/dist/world/entityPose.d.ts +2 -0
  209. package/dist/world/entityPose.js +2 -0
  210. package/dist/world/floatTextStyle.d.ts +1 -0
  211. package/dist/world/floatTextStyle.js +1 -0
  212. package/dist/world/telegraphPulse.d.ts +1 -0
  213. package/dist/world/telegraphPulse.js +1 -0
  214. package/dist/world/worldBarSamples.d.ts +25 -0
  215. package/dist/world/worldBarSamples.js +38 -0
  216. package/dist/worldSky.d.ts +5 -0
  217. package/dist/worldSky.js +11 -0
  218. package/dist/worldSync.d.ts +2 -1
  219. package/dist/worldSync.js +2 -1
  220. package/package.json +6 -6
  221. package/skills/jgengine/SKILL.md +51 -0
  222. package/skills/jgengine/api.md +434 -0
  223. package/skills/jgengine/capabilities.md +29 -0
  224. package/skills/jgengine/reference-cartridge.md +72 -0
  225. package/skills/jgengine-ui/SKILL.md +36 -0
  226. package/skills/jgengine-ui/api.md +1852 -0
  227. package/skills/jgengine-ui/capabilities.md +142 -0
  228. package/skills/jgengine-ui/reference.md +500 -0
  229. package/skills/jgengine-ui/references/visual-scorecard.md +73 -0
  230. package/skills/jgengine-world/SKILL.md +38 -0
  231. package/skills/jgengine-world/api.md +2652 -0
  232. package/skills/jgengine-world/capabilities.md +370 -0
  233. package/skills/jgengine-world/reference.md +222 -0
  234. package/llms.txt +0 -2761
@@ -37,7 +37,6 @@ export const defaultUiScenario = (ctx, playable) => {
37
37
  export function GameUiPreview({ playable, scenario = defaultUiScenario, }) {
38
38
  const [ctx, setCtx] = useState(null);
39
39
  useEffect(() => {
40
- playable.game.scene.clear();
41
40
  const context = createGameContext({
42
41
  definition: playable.game,
43
42
  content: playable.content,
@@ -53,7 +52,6 @@ export function GameUiPreview({ playable, scenario = defaultUiScenario, }) {
53
52
  }
54
53
  setCtx(context);
55
54
  return () => {
56
- playable.game.scene.clear();
57
55
  setCtx(null);
58
56
  };
59
57
  }, [playable, scenario]);
@@ -1,4 +1,5 @@
1
1
  import { distance3, resolveEmitterGain } from "@jgengine/core/audio/audioFalloff";
2
+ import { createDisposer } from "@jgengine/core/game/defineGame";
2
3
  import { MusicDirector } from "./musicDirector.js";
3
4
  import { createNoiseBuffer, realizeSynthPatch } from "./synthEngine.js";
4
5
  function createNoopEngine() {
@@ -74,6 +75,7 @@ export function createAudioEngine(config = {}) {
74
75
  return pending;
75
76
  }
76
77
  let listenerPosition = { x: 0, y: 0, z: 0 };
78
+ const activeSpatialUpdaters = new Set();
77
79
  function playInternal(soundId, position, loop) {
78
80
  const sound = sounds[soundId];
79
81
  if (sound === undefined)
@@ -89,35 +91,54 @@ export function createAudioEngine(config = {}) {
89
91
  }
90
92
  if (sound.url === undefined)
91
93
  return null;
92
- let gainNode = null;
93
- let stopped = false;
94
+ const disposer = createDisposer();
95
+ let liveGain = null;
96
+ function updateGain() {
97
+ if (liveGain !== null) {
98
+ liveGain.gain.value = resolveEmitterGain(distance3(currentPosition, listenerPosition), sound, 1);
99
+ }
100
+ }
94
101
  void loadBuffer(sound.url).then((buffer) => {
95
- if (stopped || buffer === null)
102
+ if (buffer === null)
96
103
  return;
97
104
  const sourceNode = context.createBufferSource();
98
105
  sourceNode.buffer = buffer;
99
106
  sourceNode.loop = loop || (sound.loop ?? false);
100
- gainNode = context.createGain();
107
+ const gainNode = context.createGain();
101
108
  gainNode.gain.value = resolveEmitterGain(distance3(currentPosition, listenerPosition), sound, 1);
102
109
  sourceNode.connect(gainNode);
103
110
  gainNode.connect(bus);
104
111
  sourceNode.start();
112
+ liveGain = gainNode;
113
+ disposer.onDispose(() => {
114
+ try {
115
+ sourceNode.stop();
116
+ }
117
+ catch {
118
+ }
119
+ sourceNode.disconnect();
120
+ gainNode.disconnect();
121
+ liveGain = null;
122
+ });
105
123
  });
124
+ if (loop)
125
+ activeSpatialUpdaters.add(updateGain);
106
126
  return {
107
127
  setPosition(next) {
108
128
  currentPosition = next;
109
- if (gainNode !== null) {
110
- gainNode.gain.value = resolveEmitterGain(distance3(currentPosition, listenerPosition), sound, 1);
111
- }
129
+ updateGain();
112
130
  },
113
131
  stop() {
114
- stopped = true;
132
+ activeSpatialUpdaters.delete(updateGain);
133
+ disposer.dispose();
115
134
  },
116
135
  };
117
136
  }
118
137
  return {
119
138
  setListenerPose(position) {
120
139
  listenerPosition = position;
140
+ for (const updateGain of activeSpatialUpdaters)
141
+ updateGain();
121
142
  },
122
143
  playOneShot(soundId, position) {
123
144
  void context.resume().catch(() => undefined);
@@ -140,6 +161,7 @@ export function createAudioEngine(config = {}) {
140
161
  void context.resume().catch(() => undefined);
141
162
  },
142
163
  dispose() {
164
+ activeSpatialUpdaters.clear();
143
165
  director?.dispose();
144
166
  void context.close().catch(() => undefined);
145
167
  },
@@ -0,0 +1,22 @@
1
+ import type { AudioEngine } from "./audioEngine.js";
2
+ type AudioPlayPayload = {
3
+ sound: string;
4
+ at?: readonly [number, number, number];
5
+ };
6
+ type AudioMusicPayload = {
7
+ theme: string | null;
8
+ transpose?: number;
9
+ };
10
+ /** Minimal event bus the shell wires to the audio engine. @internal */
11
+ export type AudioEventBus = {
12
+ on(event: "audio.play", handler: (payload: AudioPlayPayload) => void): () => void;
13
+ on(event: "audio.music", handler: (payload: AudioMusicPayload) => void): () => void;
14
+ on(event: "audio.resume", handler: () => void): () => void;
15
+ };
16
+ /**
17
+ * Subscribe game audio events to the shell audio engine.
18
+ * Returns a single unsubscribe that tears all three listeners down.
19
+ * @internal
20
+ */
21
+ export declare function attachAudioEventWire(events: AudioEventBus, audioEngine: Pick<AudioEngine, "playOneShot" | "playMusic" | "resume">): () => void;
22
+ export {};
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Subscribe game audio events to the shell audio engine.
3
+ * Returns a single unsubscribe that tears all three listeners down.
4
+ * @internal
5
+ */
6
+ export function attachAudioEventWire(events, audioEngine) {
7
+ const offPlay = events.on("audio.play", ({ sound, at }) => {
8
+ audioEngine.playOneShot(sound, at === undefined ? undefined : { x: at[0], y: at[1], z: at[2] });
9
+ });
10
+ const offMusic = events.on("audio.music", ({ theme, transpose }) => {
11
+ audioEngine.playMusic(theme, transpose === undefined ? undefined : { transpose });
12
+ });
13
+ const offResume = events.on("audio.resume", () => audioEngine.resume());
14
+ return () => {
15
+ offPlay();
16
+ offMusic();
17
+ offResume();
18
+ };
19
+ }
@@ -8,5 +8,6 @@ export { attachObject3D, Object3DBehaviour } from "./behaviourAttach.js";
8
8
  * receive — pass the game clock's scaling to keep behaviours on game time, or
9
9
  * omit for real-time seconds. Games driving `world.update` themselves from
10
10
  * `loop.onTick` should not also render this.
11
- */
11
+ * @internal
12
+ */
12
13
  export declare function useBehaviourWorld(world: BehaviourWorld, scaleDt?: (rawDt: number) => number): void;
package/dist/behaviour.js CHANGED
@@ -10,7 +10,8 @@ export { attachObject3D, Object3DBehaviour } from "./behaviourAttach.js";
10
10
  * receive — pass the game clock's scaling to keep behaviours on game time, or
11
11
  * omit for real-time seconds. Games driving `world.update` themselves from
12
12
  * `loop.onTick` should not also render this.
13
- */
13
+ * @internal
14
+ */
14
15
  export function useBehaviourWorld(world, scaleDt) {
15
16
  const driverRef = useRef(createBehaviourWorldDriver(world));
16
17
  useEffect(() => {
@@ -1,5 +1,6 @@
1
1
  import type { Object3D } from "three";
2
2
  import { Behaviour, type BehaviourWorld } from "@jgengine/core/behaviour/behaviour";
3
+ /** @internal */
3
4
  export declare class Object3DBehaviour extends Behaviour {
4
5
  /** @internal */ _object: Object3D | null;
5
6
  get object(): Object3D;
@@ -1,4 +1,5 @@
1
1
  import { Behaviour } from "@jgengine/core/behaviour/behaviour";
2
+ /** @internal */
2
3
  export class Object3DBehaviour extends Behaviour {
3
4
  /** @internal */ _object = null;
4
5
  get object() {
@@ -1,4 +1,5 @@
1
1
  import type { BehaviourWorld } from "@jgengine/core/behaviour/behaviour";
2
+ /** @internal */
2
3
  export declare function createBehaviourWorldDriver(world: BehaviourWorld): {
3
4
  start(): void;
4
5
  stop(): void;
@@ -1,3 +1,4 @@
1
+ /** @internal */
1
2
  export function createBehaviourWorldDriver(world) {
2
3
  let running = false;
3
4
  return {
@@ -0,0 +1,13 @@
1
+ import { type ActionStateTracker } from "@jgengine/core/input/actionBindings";
2
+ import type { Aim } from "@jgengine/core/scene/spatial";
3
+ import type { GameContext } from "@jgengine/core/runtime/gameContext";
4
+ import type { ActionCodesMap } from "@jgengine/core/input/actionBindings";
5
+ import { type CommandSink } from "./commandSink.js";
6
+ /** Actions the shell consumes natively and never routes to a same-named command. @internal */
7
+ export declare const RESERVED_INPUT_ACTIONS: ReadonlySet<string>;
8
+ /** Actions from `input` currently held down, for `ctx.input.publish` (#164.1); includes reserved movement/jump actions. @internal */
9
+ export declare function heldActionsFor(tracker: Pick<ActionStateTracker<string>, "isDown">, actions: readonly string[]): string[];
10
+ /** Whether a bound action should fire this frame: on press, or on repeat interval while held (shared by `FrameDriver` and `HudOnlyDriver`). @internal */
11
+ export declare function shouldFireBoundAction(tracker: Pick<ActionStateTracker<string>, "isDown" | "wasPressed">, action: string, input: ActionCodesMap | undefined, repeatFiredAt: ReadonlyMap<string, number>, now: number): boolean;
12
+ /** Resolves and runs the command bound to `action` via the shell's action→command convention (shared by `FrameDriver` and `HudOnlyDriver`). @internal */
13
+ export declare function dispatchBoundAction(ctx: GameContext, action: string, yaw: number, pitch: number, aim: Aim, reserved?: ReadonlySet<string>, sink?: CommandSink): void;
@@ -0,0 +1,37 @@
1
+ import { actionRepeatMs, resolveActionCommand, shouldDispatchAction, } from "@jgengine/core/input/actionBindings";
2
+ import { localCommandSink } from "./commandSink.js";
3
+ /** Actions the shell consumes natively and never routes to a same-named command. @internal */
4
+ export const RESERVED_INPUT_ACTIONS = new Set([
5
+ "moveForward",
6
+ "moveBack",
7
+ "moveLeft",
8
+ "moveRight",
9
+ "turnLeft",
10
+ "turnRight",
11
+ "sprint",
12
+ "jump",
13
+ "tabTarget",
14
+ "clearTarget",
15
+ "useAbility",
16
+ "interact",
17
+ ]);
18
+ /** Actions from `input` currently held down, for `ctx.input.publish` (#164.1); includes reserved movement/jump actions. @internal */
19
+ export function heldActionsFor(tracker, actions) {
20
+ return actions.filter((action) => tracker.isDown(action));
21
+ }
22
+ /** Whether a bound action should fire this frame: on press, or on repeat interval while held (shared by `FrameDriver` and `HudOnlyDriver`). @internal */
23
+ export function shouldFireBoundAction(tracker, action, input, repeatFiredAt, now) {
24
+ return shouldDispatchAction({
25
+ pressed: tracker.wasPressed(action),
26
+ down: tracker.isDown(action),
27
+ repeatMs: actionRepeatMs(input?.[action]),
28
+ lastFiredAt: repeatFiredAt.get(action) ?? null,
29
+ now,
30
+ });
31
+ }
32
+ /** Resolves and runs the command bound to `action` via the shell's action→command convention (shared by `FrameDriver` and `HudOnlyDriver`). @internal */
33
+ export function dispatchBoundAction(ctx, action, yaw, pitch, aim, reserved = RESERVED_INPUT_ACTIONS, sink = localCommandSink(ctx)) {
34
+ const command = resolveActionCommand(action, (name) => ctx.game.commands.has(name), reserved);
35
+ if (command !== null)
36
+ sink.run(command, { yaw, pitch, aim });
37
+ }
@@ -1,6 +1,7 @@
1
- import { type MutableRefObject } from "react";
1
+ import { type ComponentType, type MutableRefObject } from "react";
2
2
  import type { GameCameraConfig } from "@jgengine/core/game/playableGame";
3
3
  import type { CameraDirector } from "@jgengine/core/runtime/cameraDirector";
4
+ import { type ViewmodelProps } from "./GameFirstPersonCamera.js";
4
5
  export { resolveRigKind } from "./rigResolve.js";
5
6
  export interface GameCameraRigProps {
6
7
  yawRef: MutableRefObject<number>;
@@ -12,5 +13,7 @@ export interface GameCameraRigProps {
12
13
  panKeysEnabled?: boolean;
13
14
  /** Runtime follow/cinematic override (#196.2). While present it wins over the static `config` for any field it reports non-`undefined`/non-`null`; absent (or an all-`undefined` snapshot) is a no-op. */
14
15
  director?: CameraDirector;
16
+ /** Custom first-person viewmodel (#542); read only when the resolved rig is `"first"`. */
17
+ viewmodel?: ComponentType<ViewmodelProps>;
15
18
  }
16
- export declare function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerControls, panKeysEnabled, director, }: GameCameraRigProps): import("react").JSX.Element;
19
+ export declare function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerControls, panKeysEnabled, director, viewmodel, }: GameCameraRigProps): import("react").JSX.Element;
@@ -8,7 +8,7 @@ import { resolveDirectedCamera } from "./rigMath.js";
8
8
  import { resolveRigKind, turntableAsObserver } from "./rigResolve.js";
9
9
  import { CameraShakeContext, createCameraShakeChannel } from "./shakeChannel.js";
10
10
  export { resolveRigKind } from "./rigResolve.js";
11
- export function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerControls, panKeysEnabled, director, }) {
11
+ export function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerControls, panKeysEnabled, director, viewmodel, }) {
12
12
  const channel = useMemo(() => createCameraShakeChannel(config?.shake?.decayPerSecond), [config?.shake?.decayPerSecond]);
13
13
  const [, notifyDirectorChange] = useReducer((count) => count + 1, 0);
14
14
  useEffect(() => {
@@ -27,7 +27,7 @@ export function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerC
27
27
  const kind = resolveRigKind(config);
28
28
  switch (kind) {
29
29
  case "first":
30
- return (_jsx(GameFirstPersonCamera, { yawRef: yawRef, pitchRef: pitchRef, config: config?.firstPerson, followEntityId: followEntityId ?? undefined }));
30
+ return (_jsx(GameFirstPersonCamera, { yawRef: yawRef, pitchRef: pitchRef, config: config?.firstPerson, followEntityId: followEntityId ?? undefined, viewmodel: viewmodel }));
31
31
  case "topDown":
32
32
  return _jsx(TopDownRig, { yawRef: yawRef, pitchRef: pitchRef, config: config, followEntityId: followEntityId });
33
33
  case "rts":
@@ -1,12 +1,19 @@
1
- import { type MutableRefObject } from "react";
1
+ import { type ComponentType, type MutableRefObject } from "react";
2
2
  import * as THREE from "three";
3
3
  import type { FirstPersonCameraConfig } from "@jgengine/core/game/playableGame";
4
+ import type { EntityRenderCues } from "@jgengine/core/combat/renderCues";
4
5
  /** World position of the first-person weapon muzzle, or false when no viewmodel is mounted. */
5
6
  export declare function readFirstPersonMuzzle(target: THREE.Vector3): boolean;
7
+ /** Props handed to a custom viewmodel component (#542): a live cue ref (velocity/bob/firing/reloading/recoil/hit) for the followed entity, driven from your own `useFrame` — read `cuesRef.current` there rather than storing it as render state. */
8
+ export interface ViewmodelProps {
9
+ cuesRef: MutableRefObject<EntityRenderCues>;
10
+ }
6
11
  export interface GameFirstPersonCameraProps {
7
12
  yawRef: MutableRefObject<number>;
8
13
  pitchRef: MutableRefObject<number>;
9
14
  config?: FirstPersonCameraConfig;
10
15
  followEntityId?: string;
16
+ /** Custom viewmodel component replacing the built-in three-mesh gun, rendered inside the same camera-locked, muzzle-tracked anchor. Ignored when `config.viewmodel === false`. */
17
+ viewmodel?: ComponentType<ViewmodelProps>;
11
18
  }
12
- export declare function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId, }: GameFirstPersonCameraProps): import("react").JSX.Element | null;
19
+ export declare function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId, viewmodel, }: GameFirstPersonCameraProps): import("react").JSX.Element | null;
@@ -5,6 +5,7 @@ import * as THREE from "three";
5
5
  import { DEFAULT_EYE_HEIGHT } from "@jgengine/core/combat/shotOrigin";
6
6
  import { useGameContext } from "@jgengine/react/provider";
7
7
  import { usePlayer } from "@jgengine/react/hooks";
8
+ import { useEntityRenderCues } from "../render/useEntityRenderCues.js";
8
9
  import { usePlayerFov } from "./PlayerFov.js";
9
10
  import { GAME_SIM_FRAME_PRIORITY, ORBIT_CAMERA_FRAME_PRIORITY } from "./orbitCameraMath.js";
10
11
  const DEFAULT_SENSITIVITY = 0.0025;
@@ -21,7 +22,7 @@ export function readFirstPersonMuzzle(target) {
21
22
  target.copy(muzzleWorld);
22
23
  return true;
23
24
  }
24
- export function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId, }) {
25
+ export function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId, viewmodel, }) {
25
26
  const eyeHeight = config?.eyeHeight ?? DEFAULT_EYE_HEIGHT;
26
27
  const sensitivity = config?.sensitivity ?? DEFAULT_SENSITIVITY;
27
28
  const maxPitch = config?.maxPitch ?? DEFAULT_MAX_PITCH;
@@ -32,6 +33,7 @@ export function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId
32
33
  const domElement = useThree((state) => state.gl.domElement);
33
34
  const followId = followEntityId ?? userId;
34
35
  const seededRef = useRef(false);
36
+ const cuesRef = useEntityRenderCues(followId);
35
37
  useEffect(() => {
36
38
  const requestLock = () => {
37
39
  if (window.matchMedia?.("(pointer: coarse)").matches)
@@ -73,9 +75,9 @@ export function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId
73
75
  }, ORBIT_CAMERA_FRAME_PRIORITY);
74
76
  if (config?.viewmodel === false)
75
77
  return null;
76
- return _jsx(FirstPersonViewmodel, { camera: camera });
78
+ return _jsx(FirstPersonViewmodel, { camera: camera, viewmodel: viewmodel, cuesRef: cuesRef });
77
79
  }
78
- function FirstPersonViewmodel({ camera }) {
80
+ function FirstPersonViewmodel({ camera, viewmodel: Viewmodel, cuesRef, }) {
79
81
  const groupRef = useRef(null);
80
82
  useEffect(() => () => {
81
83
  muzzleTracked = false;
@@ -92,5 +94,8 @@ function FirstPersonViewmodel({ camera }) {
92
94
  muzzleWorld.copy(MUZZLE_OFFSET).applyQuaternion(camera.quaternion).add(camera.position);
93
95
  muzzleTracked = true;
94
96
  }, GAME_SIM_FRAME_PRIORITY);
97
+ if (Viewmodel !== undefined) {
98
+ return (_jsx("group", { ref: groupRef, children: _jsx(Viewmodel, { cuesRef: cuesRef }) }));
99
+ }
95
100
  return (_jsxs("group", { ref: groupRef, children: [_jsxs("mesh", { position: [0, 0, -0.22], children: [_jsx("boxGeometry", { args: [0.09, 0.11, 0.55] }), _jsx("meshStandardMaterial", { color: "#22262d", metalness: 0.6, roughness: 0.35 })] }), _jsxs("mesh", { position: [0, -0.13, 0.04], rotation: [0.35, 0, 0], children: [_jsx("boxGeometry", { args: [0.08, 0.18, 0.11] }), _jsx("meshStandardMaterial", { color: "#33373f", metalness: 0.4, roughness: 0.5 })] }), _jsxs("mesh", { position: [0, 0.03, -0.52], rotation: [Math.PI / 2, 0, 0], children: [_jsx("cylinderGeometry", { args: [0.022, 0.022, 0.18, 10] }), _jsx("meshStandardMaterial", { color: "#0e0f12", metalness: 0.7, roughness: 0.3 })] })] }));
96
101
  }
@@ -2,5 +2,5 @@ import { type InspectionCameraConfig } from "./inspectionCameraMath.js";
2
2
  export interface GameInspectionCameraProps {
3
3
  config?: InspectionCameraConfig;
4
4
  }
5
- /** 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. */
5
+ /** 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. */
6
6
  export declare function GameInspectionCamera({ config: configPatch }: GameInspectionCameraProps): import("react").JSX.Element;
@@ -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);
@@ -31,5 +31,5 @@ export function GameInspectionCamera({ config: configPatch }) {
31
31
  camera.position.add(controls.target.clone().sub(previous));
32
32
  controls.update();
33
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: MOUSE.ROTATE, MIDDLE: MOUSE.PAN, RIGHT: MOUSE.PAN } }));
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 } }));
35
35
  }
@@ -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);
@@ -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;