@jgengine/shell 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/CHANGELOG.md +39 -2
  2. package/LICENSE +203 -661
  3. package/README.md +1 -1
  4. package/dist/CombatPresentation.d.ts +5 -0
  5. package/dist/CombatPresentation.js +8 -0
  6. package/dist/GameHost.d.ts +25 -1
  7. package/dist/GameHost.js +60 -4
  8. package/dist/GamePhaseStamp.d.ts +1 -0
  9. package/dist/GamePhaseStamp.js +9 -0
  10. package/dist/GamePlayer.d.ts +5 -0
  11. package/dist/GamePlayer.js +5 -0
  12. package/dist/GamePlayerShell.d.ts +5 -12
  13. package/dist/GamePlayerShell.js +67 -1332
  14. package/dist/GameUiPreview.js +3 -2
  15. package/dist/Shell3dPresentation.d.ts +63 -0
  16. package/dist/Shell3dPresentation.js +302 -0
  17. package/dist/ShellChrome.d.ts +51 -0
  18. package/dist/ShellChrome.js +51 -0
  19. package/dist/ShellHudPresentation.d.ts +41 -0
  20. package/dist/ShellHudPresentation.js +22 -0
  21. package/dist/audio/audioEngine.d.ts +4 -0
  22. package/dist/audio/audioEngine.js +128 -21
  23. package/dist/audio/audioWire.d.ts +40 -0
  24. package/dist/audio/audioWire.js +62 -0
  25. package/dist/audio/loopParams.d.ts +20 -0
  26. package/dist/audio/loopParams.js +28 -0
  27. package/dist/behaviour.d.ts +2 -1
  28. package/dist/behaviour.js +2 -1
  29. package/dist/behaviourAttach.d.ts +1 -0
  30. package/dist/behaviourAttach.js +1 -0
  31. package/dist/behaviourDriver.d.ts +1 -0
  32. package/dist/behaviourDriver.js +1 -0
  33. package/dist/boundActionDispatch.d.ts +13 -0
  34. package/dist/boundActionDispatch.js +37 -0
  35. package/dist/camera/GameCameraRig.d.ts +5 -2
  36. package/dist/camera/GameCameraRig.js +8 -3
  37. package/dist/camera/GameFirstPersonCamera.d.ts +9 -2
  38. package/dist/camera/GameFirstPersonCamera.js +8 -3
  39. package/dist/camera/GameInspectionCamera.d.ts +1 -1
  40. package/dist/camera/GameInspectionCamera.js +2 -2
  41. package/dist/camera/GameOrbitCamera.js +24 -9
  42. package/dist/camera/cameraBlendMath.d.ts +3 -0
  43. package/dist/camera/cameraBlendMath.js +3 -0
  44. package/dist/camera/cameraRigs.d.ts +3 -1
  45. package/dist/camera/cameraRigs.js +21 -3
  46. package/dist/camera/fovPreference.d.ts +6 -1
  47. package/dist/camera/fovPreference.js +6 -1
  48. package/dist/camera/index.d.ts +4 -7
  49. package/dist/camera/index.js +4 -7
  50. package/dist/camera/inspectionCameraMath.d.ts +7 -2
  51. package/dist/camera/inspectionCameraMath.js +7 -2
  52. package/dist/camera/orbitCameraMath.d.ts +36 -6
  53. package/dist/camera/orbitCameraMath.js +56 -6
  54. package/dist/camera/rigMath.d.ts +114 -24
  55. package/dist/camera/rigMath.js +129 -24
  56. package/dist/camera/rigResolve.d.ts +4 -2
  57. package/dist/camera/rigResolve.js +4 -2
  58. package/dist/camera/shakeChannel.d.ts +6 -2
  59. package/dist/camera/shakeChannel.js +6 -2
  60. package/dist/camera/shakeChannelMath.d.ts +1 -0
  61. package/dist/camera/shakeChannelMath.js +1 -0
  62. package/dist/commandSink.d.ts +8 -3
  63. package/dist/commandSink.js +8 -3
  64. package/dist/defineGame.d.ts +15 -1
  65. package/dist/defineGame.js +50 -9
  66. package/dist/devtools/ColPanel.d.ts +1 -0
  67. package/dist/devtools/ColPanel.js +14 -0
  68. package/dist/devtools/DevtoolsOverlay.d.ts +11 -7
  69. package/dist/devtools/DevtoolsOverlay.js +47 -462
  70. package/dist/devtools/KeysPanel.d.ts +4 -0
  71. package/dist/devtools/KeysPanel.js +31 -0
  72. package/dist/devtools/LogsPanel.d.ts +1 -0
  73. package/dist/devtools/LogsPanel.js +12 -0
  74. package/dist/devtools/NetPanel.d.ts +4 -0
  75. package/dist/devtools/NetPanel.js +10 -0
  76. package/dist/devtools/PerfPanel.d.ts +4 -0
  77. package/dist/devtools/PerfPanel.js +53 -0
  78. package/dist/devtools/TunePanel.d.ts +3 -0
  79. package/dist/devtools/TunePanel.js +309 -0
  80. package/dist/devtools/agentBridge.d.ts +41 -0
  81. package/dist/devtools/agentBridge.js +266 -0
  82. package/dist/devtools/collisionDebug.d.ts +6 -0
  83. package/dist/devtools/collisionDebug.js +6 -0
  84. package/dist/devtools/collisionDebugMath.d.ts +8 -2
  85. package/dist/devtools/collisionDebugMath.js +24 -5
  86. package/dist/devtools/devtoolsOverrides.d.ts +6 -0
  87. package/dist/devtools/devtoolsOverrides.js +48 -0
  88. package/dist/devtools/panelAtoms.d.ts +9 -0
  89. package/dist/devtools/panelAtoms.js +10 -0
  90. package/dist/devtools/perfDiagnose.d.ts +2 -0
  91. package/dist/devtools/perfDiagnose.js +43 -0
  92. package/dist/diagnostics/RuntimeDiagnostics.d.ts +36 -0
  93. package/dist/diagnostics/RuntimeDiagnostics.js +80 -0
  94. package/dist/drivers/FrameDriver.d.ts +37 -0
  95. package/dist/drivers/FrameDriver.js +232 -0
  96. package/dist/drivers/HudOnlyDriver.d.ts +16 -0
  97. package/dist/drivers/HudOnlyDriver.js +60 -0
  98. package/dist/environment/Daylight.d.ts +23 -7
  99. package/dist/environment/Daylight.js +106 -13
  100. package/dist/environment/EnvironmentScene.js +44 -9
  101. package/dist/environment/VolumetricClouds.d.ts +17 -0
  102. package/dist/environment/VolumetricClouds.js +202 -0
  103. package/dist/environment/daylightCycle.d.ts +3 -1
  104. package/dist/environment/daylightCycle.js +3 -1
  105. package/dist/environment/groundPadMath.d.ts +3 -0
  106. package/dist/environment/groundPadMath.js +3 -0
  107. package/dist/environment/index.d.ts +1 -0
  108. package/dist/environment/index.js +1 -0
  109. package/dist/environment/skyLightingPolicy.d.ts +2 -0
  110. package/dist/environment/skyLightingPolicy.js +2 -0
  111. package/dist/environment/terrainGroundColor.d.ts +9 -0
  112. package/dist/environment/terrainGroundColor.js +22 -0
  113. package/dist/gameKit.d.ts +27 -0
  114. package/dist/gameKit.js +29 -0
  115. package/dist/hotbarActions.d.ts +26 -0
  116. package/dist/hotbarActions.js +27 -0
  117. package/dist/inputSink.d.ts +16 -4
  118. package/dist/inputSink.js +57 -7
  119. package/dist/map/MapMarkerBeacons.d.ts +3 -2
  120. package/dist/materialOverride.d.ts +10 -0
  121. package/dist/materialOverride.js +15 -3
  122. package/dist/multiplayer.js +8 -2
  123. package/dist/postfx/PostProcessing.d.ts +9 -1
  124. package/dist/postfx/PostProcessing.js +56 -11
  125. package/dist/postfx/gradeShader.js +9 -1
  126. package/dist/presentationResolve.d.ts +35 -0
  127. package/dist/presentationResolve.js +51 -0
  128. package/dist/registry.d.ts +4 -2
  129. package/dist/render/EnvironmentLighting.d.ts +10 -0
  130. package/dist/render/EnvironmentLighting.js +34 -0
  131. package/dist/render/SceneLighting.d.ts +7 -0
  132. package/dist/render/SceneLighting.js +18 -0
  133. package/dist/render/SceneModels.d.ts +14 -0
  134. package/dist/render/SceneModels.js +337 -0
  135. package/dist/render/assetBase.d.ts +17 -0
  136. package/dist/render/assetBase.js +35 -0
  137. package/dist/render/defaultSurface.d.ts +29 -0
  138. package/dist/render/defaultSurface.js +101 -0
  139. package/dist/render/modelLoad.d.ts +24 -0
  140. package/dist/render/modelLoad.js +76 -0
  141. package/dist/render/modelRender.d.ts +11 -0
  142. package/dist/render/modelRender.js +15 -0
  143. package/dist/render/resolveModel.d.ts +54 -3
  144. package/dist/render/resolveModel.js +116 -4
  145. package/dist/render/useEntityRenderCues.d.ts +13 -0
  146. package/dist/render/useEntityRenderCues.js +62 -0
  147. package/dist/scatter/InstancedScatter.d.ts +24 -0
  148. package/dist/scatter/InstancedScatter.js +135 -0
  149. package/dist/scatter/scatterModels.d.ts +25 -0
  150. package/dist/scatter/scatterModels.js +61 -0
  151. package/dist/scatter/scatterProxies.d.ts +6 -0
  152. package/dist/scatter/scatterProxies.js +118 -0
  153. package/dist/scatter.d.ts +1 -0
  154. package/dist/scatter.js +1 -0
  155. package/dist/scene/AuthoredScene.d.ts +84 -0
  156. package/dist/scene/AuthoredScene.js +178 -0
  157. package/dist/scene/CityRenderer.d.ts +2 -0
  158. package/dist/scene/CityRenderer.js +1031 -0
  159. package/dist/scene/GeneratedAssetRenderer.d.ts +14 -0
  160. package/dist/scene/GeneratedAssetRenderer.js +59 -0
  161. package/dist/scene/GrassFieldRenderer.d.ts +2 -0
  162. package/dist/scene/GrassFieldRenderer.js +73 -0
  163. package/dist/scene/PoleLineRenderer.d.ts +2 -0
  164. package/dist/scene/PoleLineRenderer.js +287 -0
  165. package/dist/scene/SoilPatchRenderer.d.ts +2 -0
  166. package/dist/scene/SoilPatchRenderer.js +84 -0
  167. package/dist/scene/StudioStage.d.ts +37 -0
  168. package/dist/scene/StudioStage.js +42 -0
  169. package/dist/scene/WaterRenderer.d.ts +2 -0
  170. package/dist/scene/WaterRenderer.js +61 -0
  171. package/dist/scene/builtinSceneKindRenderers.d.ts +2 -0
  172. package/dist/scene/builtinSceneKindRenderers.js +26 -0
  173. package/dist/scene/cityWindowMaterial.d.ts +16 -0
  174. package/dist/scene/cityWindowMaterial.js +81 -0
  175. package/dist/scene/sceneKindRenderers.d.ts +30 -0
  176. package/dist/scene/sceneKindRenderers.js +17 -0
  177. package/dist/scene.d.ts +1 -0
  178. package/dist/scene.js +1 -0
  179. package/dist/settings/appliedSettings.d.ts +7 -1
  180. package/dist/settings/appliedSettings.js +13 -2
  181. package/dist/settings/settingsController.d.ts +2 -0
  182. package/dist/settings/settingsController.js +22 -1
  183. package/dist/shellConstants.d.ts +4 -0
  184. package/dist/shellConstants.js +4 -0
  185. package/dist/shellMovement.d.ts +8 -0
  186. package/dist/shellMovement.js +11 -0
  187. package/dist/shellPointer.d.ts +20 -0
  188. package/dist/shellPointer.js +28 -0
  189. package/dist/structures/PlacementGhost.d.ts +4 -0
  190. package/dist/structures/PlacementGhost.js +4 -0
  191. package/dist/structures/TransformGizmo.d.ts +53 -0
  192. package/dist/structures/TransformGizmo.js +59 -0
  193. package/dist/structures/index.d.ts +1 -0
  194. package/dist/structures/index.js +1 -0
  195. package/dist/terrain/GrassField.d.ts +11 -3
  196. package/dist/terrain/GrassField.js +16 -6
  197. package/dist/terrain/TerraformBrushCursor.js +3 -0
  198. package/dist/terrain/grassBudget.d.ts +11 -2
  199. package/dist/terrain/grassBudget.js +20 -4
  200. package/dist/terrain/grassGeometry.d.ts +35 -0
  201. package/dist/terrain/grassGeometry.js +85 -20
  202. package/dist/terrain/grassMaterial.d.ts +20 -0
  203. package/dist/terrain/grassMaterial.js +68 -13
  204. package/dist/terrain/heightfieldGeometry.d.ts +34 -0
  205. package/dist/terrain/heightfieldGeometry.js +109 -0
  206. package/dist/terrain/index.d.ts +1 -0
  207. package/dist/terrain/index.js +1 -0
  208. package/dist/terrain/random.d.ts +3 -0
  209. package/dist/terrain/random.js +3 -0
  210. package/dist/terrain/soilPatchMaterial.d.ts +23 -0
  211. package/dist/terrain/soilPatchMaterial.js +147 -0
  212. package/dist/terrain/terrainDetailMaterial.d.ts +19 -2
  213. package/dist/terrain/terrainDetailMaterial.js +87 -7
  214. package/dist/terrain/terrainMath.d.ts +8 -1
  215. package/dist/terrain/terrainMath.js +8 -1
  216. package/dist/touch/TouchControlsOverlay.d.ts +9 -5
  217. package/dist/touch/TouchControlsOverlay.js +201 -22
  218. package/dist/useShellMultiplayerSync.d.ts +9 -0
  219. package/dist/useShellMultiplayerSync.js +98 -0
  220. package/dist/visibility/CullingProvider.js +1 -1
  221. package/dist/water/Ocean.d.ts +7 -1
  222. package/dist/water/Ocean.js +16 -3
  223. package/dist/water/OceanConfig.d.ts +11 -0
  224. package/dist/water/OceanConfig.js +8 -0
  225. package/dist/water/OceanMaterial.d.ts +12 -0
  226. package/dist/water/OceanMaterial.js +9 -0
  227. package/dist/water/OceanShader.d.ts +2 -2
  228. package/dist/water/OceanShader.js +78 -10
  229. package/dist/weather/fireSpreadPose.d.ts +1 -0
  230. package/dist/weather/fireSpreadPose.js +1 -0
  231. package/dist/weather/weatherGeometry.d.ts +1 -0
  232. package/dist/weather/weatherGeometry.js +1 -0
  233. package/dist/weather/weatherMath.d.ts +3 -0
  234. package/dist/weather/weatherMath.js +3 -0
  235. package/dist/weather/weatherUniforms.d.ts +3 -0
  236. package/dist/weather/weatherUniforms.js +3 -0
  237. package/dist/world/PlaceScene.d.ts +11 -0
  238. package/dist/world/PlaceScene.js +27 -0
  239. package/dist/world/RetainedVfx.d.ts +10 -0
  240. package/dist/world/RetainedVfx.js +136 -0
  241. package/dist/world/WorldHud.d.ts +49 -2
  242. package/dist/world/WorldHud.js +87 -2
  243. package/dist/world/WorldScene.d.ts +21 -0
  244. package/dist/world/WorldScene.js +186 -0
  245. package/dist/world/WorldVfx.d.ts +9 -0
  246. package/dist/world/WorldVfx.js +184 -0
  247. package/dist/world/entityPose.d.ts +2 -0
  248. package/dist/world/entityPose.js +2 -0
  249. package/dist/world/floatTextStyle.d.ts +1 -0
  250. package/dist/world/floatTextStyle.js +1 -0
  251. package/dist/world/telegraphPulse.d.ts +1 -0
  252. package/dist/world/telegraphPulse.js +1 -0
  253. package/dist/world/worldBarSamples.d.ts +25 -0
  254. package/dist/world/worldBarSamples.js +38 -0
  255. package/dist/worldSky.d.ts +11 -0
  256. package/dist/worldSky.js +21 -0
  257. package/dist/worldSync.d.ts +2 -1
  258. package/dist/worldSync.js +2 -1
  259. package/package.json +6 -6
  260. package/skills/game-design/SKILL.md +28 -0
  261. package/skills/game-design/agents/openai.yaml +4 -0
  262. package/skills/game-design/references/application-playbook.md +170 -0
  263. package/skills/game-design/references/field-guide.md +288 -0
  264. package/skills/jgengine/SKILL.md +54 -0
  265. package/skills/jgengine/api.md +527 -0
  266. package/skills/jgengine/capabilities.md +131 -0
  267. package/skills/jgengine/recipes/minimal-game.md +88 -0
  268. package/skills/jgengine-ui/SKILL.md +41 -0
  269. package/skills/jgengine-ui/api.md +2007 -0
  270. package/skills/jgengine-ui/capabilities.md +211 -0
  271. package/skills/jgengine-ui/recipes/portable-minimap.md +150 -0
  272. package/skills/jgengine-ui/reference.md +513 -0
  273. package/skills/jgengine-ui/references/visual-scorecard.md +73 -0
  274. package/skills/jgengine-world/SKILL.md +51 -0
  275. package/skills/jgengine-world/api.md +3056 -0
  276. package/skills/jgengine-world/capabilities.md +476 -0
  277. package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
  278. package/skills/jgengine-world/reference.md +261 -0
  279. package/skills/level-design/SKILL.md +54 -0
  280. package/skills/level-design/agents/openai.yaml +4 -0
  281. package/skills/level-design/references/application-playbook.md +192 -0
  282. package/skills/level-design/references/field-guide.md +337 -0
  283. package/skills/level-design/references/live-editor-loop.md +179 -0
  284. package/dist/cartridge.d.ts +0 -145
  285. package/dist/cartridge.js +0 -246
  286. package/llms.txt +0 -2761
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The happy-path game kit — one import surface covering a whole simple game: authoring
3
+ * (`defineGame`), mounting (`GameHost`), serializable state (`defineStore`), behavior
4
+ * (`defineSystem`), the authored-scene document helpers, HUD primitives, and deterministic
5
+ * randomness. Start here; reach for deep package paths only when a game outgrows the kit.
6
+ *
7
+ * @capability game-kit one-stop happy-path import surface for authoring, mounting, state, systems, authored scenes, and HUD
8
+ */
9
+ export { defineGame, type GameConfig } from "./defineGame.js";
10
+ export { GameHost, type GameHostProps, type EditorSummonModule } from "./GameHost.js";
11
+ export type { PlayableGame } from "./registry.js";
12
+ export { defineStore, type StoreHandle } from "@jgengine/core/store/defineStore";
13
+ export { defineKeyedStore } from "@jgengine/core/store/defineKeyedStore";
14
+ export { defineSystem, type SystemDefinition } from "@jgengine/core/game/defineSystem";
15
+ export type { GameContext } from "@jgengine/core/runtime/gameContext";
16
+ export type { GameLoop } from "@jgengine/core/game/defineGame";
17
+ export { normalizeEditorLayers } from "@jgengine/core/editor/document";
18
+ export type { EditorDocument, EditorCatalogDefinition } from "@jgengine/core/editor/types";
19
+ export { authoredSpawnPosition } from "@jgengine/core/world/authoredSpawn";
20
+ export { collectAuthoredTriggers } from "@jgengine/core/scene/authoredTriggers";
21
+ export { environmentContentFromDocument } from "@jgengine/core/editor/environment";
22
+ export { environment } from "@jgengine/core/world/features";
23
+ export type { GameCameraConfig } from "@jgengine/core/game/playableGame";
24
+ export { HudCanvas, HudPanel, useHudLayout } from "@jgengine/react/hudLayout";
25
+ export { StatBar, Hotbar, Clock, Coins } from "@jgengine/react/hud";
26
+ export { seededRng } from "@jgengine/core/random/rng";
27
+ export { offline } from "@jgengine/core/runtime/adapter";
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The happy-path game kit — one import surface covering a whole simple game: authoring
3
+ * (`defineGame`), mounting (`GameHost`), serializable state (`defineStore`), behavior
4
+ * (`defineSystem`), the authored-scene document helpers, HUD primitives, and deterministic
5
+ * randomness. Start here; reach for deep package paths only when a game outgrows the kit.
6
+ *
7
+ * @capability game-kit one-stop happy-path import surface for authoring, mounting, state, systems, authored scenes, and HUD
8
+ */
9
+ // Authoring + mounting — the one public path from config to a running game.
10
+ export { defineGame } from "./defineGame.js";
11
+ export { GameHost } from "./GameHost.js";
12
+ // Serializable state + composable behavior.
13
+ export { defineStore } from "@jgengine/core/store/defineStore";
14
+ export { defineKeyedStore } from "@jgengine/core/store/defineKeyedStore";
15
+ export { defineSystem } from "@jgengine/core/game/defineSystem";
16
+ // Authored scene document — normalize `editor.scene.json`, read spawns and trigger volumes.
17
+ export { normalizeEditorLayers } from "@jgengine/core/editor/document";
18
+ export { authoredSpawnPosition } from "@jgengine/core/world/authoredSpawn";
19
+ export { collectAuthoredTriggers } from "@jgengine/core/scene/authoredTriggers";
20
+ export { environmentContentFromDocument } from "@jgengine/core/editor/environment";
21
+ // World + camera.
22
+ export { environment } from "@jgengine/core/world/features";
23
+ // HUD layout + optional building blocks — games own custom chrome (not a stock face).
24
+ export { HudCanvas, HudPanel, useHudLayout } from "@jgengine/react/hudLayout";
25
+ export { StatBar, Hotbar, Clock, Coins } from "@jgengine/react/hud";
26
+ // Deterministic randomness (inject seeds; never Math.random in game logic).
27
+ export { seededRng } from "@jgengine/core/random/rng";
28
+ // Multiplayer story: omit `multiplayer` entirely for solo — offline is the shell default.
29
+ export { offline } from "@jgengine/core/runtime/adapter";
@@ -0,0 +1,26 @@
1
+ import { type ActionCodesMap } from "@jgengine/core/input/actionBindings";
2
+ import type { Aim } from "@jgengine/core/scene/spatial";
3
+ import type { GameContext } from "@jgengine/core/runtime/gameContext";
4
+ type Inventories = Record<string, {
5
+ hud?: string;
6
+ }>;
7
+ /** Minimal playable slice — avoids coupling to shell vs core PlayableGame generics. @internal */
8
+ export type HotbarPlayable = {
9
+ game: {
10
+ inventories?: Inventories;
11
+ input?: ActionCodesMap;
12
+ };
13
+ };
14
+ /** @internal Map bound action names to hotbar slot indices. */
15
+ export declare function findHotbarSlotActions(input: ActionCodesMap | undefined): {
16
+ action: string;
17
+ slot: number;
18
+ }[];
19
+ /** @internal Resolve the inventory id used as the hotbar HUD binding. */
20
+ export declare function hotbarIdFor(playable: HotbarPlayable): string | null;
21
+ /** @internal Activate an item from a hotbar slot via `ctx.item.use`. */
22
+ export declare function executeHotbarSlot(ctx: GameContext, fromId: string, hotbarId: string, slot: number, yaw: number, pitch: number, aimOverride?: Aim): {
23
+ ok: boolean;
24
+ error?: string;
25
+ };
26
+ export {};
@@ -0,0 +1,27 @@
1
+ import { hotbarSlotActionIndex } from "@jgengine/core/input/actionBindings";
2
+ /** @internal Map bound action names to hotbar slot indices. */
3
+ export function findHotbarSlotActions(input) {
4
+ return Object.keys(input ?? {}).flatMap((action) => {
5
+ const slot = hotbarSlotActionIndex(action);
6
+ return slot === null ? [] : [{ action, slot }];
7
+ });
8
+ }
9
+ /** @internal Resolve the inventory id used as the hotbar HUD binding. */
10
+ export function hotbarIdFor(playable) {
11
+ const declarations = Object.entries(playable.game.inventories ?? {});
12
+ const hud = declarations.find(([, declaration]) => declaration.hud === "hotbar");
13
+ return (hud ?? declarations[0])?.[0] ?? null;
14
+ }
15
+ /** @internal Activate an item from a hotbar slot via `ctx.item.use`. */
16
+ export function executeHotbarSlot(ctx, fromId, hotbarId, slot, yaw, pitch, aimOverride) {
17
+ const stack = ctx.player.inventory.state(hotbarId).slots[slot];
18
+ if (stack === undefined || stack === null)
19
+ return { ok: false, error: `Hotbar slot ${slot + 1} is empty` };
20
+ const result = ctx.item.use.use({
21
+ from: fromId,
22
+ itemId: stack.itemId,
23
+ inventoryId: hotbarId,
24
+ aim: aimOverride ?? { yaw, pitch },
25
+ });
26
+ return result.error === undefined ? { ok: true } : { ok: false, error: result.error };
27
+ }
@@ -4,15 +4,27 @@ import type { LiveGameBackend } from "@jgengine/core/runtime/transport";
4
4
  export interface InputSink {
5
5
  send(frame: InputFrame): void;
6
6
  }
7
- /** Discards input — the single-player / client-authoritative default, where the client integrates movement itself. */
7
+ /** Discards input — the single-player / client-authoritative default, where the client integrates movement itself.
8
+ * @internal
9
+ */
8
10
  export declare function noopInputSink(): InputSink;
9
- /** Sends each frame's input to the authoritative host over the transport, reusing the `runCommand` path via {@link INPUT_COMMAND}. */
11
+ /**
12
+ * Sends each frame's input to the authoritative host over the transport, reusing the `runCommand` path via
13
+ * {@link INPUT_COMMAND}. Sends are sequenced one-in-flight-at-a-time per `serverId` and stamped with a monotonic
14
+ * seq: a frame sent while one is already in flight replaces the pending frame rather than racing it, so the
15
+ * latest intent always wins and a stale frame can never resolve after (and overwrite) a newer one.
16
+ * @internal
17
+ */
10
18
  export declare function remoteInputSink(backend: Pick<LiveGameBackend, "transport">, serverId: string): InputSink;
11
- /** The sink a server-authoritative shell sends its per-frame input through: remote when `authority: "server"` and a server is joined, a no-op otherwise. */
19
+ /** The sink a server-authoritative shell sends its per-frame input through: remote when `authority: "server"` and a server is joined, a no-op otherwise.
20
+ * @internal
21
+ */
12
22
  export declare function resolveInputSink(opts: {
13
23
  serverAuthoritative: boolean;
14
24
  backend: Pick<LiveGameBackend, "transport"> | null;
15
25
  serverId: string | null;
16
26
  }): InputSink;
17
- /** Whether two input frames carry identical intent — the shell skips resending unchanged frames so a still player floods the host with nothing. */
27
+ /** Whether two input frames carry identical intent — the shell skips resending unchanged frames so a still player floods the host with nothing.
28
+ * @internal
29
+ */
18
30
  export declare function inputFramesEqual(a: InputFrame, b: InputFrame): boolean;
package/dist/inputSink.js CHANGED
@@ -1,26 +1,76 @@
1
1
  import { INPUT_COMMAND } from "@jgengine/core/runtime/hostedGameRunner";
2
- /** Discards input — the single-player / client-authoritative default, where the client integrates movement itself. */
2
+ /** Discards input — the single-player / client-authoritative default, where the client integrates movement itself.
3
+ * @internal
4
+ */
3
5
  export function noopInputSink() {
4
6
  return { send() { } };
5
7
  }
6
- /** Sends each frame's input to the authoritative host over the transport, reusing the `runCommand` path via {@link INPUT_COMMAND}. */
8
+ const remoteInputSources = new Map();
9
+ function remoteInputSourceFor(serverId) {
10
+ let source = remoteInputSources.get(serverId);
11
+ if (source === undefined) {
12
+ source = { pending: null, inFlight: false };
13
+ remoteInputSources.set(serverId, source);
14
+ }
15
+ return source;
16
+ }
17
+ function monotonicInputSeq() {
18
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
19
+ }
20
+ function pumpRemoteInput(backend, serverId, source) {
21
+ if (source.inFlight)
22
+ return;
23
+ const frame = source.pending;
24
+ if (frame === null) {
25
+ remoteInputSources.delete(serverId);
26
+ return;
27
+ }
28
+ source.pending = null;
29
+ source.inFlight = true;
30
+ const seq = monotonicInputSeq();
31
+ void backend.transport
32
+ .runCommand({ serverId, command: INPUT_COMMAND, input: { ...frame, seq } })
33
+ .then((result) => {
34
+ if (!result.ok) {
35
+ console.warn(`[jgengine:input] frame seq=${seq} to server "${serverId}" rejected: ${result.reason}`);
36
+ }
37
+ })
38
+ .catch((error) => {
39
+ console.warn(`[jgengine:input] frame seq=${seq} to server "${serverId}" failed to send`, error);
40
+ })
41
+ .finally(() => {
42
+ source.inFlight = false;
43
+ pumpRemoteInput(backend, serverId, source);
44
+ });
45
+ }
46
+ /**
47
+ * Sends each frame's input to the authoritative host over the transport, reusing the `runCommand` path via
48
+ * {@link INPUT_COMMAND}. Sends are sequenced one-in-flight-at-a-time per `serverId` and stamped with a monotonic
49
+ * seq: a frame sent while one is already in flight replaces the pending frame rather than racing it, so the
50
+ * latest intent always wins and a stale frame can never resolve after (and overwrite) a newer one.
51
+ * @internal
52
+ */
7
53
  export function remoteInputSink(backend, serverId) {
8
54
  return {
9
55
  send(frame) {
10
- void backend.transport
11
- .runCommand({ serverId, command: INPUT_COMMAND, input: frame })
12
- .catch(() => undefined);
56
+ const source = remoteInputSourceFor(serverId);
57
+ source.pending = frame;
58
+ pumpRemoteInput(backend, serverId, source);
13
59
  },
14
60
  };
15
61
  }
16
- /** The sink a server-authoritative shell sends its per-frame input through: remote when `authority: "server"` and a server is joined, a no-op otherwise. */
62
+ /** The sink a server-authoritative shell sends its per-frame input through: remote when `authority: "server"` and a server is joined, a no-op otherwise.
63
+ * @internal
64
+ */
17
65
  export function resolveInputSink(opts) {
18
66
  if (opts.serverAuthoritative && opts.backend !== null && opts.serverId !== null) {
19
67
  return remoteInputSink(opts.backend, opts.serverId);
20
68
  }
21
69
  return noopInputSink();
22
70
  }
23
- /** Whether two input frames carry identical intent — the shell skips resending unchanged frames so a still player floods the host with nothing. */
71
+ /** Whether two input frames carry identical intent — the shell skips resending unchanged frames so a still player floods the host with nothing.
72
+ * @internal
73
+ */
24
74
  export function inputFramesEqual(a, b) {
25
75
  if (a.held.length !== b.held.length)
26
76
  return false;
@@ -1,6 +1,7 @@
1
- import { type MarkerKindStyle, type MarkerSet } from "@jgengine/core/world/markers";
1
+ import { type MarkerCollection, type MarkerKindStyle } from "@jgengine/core/world/markers";
2
2
  export interface MapMarkerBeaconsProps {
3
- markers: MarkerSet;
3
+ /** Static views, an external marker source, or a native JGengine MarkerSet. */
4
+ markers: MarkerCollection;
4
5
  kindStyles?: Record<string, MarkerKindStyle>;
5
6
  height?: number;
6
7
  }
@@ -2,5 +2,15 @@ import * as THREE from "three";
2
2
  import type { ModelMaterialOverride } from "@jgengine/core/game/playableGame";
3
3
  export interface MaterialOverrideOptions {
4
4
  clone?: boolean;
5
+ /** Loaded PBR textures matching `ModelMaterialOverride.maps`' roles, applied onto each `MeshStandardMaterial` alongside the tint override. The caller (a React component, via `useTexture`) owns loading; this function never fetches a URL. */
6
+ textures?: MaterialOverrideTextures;
5
7
  }
8
+ /** Loaded PBR textures for `applyMaterialOverride`'s `textures` option — matches `ModelMaterialMaps`' roles. */
9
+ export interface MaterialOverrideTextures {
10
+ color?: THREE.Texture;
11
+ normal?: THREE.Texture;
12
+ roughness?: THREE.Texture;
13
+ ao?: THREE.Texture;
14
+ }
15
+ /** @internal */
6
16
  export declare function applyMaterialOverride(root: THREE.Object3D, override: ModelMaterialOverride, options?: MaterialOverrideOptions): void;
@@ -1,16 +1,18 @@
1
1
  import * as THREE from "three";
2
+ /** @internal */
2
3
  export function applyMaterialOverride(root, override, options) {
3
4
  const clone = options?.clone !== false;
5
+ const textures = options?.textures;
4
6
  root.traverse((node) => {
5
7
  const mesh = node;
6
8
  if (!mesh.isMesh)
7
9
  return;
8
10
  mesh.material = Array.isArray(mesh.material)
9
- ? mesh.material.map((material) => overrideOne(material, override, clone))
10
- : overrideOne(mesh.material, override, clone);
11
+ ? mesh.material.map((material) => overrideOne(material, override, clone, textures))
12
+ : overrideOne(mesh.material, override, clone, textures);
11
13
  });
12
14
  }
13
- function overrideOne(material, override, clone) {
15
+ function overrideOne(material, override, clone, textures) {
14
16
  if (!(material instanceof THREE.MeshStandardMaterial))
15
17
  return material;
16
18
  const target = clone ? material.clone() : material;
@@ -24,5 +26,15 @@ function overrideOne(material, override, clone) {
24
26
  target.emissive.set(override.emissive);
25
27
  if (override.emissiveIntensity !== undefined)
26
28
  target.emissiveIntensity = override.emissiveIntensity;
29
+ if (textures?.color !== undefined)
30
+ target.map = textures.color;
31
+ if (textures?.normal !== undefined)
32
+ target.normalMap = textures.normal;
33
+ if (textures?.roughness !== undefined)
34
+ target.roughnessMap = textures.roughness;
35
+ if (textures?.ao !== undefined)
36
+ target.aoMap = textures.ao;
37
+ if (textures !== undefined)
38
+ target.needsUpdate = true;
27
39
  return target;
28
40
  }
@@ -29,16 +29,22 @@ export function resolveShellMultiplayer(args) {
29
29
  if (args.force === true)
30
30
  return build(args.url ?? DEFAULT_WS_URL);
31
31
  const adapter = adapterOf(args.game.multiplayer);
32
- if (adapter === null)
32
+ if (adapter === null || adapter.kind === "offline")
33
33
  return null;
34
34
  if (adapter.kind === "ws") {
35
35
  const url = args.url ?? adapter.url;
36
- if (url === undefined && isServerAuthoritative(args.game.multiplayer))
36
+ if (url === undefined && isServerAuthoritative(args.game.multiplayer)) {
37
+ console.warn(`[jgengine:multiplayer] ${args.gameId}: ws({ authority: "server" }) has no url; falling back to single-player. ` +
38
+ `Pass a url (or set adapter.url) so the shell can reach the host.`);
37
39
  return null;
40
+ }
38
41
  return build(url ?? DEFAULT_WS_URL);
39
42
  }
40
43
  if (adapter.kind === "lan")
41
44
  return build(args.url ?? lanUrl(adapter));
45
+ console.warn(`[jgengine:multiplayer] ${args.gameId}: multiplayer adapter "${adapter.kind}" needs its own session bootstrap ` +
46
+ `(e.g. resolvePeerShellMultiplayer for p2p()); resolveShellMultiplayer cannot build a ws backend for it, ` +
47
+ `so the game shipped single-player. Wire up the ${adapter.kind} transport or switch to ws()/lan().`);
42
48
  return null;
43
49
  }
44
50
  export async function resolvePeerShellMultiplayer(args) {
@@ -1,10 +1,18 @@
1
1
  import type { PostProcessingConfig } from "@jgengine/core/render/postProcessing";
2
+ import type { GraphicsQuality } from "@jgengine/core/settings/settingsModel";
2
3
  /**
3
4
  * Mounts an `EffectComposer` inside the shell Canvas and takes over rendering
4
5
  * (priority-1 `useFrame`, which disables R3F auto-render) to run the configured
5
6
  * post chain: RenderPass → GTAO → UnrealBloom → OutputPass → Grade. Rendered only
6
7
  * when `PlayableGame.postProcessing` is set, so games without it draw unchanged.
8
+ *
9
+ * `quality` (the player's graphics-quality setting) gates the passes whose cost
10
+ * scales with scene geometry or resolution beyond the dpr cap: GTAO re-renders
11
+ * the whole scene for depth/normals and runs a multi-sample full-screen pass,
12
+ * and Bokeh DOF renders scene depth again — both run on "high" only. Bloom,
13
+ * tone mapping, and grade stay on every tier.
7
14
  */
8
- export declare function PostProcessing({ config }: {
15
+ export declare function PostProcessing({ config, quality }: {
9
16
  config: PostProcessingConfig;
17
+ quality?: GraphicsQuality;
10
18
  }): null;
@@ -1,9 +1,10 @@
1
1
  import { useFrame, useThree } from "@react-three/fiber";
2
- import { useEffect, useMemo } from "react";
2
+ import { useEffect, useRef } from "react";
3
3
  import * as THREE from "three";
4
4
  import { EffectComposer, RenderPass, UnrealBloomPass } from "three-stdlib";
5
5
  import { OutputPass } from "three/examples/jsm/postprocessing/OutputPass.js";
6
6
  import { GTAOPass } from "three/examples/jsm/postprocessing/GTAOPass.js";
7
+ import { BokehPass } from "three/examples/jsm/postprocessing/BokehPass.js";
7
8
  import { createGradePass } from "./gradeShader.js";
8
9
  const TONE_MAPPING = {
9
10
  aces: THREE.ACESFilmicToneMapping,
@@ -13,18 +14,42 @@ const TONE_MAPPING = {
13
14
  linear: THREE.LinearToneMapping,
14
15
  none: THREE.NoToneMapping,
15
16
  };
17
+ function disposeGraph(built) {
18
+ for (const pass of built.composer.passes)
19
+ pass.dispose();
20
+ if (built.dof !== null) {
21
+ built.dof.renderTargetDepth.dispose();
22
+ built.dof.materialDepth.dispose();
23
+ built.dof.materialBokeh.dispose();
24
+ built.dof.fsQuad.dispose();
25
+ }
26
+ built.composer.dispose();
27
+ }
28
+ function syncSize(built, width, height, pixelRatio) {
29
+ built.composer.setPixelRatio(pixelRatio);
30
+ built.composer.setSize(width, height);
31
+ if (built.dof !== null)
32
+ built.dof.renderTargetDepth.setSize(width * pixelRatio, height * pixelRatio);
33
+ }
16
34
  /**
17
35
  * Mounts an `EffectComposer` inside the shell Canvas and takes over rendering
18
36
  * (priority-1 `useFrame`, which disables R3F auto-render) to run the configured
19
37
  * post chain: RenderPass → GTAO → UnrealBloom → OutputPass → Grade. Rendered only
20
38
  * when `PlayableGame.postProcessing` is set, so games without it draw unchanged.
39
+ *
40
+ * `quality` (the player's graphics-quality setting) gates the passes whose cost
41
+ * scales with scene geometry or resolution beyond the dpr cap: GTAO re-renders
42
+ * the whole scene for depth/normals and runs a multi-sample full-screen pass,
43
+ * and Bokeh DOF renders scene depth again — both run on "high" only. Bloom,
44
+ * tone mapping, and grade stay on every tier.
21
45
  */
22
- export function PostProcessing({ config }) {
46
+ export function PostProcessing({ config, quality = "high" }) {
23
47
  const gl = useThree((s) => s.gl);
24
48
  const scene = useThree((s) => s.scene);
25
49
  const camera = useThree((s) => s.camera);
26
50
  const size = useThree((s) => s.size);
27
- const built = useMemo(() => {
51
+ const builtRef = useRef(null);
52
+ useEffect(() => {
28
53
  const width = Math.max(1, size.width);
29
54
  const height = Math.max(1, size.height);
30
55
  const target = new THREE.WebGLRenderTarget(width, height, {
@@ -33,7 +58,8 @@ export function PostProcessing({ config }) {
33
58
  });
34
59
  const composer = new EffectComposer(gl, target);
35
60
  composer.addPass(new RenderPass(scene, camera));
36
- if (config.ao !== undefined && config.ao !== false) {
61
+ const heavyPasses = quality === "high";
62
+ if (heavyPasses && config.ao !== undefined && config.ao !== false) {
37
63
  const ao = new GTAOPass(scene, camera, width, height);
38
64
  ao.blendIntensity = config.ao.blend ?? 1;
39
65
  ao.updateGtaoMaterial({
@@ -49,14 +75,30 @@ export function PostProcessing({ config }) {
49
75
  const b = config.bloom ?? {};
50
76
  composer.addPass(new UnrealBloomPass(new THREE.Vector2(width, height), b.strength ?? 0.32, b.radius ?? 0.55, b.threshold ?? 0.85));
51
77
  }
78
+ let dof = null;
79
+ if (heavyPasses && config.dof !== undefined && config.dof !== false) {
80
+ const d = config.dof;
81
+ dof = new BokehPass(scene, camera, {
82
+ focus: d.focus ?? 18,
83
+ aperture: d.aperture ?? 0.00025,
84
+ maxblur: d.maxBlur ?? 0.01,
85
+ });
86
+ composer.addPass(dof);
87
+ }
52
88
  composer.addPass(new OutputPass());
53
89
  let grade = null;
54
90
  if (config.grade !== false) {
55
91
  grade = createGradePass(config.grade ?? {});
56
92
  composer.addPass(grade);
57
93
  }
58
- return { composer, grade };
59
- }, [gl, scene, camera, config, size.width, size.height]);
94
+ const graph = { composer, grade, dof };
95
+ syncSize(graph, width, height, gl.getPixelRatio());
96
+ builtRef.current = graph;
97
+ return () => {
98
+ disposeGraph(graph);
99
+ builtRef.current = null;
100
+ };
101
+ }, [gl, scene, camera, config, quality]);
60
102
  useEffect(() => {
61
103
  const prevTone = gl.toneMapping;
62
104
  const prevExposure = gl.toneMappingExposure;
@@ -68,12 +110,15 @@ export function PostProcessing({ config }) {
68
110
  };
69
111
  }, [gl, config.toneMapping, config.exposure]);
70
112
  useEffect(() => {
71
- const { composer } = built;
72
- composer.setPixelRatio(gl.getPixelRatio());
73
- composer.setSize(size.width, size.height);
74
- return () => composer.dispose();
75
- }, [built, gl, size.width, size.height]);
113
+ const built = builtRef.current;
114
+ if (built === null)
115
+ return;
116
+ syncSize(built, Math.max(1, size.width), Math.max(1, size.height), gl.getPixelRatio());
117
+ }, [gl, size.width, size.height]);
76
118
  useFrame((_, delta) => {
119
+ const built = builtRef.current;
120
+ if (built === null)
121
+ return;
77
122
  if (built.grade !== null)
78
123
  built.grade.uniforms.uTime.value += delta;
79
124
  built.composer.render(delta);
@@ -6,6 +6,7 @@ const DEFAULT_GAMMA = 0.96;
6
6
  const DEFAULT_SATURATION = 1.12;
7
7
  const DEFAULT_VIGNETTE = 0.2;
8
8
  const DEFAULT_GRAIN = 0.012;
9
+ const DEFAULT_CHROMA = 0;
9
10
  const fragmentShader = /* glsl */ `
10
11
  uniform sampler2D tDiffuse;
11
12
  uniform vec3 uLift;
@@ -14,6 +15,7 @@ const fragmentShader = /* glsl */ `
14
15
  uniform float uSaturation;
15
16
  uniform float uVignette;
16
17
  uniform float uGrain;
18
+ uniform float uChroma;
17
19
  uniform float uTime;
18
20
  varying vec2 vUv;
19
21
 
@@ -22,7 +24,12 @@ const fragmentShader = /* glsl */ `
22
24
  }
23
25
 
24
26
  void main() {
25
- vec3 c = texture2D(tDiffuse, vUv).rgb;
27
+ vec2 dir = vUv - 0.5;
28
+ // Chromatic aberration: split RGB along the radial direction, growing toward the edges.
29
+ vec2 shift = uChroma > 0.0 ? dir * uChroma * dot(dir, dir) * 4.0 : vec2(0.0);
30
+ vec3 c = uChroma > 0.0
31
+ ? vec3(texture2D(tDiffuse, vUv + shift).r, texture2D(tDiffuse, vUv).g, texture2D(tDiffuse, vUv - shift).b)
32
+ : texture2D(tDiffuse, vUv).rgb;
26
33
  c = c + uLift;
27
34
  c = c * uGain;
28
35
  c = pow(max(c, 0.0), vec3(uGamma));
@@ -56,6 +63,7 @@ export function createGradePass(config = {}) {
56
63
  uSaturation: { value: config.saturation ?? DEFAULT_SATURATION },
57
64
  uVignette: { value: config.vignette ?? DEFAULT_VIGNETTE },
58
65
  uGrain: { value: config.grain ?? DEFAULT_GRAIN },
66
+ uChroma: { value: config.chromaticAberration ?? DEFAULT_CHROMA },
59
67
  uTime: { value: 0 },
60
68
  },
61
69
  vertexShader,
@@ -0,0 +1,35 @@
1
+ import type { CatalogEntityRole } from "@jgengine/core/runtime/gameContext";
2
+ import type { PresentationEffectsConfig } from "@jgengine/core/game/playableGame";
3
+ /** Resolved world-bar / nameplate config, or `null` when the feature is off. @internal */
4
+ export interface ResolvedWorldOverlayBars {
5
+ statId: string;
6
+ roles?: readonly CatalogEntityRole[];
7
+ maxDistance?: number;
8
+ }
9
+ /**
10
+ * Normalize `worldHealthBars` / `nameplates` (`boolean | object | undefined`) into a
11
+ * single resolved shape. `undefined` / `false` → `null` (off); `true` → default stat `"health"`;
12
+ * object → explicit fields with `statId` defaulting to `"health"`.
13
+ * @internal
14
+ */
15
+ export declare function resolveWorldOverlayBars(config: boolean | {
16
+ statId?: string;
17
+ roles?: readonly CatalogEntityRole[];
18
+ maxDistance?: number;
19
+ } | undefined): ResolvedWorldOverlayBars | null;
20
+ /** Resolved combat presentation channels (each boolean is whether that stack piece mounts). @internal */
21
+ export interface ResolvedPresentationEffects {
22
+ telegraphs: boolean;
23
+ vfx: boolean;
24
+ floatText: boolean;
25
+ tracers: boolean;
26
+ shake: boolean;
27
+ }
28
+ /**
29
+ * Resolve `presentationEffects` for the 3D shell.
30
+ * - `undefined` / `true` → all channels on (historical default).
31
+ * - `false` → none.
32
+ * - object → per-channel; missing keys default to on so games can disable one channel.
33
+ * @internal
34
+ */
35
+ export declare function resolvePresentationEffects(config: boolean | PresentationEffectsConfig | undefined): ResolvedPresentationEffects;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Normalize `worldHealthBars` / `nameplates` (`boolean | object | undefined`) into a
3
+ * single resolved shape. `undefined` / `false` → `null` (off); `true` → default stat `"health"`;
4
+ * object → explicit fields with `statId` defaulting to `"health"`.
5
+ * @internal
6
+ */
7
+ export function resolveWorldOverlayBars(config) {
8
+ if (config === undefined || config === false)
9
+ return null;
10
+ if (config === true)
11
+ return { statId: "health" };
12
+ return {
13
+ statId: config.statId ?? "health",
14
+ ...(config.roles === undefined ? {} : { roles: config.roles }),
15
+ ...(config.maxDistance === undefined ? {} : { maxDistance: config.maxDistance }),
16
+ };
17
+ }
18
+ const ALL_ON = {
19
+ telegraphs: true,
20
+ vfx: true,
21
+ floatText: true,
22
+ tracers: true,
23
+ shake: true,
24
+ };
25
+ const ALL_OFF = {
26
+ telegraphs: false,
27
+ vfx: false,
28
+ floatText: false,
29
+ tracers: false,
30
+ shake: false,
31
+ };
32
+ /**
33
+ * Resolve `presentationEffects` for the 3D shell.
34
+ * - `undefined` / `true` → all channels on (historical default).
35
+ * - `false` → none.
36
+ * - object → per-channel; missing keys default to on so games can disable one channel.
37
+ * @internal
38
+ */
39
+ export function resolvePresentationEffects(config) {
40
+ if (config === false)
41
+ return ALL_OFF;
42
+ if (config === undefined || config === true)
43
+ return ALL_ON;
44
+ return {
45
+ telegraphs: config.telegraphs !== false,
46
+ vfx: config.vfx !== false,
47
+ floatText: config.floatText !== false,
48
+ tracers: config.tracers !== false,
49
+ shake: config.shake !== false,
50
+ };
51
+ }
@@ -1,9 +1,11 @@
1
1
  import type { ComponentType, ReactNode } from "react";
2
- import type { PlayableGame as EnginePlayableGame } from "@jgengine/core/game/playableGame";
2
+ import type { PlayableGame as EnginePlayableGame, WorldOverlayProps } from "@jgengine/core/game/playableGame";
3
3
  import type { SceneEntity } from "@jgengine/core/scene/entityStore";
4
4
  import type { SceneObject } from "@jgengine/core/scene/objectStore";
5
+ import type { ViewmodelProps } from "./camera/GameFirstPersonCamera.js";
5
6
  export type RenderEntity = (entity: SceneEntity) => ReactNode;
6
7
  export type RenderObject = (object: SceneObject) => ReactNode;
7
- export type PlayableGame = EnginePlayableGame<ComponentType, ComponentType, RenderEntity, RenderObject>;
8
+ /** The concrete React-bound playable a shell `defineGame` returns and `GameHost` mounts — the one PlayableGame games handle. */
9
+ export type PlayableGame = EnginePlayableGame<ComponentType, ComponentType, RenderEntity, RenderObject, ComponentType<ViewmodelProps>, ComponentType<WorldOverlayProps>>;
8
10
  export type GameRegistry = Record<string, () => Promise<PlayableGame>>;
9
11
  export declare function resolveGameLoader(registry: GameRegistry, gameId: string, fallbackGameId?: string): (() => Promise<PlayableGame>) | undefined;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Sets a neutral, network-free image-based-lighting environment on the scene so every
3
+ * `MeshStandardMaterial` — models and un-modeled primitive fallbacks alike — picks up soft
4
+ * reflections and fill instead of rendering dead-flat. A game's own sky/lighting still layers on
5
+ * top; this only supplies the reflective ambient the shell never had. Opt out with `look: "flat"`.
6
+ * @internal shell-internal default lighting; games never import it.
7
+ */
8
+ export declare function EnvironmentLighting({ intensity }: {
9
+ intensity?: number;
10
+ }): null;
@@ -0,0 +1,34 @@
1
+ import { useThree } from "@react-three/fiber";
2
+ import { useEffect } from "react";
3
+ import * as THREE from "three";
4
+ import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js";
5
+ /**
6
+ * Sets a neutral, network-free image-based-lighting environment on the scene so every
7
+ * `MeshStandardMaterial` — models and un-modeled primitive fallbacks alike — picks up soft
8
+ * reflections and fill instead of rendering dead-flat. A game's own sky/lighting still layers on
9
+ * top; this only supplies the reflective ambient the shell never had. Opt out with `look: "flat"`.
10
+ * @internal shell-internal default lighting; games never import it.
11
+ */
12
+ export function EnvironmentLighting({ intensity = 0.28 }) {
13
+ const gl = useThree((state) => state.gl);
14
+ const scene = useThree((state) => state.scene);
15
+ useEffect(() => {
16
+ const pmrem = new THREE.PMREMGenerator(gl);
17
+ const room = new RoomEnvironment();
18
+ const target = pmrem.fromScene(room, 0.04);
19
+ const previous = scene.environment;
20
+ scene.environment = target.texture;
21
+ const sceneWithIntensity = scene;
22
+ const previousIntensity = sceneWithIntensity.environmentIntensity;
23
+ sceneWithIntensity.environmentIntensity = intensity;
24
+ room.dispose?.();
25
+ pmrem.dispose();
26
+ return () => {
27
+ target.dispose();
28
+ scene.environment = previous;
29
+ if (previousIntensity !== undefined)
30
+ sceneWithIntensity.environmentIntensity = previousIntensity;
31
+ };
32
+ }, [gl, scene, intensity]);
33
+ return null;
34
+ }