@jgengine/shell 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/CHANGELOG.md +34 -3
  2. package/LICENSE +203 -661
  3. package/README.md +1 -1
  4. package/dist/GamePhaseStamp.d.ts +1 -0
  5. package/dist/GamePhaseStamp.js +9 -0
  6. package/dist/GamePlayerShell.d.ts +4 -31
  7. package/dist/GamePlayerShell.js +162 -932
  8. package/dist/GameUiPreview.js +0 -2
  9. package/dist/audio/audioEngine.d.ts +8 -0
  10. package/dist/audio/audioEngine.js +63 -8
  11. package/dist/audio/audioWire.d.ts +22 -0
  12. package/dist/audio/audioWire.js +19 -0
  13. package/dist/audio/musicDirector.d.ts +31 -0
  14. package/dist/audio/musicDirector.js +125 -0
  15. package/dist/audio/musicVoices.d.ts +10 -0
  16. package/dist/audio/musicVoices.js +338 -0
  17. package/dist/audio/synthEngine.d.ts +9 -0
  18. package/dist/audio/synthEngine.js +57 -0
  19. package/dist/behaviour.d.ts +2 -1
  20. package/dist/behaviour.js +2 -1
  21. package/dist/behaviourAttach.d.ts +1 -0
  22. package/dist/behaviourAttach.js +1 -0
  23. package/dist/behaviourDriver.d.ts +1 -0
  24. package/dist/behaviourDriver.js +1 -0
  25. package/dist/boundActionDispatch.d.ts +13 -0
  26. package/dist/boundActionDispatch.js +37 -0
  27. package/dist/camera/GameCameraRig.d.ts +5 -2
  28. package/dist/camera/GameCameraRig.js +2 -2
  29. package/dist/camera/GameFirstPersonCamera.d.ts +12 -2
  30. package/dist/camera/GameFirstPersonCamera.js +29 -7
  31. package/dist/camera/GameInspectionCamera.d.ts +1 -1
  32. package/dist/camera/GameInspectionCamera.js +13 -2
  33. package/dist/camera/GameOrbitCamera.js +59 -10
  34. package/dist/camera/cameraBlendMath.d.ts +3 -0
  35. package/dist/camera/cameraBlendMath.js +3 -0
  36. package/dist/camera/fovPreference.d.ts +6 -1
  37. package/dist/camera/fovPreference.js +6 -1
  38. package/dist/camera/index.d.ts +4 -7
  39. package/dist/camera/index.js +4 -7
  40. package/dist/camera/inspectionCameraMath.d.ts +7 -2
  41. package/dist/camera/inspectionCameraMath.js +7 -2
  42. package/dist/camera/orbitCameraMath.d.ts +54 -6
  43. package/dist/camera/orbitCameraMath.js +61 -6
  44. package/dist/camera/rigMath.d.ts +94 -24
  45. package/dist/camera/rigMath.js +100 -24
  46. package/dist/camera/rigResolve.d.ts +4 -2
  47. package/dist/camera/rigResolve.js +4 -2
  48. package/dist/camera/shakeChannel.d.ts +6 -2
  49. package/dist/camera/shakeChannel.js +6 -2
  50. package/dist/camera/shakeChannelMath.d.ts +1 -0
  51. package/dist/camera/shakeChannelMath.js +1 -0
  52. package/dist/cartridge/runtime.d.ts +15 -0
  53. package/dist/cartridge/runtime.js +556 -0
  54. package/dist/cartridge/spec.d.ts +188 -0
  55. package/dist/cartridge/spec.js +19 -0
  56. package/dist/cartridge/validate.d.ts +2 -0
  57. package/dist/cartridge/validate.js +78 -0
  58. package/dist/cartridge.d.ts +1 -1
  59. package/dist/cartridge.js +4 -3
  60. package/dist/commandSink.d.ts +25 -0
  61. package/dist/commandSink.js +32 -0
  62. package/dist/defineGame.js +26 -5
  63. package/dist/devtools/ColPanel.d.ts +1 -0
  64. package/dist/devtools/ColPanel.js +14 -0
  65. package/dist/devtools/CollisionDebugWorld.js +1 -0
  66. package/dist/devtools/DevtoolsOverlay.d.ts +7 -3
  67. package/dist/devtools/DevtoolsOverlay.js +19 -450
  68. package/dist/devtools/KeysPanel.d.ts +4 -0
  69. package/dist/devtools/KeysPanel.js +31 -0
  70. package/dist/devtools/LogsPanel.d.ts +1 -0
  71. package/dist/devtools/LogsPanel.js +12 -0
  72. package/dist/devtools/NetPanel.d.ts +4 -0
  73. package/dist/devtools/NetPanel.js +10 -0
  74. package/dist/devtools/PerfPanel.d.ts +4 -0
  75. package/dist/devtools/PerfPanel.js +53 -0
  76. package/dist/devtools/TunePanel.d.ts +3 -0
  77. package/dist/devtools/TunePanel.js +309 -0
  78. package/dist/devtools/agentBridge.d.ts +41 -0
  79. package/dist/devtools/agentBridge.js +262 -0
  80. package/dist/devtools/collisionDebug.d.ts +6 -0
  81. package/dist/devtools/collisionDebug.js +6 -0
  82. package/dist/devtools/collisionDebugMath.d.ts +9 -2
  83. package/dist/devtools/collisionDebugMath.js +25 -5
  84. package/dist/devtools/devtoolsOverrides.d.ts +6 -0
  85. package/dist/devtools/devtoolsOverrides.js +48 -0
  86. package/dist/devtools/panelAtoms.d.ts +9 -0
  87. package/dist/devtools/panelAtoms.js +10 -0
  88. package/dist/devtools/perfDiagnose.d.ts +2 -0
  89. package/dist/devtools/perfDiagnose.js +43 -0
  90. package/dist/diagnostics/RuntimeDiagnostics.d.ts +29 -0
  91. package/dist/diagnostics/RuntimeDiagnostics.js +69 -0
  92. package/dist/drivers/FrameDriver.d.ts +37 -0
  93. package/dist/drivers/FrameDriver.js +232 -0
  94. package/dist/drivers/HudOnlyDriver.d.ts +16 -0
  95. package/dist/drivers/HudOnlyDriver.js +60 -0
  96. package/dist/environment/Daylight.d.ts +29 -7
  97. package/dist/environment/Daylight.js +153 -13
  98. package/dist/environment/EnvironmentScene.js +42 -7
  99. package/dist/environment/RoadRibbons.d.ts +7 -0
  100. package/dist/environment/RoadRibbons.js +57 -0
  101. package/dist/environment/VolumetricClouds.d.ts +17 -0
  102. package/dist/environment/VolumetricClouds.js +202 -0
  103. package/dist/environment/daylightCycle.d.ts +3 -1
  104. package/dist/environment/daylightCycle.js +3 -1
  105. package/dist/environment/groundPadMath.d.ts +3 -0
  106. package/dist/environment/groundPadMath.js +3 -0
  107. package/dist/environment/index.d.ts +1 -0
  108. package/dist/environment/index.js +1 -0
  109. package/dist/environment/skyLightingPolicy.d.ts +2 -0
  110. package/dist/environment/skyLightingPolicy.js +2 -0
  111. package/dist/hotbarActions.d.ts +26 -0
  112. package/dist/hotbarActions.js +27 -0
  113. package/dist/inputSink.d.ts +30 -0
  114. package/dist/inputSink.js +85 -0
  115. package/dist/materialOverride.d.ts +10 -0
  116. package/dist/materialOverride.js +15 -3
  117. package/dist/multiplayer.js +14 -4
  118. package/dist/postfx/PostProcessing.d.ts +10 -0
  119. package/dist/postfx/PostProcessing.js +120 -0
  120. package/dist/postfx/gradeShader.d.ts +4 -0
  121. package/dist/postfx/gradeShader.js +72 -0
  122. package/dist/registry.d.ts +3 -2
  123. package/dist/render/EnvironmentLighting.d.ts +10 -0
  124. package/dist/render/EnvironmentLighting.js +34 -0
  125. package/dist/render/SceneLighting.d.ts +7 -0
  126. package/dist/render/SceneLighting.js +18 -0
  127. package/dist/render/SceneModels.d.ts +14 -0
  128. package/dist/render/SceneModels.js +337 -0
  129. package/dist/render/defaultSurface.d.ts +29 -0
  130. package/dist/render/defaultSurface.js +101 -0
  131. package/dist/render/modelLoad.d.ts +24 -0
  132. package/dist/render/modelLoad.js +73 -0
  133. package/dist/render/modelRender.d.ts +11 -0
  134. package/dist/render/modelRender.js +15 -0
  135. package/dist/render/resolveModel.d.ts +39 -3
  136. package/dist/render/resolveModel.js +79 -2
  137. package/dist/render/useEntityRenderCues.d.ts +13 -0
  138. package/dist/render/useEntityRenderCues.js +62 -0
  139. package/dist/scatter/InstancedScatter.d.ts +24 -0
  140. package/dist/scatter/InstancedScatter.js +130 -0
  141. package/dist/scatter/scatterModels.d.ts +25 -0
  142. package/dist/scatter/scatterModels.js +61 -0
  143. package/dist/scatter/scatterProxies.d.ts +6 -0
  144. package/dist/scatter/scatterProxies.js +94 -0
  145. package/dist/scatter.d.ts +1 -0
  146. package/dist/scatter.js +1 -0
  147. package/dist/scene/AuthoredScene.d.ts +82 -0
  148. package/dist/scene/AuthoredScene.js +173 -0
  149. package/dist/scene/GeneratedAssetRenderer.d.ts +14 -0
  150. package/dist/scene/GeneratedAssetRenderer.js +59 -0
  151. package/dist/scene/GrassFieldRenderer.d.ts +2 -0
  152. package/dist/scene/GrassFieldRenderer.js +25 -0
  153. package/dist/scene/SoilPatchRenderer.d.ts +2 -0
  154. package/dist/scene/SoilPatchRenderer.js +21 -0
  155. package/dist/scene/StudioStage.d.ts +37 -0
  156. package/dist/scene/StudioStage.js +42 -0
  157. package/dist/scene/WaterRenderer.d.ts +2 -0
  158. package/dist/scene/WaterRenderer.js +37 -0
  159. package/dist/scene/builtinSceneKindRenderers.d.ts +2 -0
  160. package/dist/scene/builtinSceneKindRenderers.js +22 -0
  161. package/dist/scene/sceneKindRenderers.d.ts +28 -0
  162. package/dist/scene/sceneKindRenderers.js +17 -0
  163. package/dist/scene.d.ts +1 -0
  164. package/dist/scene.js +1 -0
  165. package/dist/settings/appliedSettings.d.ts +5 -0
  166. package/dist/settings/appliedSettings.js +9 -0
  167. package/dist/settings/settingsController.d.ts +2 -0
  168. package/dist/settings/settingsController.js +22 -1
  169. package/dist/shellConstants.d.ts +4 -0
  170. package/dist/shellConstants.js +4 -0
  171. package/dist/shellMovement.d.ts +8 -0
  172. package/dist/shellMovement.js +11 -0
  173. package/dist/shellPointer.d.ts +20 -0
  174. package/dist/shellPointer.js +28 -0
  175. package/dist/structures/PlacementGhost.d.ts +4 -0
  176. package/dist/structures/PlacementGhost.js +4 -0
  177. package/dist/structures/TransformGizmo.d.ts +43 -0
  178. package/dist/structures/TransformGizmo.js +55 -0
  179. package/dist/structures/index.d.ts +1 -0
  180. package/dist/structures/index.js +1 -0
  181. package/dist/terrain/CarvedTerrain.d.ts +4 -1
  182. package/dist/terrain/CarvedTerrain.js +4 -3
  183. package/dist/terrain/GrassField.js +1 -1
  184. package/dist/terrain/TerraformBrushCursor.js +3 -0
  185. package/dist/terrain/grassBudget.d.ts +11 -2
  186. package/dist/terrain/grassBudget.js +20 -4
  187. package/dist/terrain/grassGeometry.d.ts +3 -0
  188. package/dist/terrain/grassGeometry.js +3 -0
  189. package/dist/terrain/grassMaterial.d.ts +2 -0
  190. package/dist/terrain/grassMaterial.js +2 -0
  191. package/dist/terrain/random.d.ts +3 -0
  192. package/dist/terrain/random.js +3 -0
  193. package/dist/terrain/soilPatchMaterial.d.ts +10 -0
  194. package/dist/terrain/soilPatchMaterial.js +109 -0
  195. package/dist/terrain/terrainDetailMaterial.d.ts +31 -0
  196. package/dist/terrain/terrainDetailMaterial.js +157 -0
  197. package/dist/terrain/terrainMath.d.ts +8 -1
  198. package/dist/terrain/terrainMath.js +8 -1
  199. package/dist/touch/TouchControlsOverlay.d.ts +9 -5
  200. package/dist/touch/TouchControlsOverlay.js +211 -22
  201. package/dist/useShellMultiplayerSync.d.ts +9 -0
  202. package/dist/useShellMultiplayerSync.js +98 -0
  203. package/dist/visibility/CullingProvider.js +1 -1
  204. package/dist/water/OceanConfig.d.ts +2 -0
  205. package/dist/water/OceanConfig.js +2 -0
  206. package/dist/weather/fireSpreadPose.d.ts +1 -0
  207. package/dist/weather/fireSpreadPose.js +1 -0
  208. package/dist/weather/weatherGeometry.d.ts +1 -0
  209. package/dist/weather/weatherGeometry.js +1 -0
  210. package/dist/weather/weatherMath.d.ts +3 -0
  211. package/dist/weather/weatherMath.js +3 -0
  212. package/dist/weather/weatherUniforms.d.ts +3 -0
  213. package/dist/weather/weatherUniforms.js +3 -0
  214. package/dist/world/RetainedVfx.d.ts +10 -0
  215. package/dist/world/RetainedVfx.js +136 -0
  216. package/dist/world/WorldHud.d.ts +52 -3
  217. package/dist/world/WorldHud.js +94 -9
  218. package/dist/world/WorldScene.d.ts +21 -0
  219. package/dist/world/WorldScene.js +159 -0
  220. package/dist/world/WorldVfx.d.ts +9 -0
  221. package/dist/world/WorldVfx.js +184 -0
  222. package/dist/world/entityPose.d.ts +2 -0
  223. package/dist/world/entityPose.js +2 -0
  224. package/dist/world/floatTextStyle.d.ts +1 -0
  225. package/dist/world/floatTextStyle.js +1 -0
  226. package/dist/world/telegraphPulse.d.ts +1 -0
  227. package/dist/world/telegraphPulse.js +1 -0
  228. package/dist/world/worldBarSamples.d.ts +26 -1
  229. package/dist/world/worldBarSamples.js +45 -1
  230. package/dist/worldSky.d.ts +5 -0
  231. package/dist/worldSky.js +11 -0
  232. package/dist/worldSync.d.ts +11 -0
  233. package/dist/worldSync.js +20 -0
  234. package/package.json +6 -6
  235. package/skills/jgengine/SKILL.md +51 -0
  236. package/skills/jgengine/api.md +434 -0
  237. package/skills/jgengine/capabilities.md +29 -0
  238. package/skills/jgengine/reference-cartridge.md +72 -0
  239. package/skills/jgengine-ui/SKILL.md +36 -0
  240. package/skills/jgengine-ui/api.md +1852 -0
  241. package/skills/jgengine-ui/capabilities.md +142 -0
  242. package/skills/jgengine-ui/reference.md +500 -0
  243. package/skills/jgengine-ui/references/visual-scorecard.md +73 -0
  244. package/skills/jgengine-world/SKILL.md +38 -0
  245. package/skills/jgengine-world/api.md +2652 -0
  246. package/skills/jgengine-world/capabilities.md +370 -0
  247. package/skills/jgengine-world/reference.md +222 -0
  248. package/llms.txt +0 -2643
@@ -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
  }
@@ -1,4 +1,4 @@
1
- import { adapterOf } from "@jgengine/core/runtime/adapter";
1
+ import { adapterOf, isServerAuthoritative } from "@jgengine/core/runtime/adapter";
2
2
  import { createWsBackend } from "@jgengine/ws/createWsBackend";
3
3
  import { announcePeerHost, broadcastChannelSignaling, createPeerGuest, createPeerHost, joinPeerSession, } from "@jgengine/ws/peer";
4
4
  export const DEFAULT_FEED_ACTIONS = ["entity.died"];
@@ -29,12 +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
- if (adapter.kind === "ws")
35
- return build(args.url ?? adapter.url ?? DEFAULT_WS_URL);
34
+ if (adapter.kind === "ws") {
35
+ const url = args.url ?? adapter.url;
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.`);
39
+ return null;
40
+ }
41
+ return build(url ?? DEFAULT_WS_URL);
42
+ }
36
43
  if (adapter.kind === "lan")
37
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().`);
38
48
  return null;
39
49
  }
40
50
  export async function resolvePeerShellMultiplayer(args) {
@@ -0,0 +1,10 @@
1
+ import type { PostProcessingConfig } from "@jgengine/core/render/postProcessing";
2
+ /**
3
+ * Mounts an `EffectComposer` inside the shell Canvas and takes over rendering
4
+ * (priority-1 `useFrame`, which disables R3F auto-render) to run the configured
5
+ * post chain: RenderPass → GTAO → UnrealBloom → OutputPass → Grade. Rendered only
6
+ * when `PlayableGame.postProcessing` is set, so games without it draw unchanged.
7
+ */
8
+ export declare function PostProcessing({ config }: {
9
+ config: PostProcessingConfig;
10
+ }): null;
@@ -0,0 +1,120 @@
1
+ import { useFrame, useThree } from "@react-three/fiber";
2
+ import { useEffect, useRef } from "react";
3
+ import * as THREE from "three";
4
+ import { EffectComposer, RenderPass, UnrealBloomPass } from "three-stdlib";
5
+ import { OutputPass } from "three/examples/jsm/postprocessing/OutputPass.js";
6
+ import { GTAOPass } from "three/examples/jsm/postprocessing/GTAOPass.js";
7
+ import { BokehPass } from "three/examples/jsm/postprocessing/BokehPass.js";
8
+ import { createGradePass } from "./gradeShader.js";
9
+ const TONE_MAPPING = {
10
+ aces: THREE.ACESFilmicToneMapping,
11
+ agx: THREE.AgXToneMapping,
12
+ reinhard: THREE.ReinhardToneMapping,
13
+ cineon: THREE.CineonToneMapping,
14
+ linear: THREE.LinearToneMapping,
15
+ none: THREE.NoToneMapping,
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
+ }
34
+ /**
35
+ * Mounts an `EffectComposer` inside the shell Canvas and takes over rendering
36
+ * (priority-1 `useFrame`, which disables R3F auto-render) to run the configured
37
+ * post chain: RenderPass → GTAO → UnrealBloom → OutputPass → Grade. Rendered only
38
+ * when `PlayableGame.postProcessing` is set, so games without it draw unchanged.
39
+ */
40
+ export function PostProcessing({ config }) {
41
+ const gl = useThree((s) => s.gl);
42
+ const scene = useThree((s) => s.scene);
43
+ const camera = useThree((s) => s.camera);
44
+ const size = useThree((s) => s.size);
45
+ const builtRef = useRef(null);
46
+ useEffect(() => {
47
+ const width = Math.max(1, size.width);
48
+ const height = Math.max(1, size.height);
49
+ const target = new THREE.WebGLRenderTarget(width, height, {
50
+ type: THREE.HalfFloatType,
51
+ samples: 2,
52
+ });
53
+ const composer = new EffectComposer(gl, target);
54
+ composer.addPass(new RenderPass(scene, camera));
55
+ if (config.ao !== undefined && config.ao !== false) {
56
+ const ao = new GTAOPass(scene, camera, width, height);
57
+ ao.blendIntensity = config.ao.blend ?? 1;
58
+ ao.updateGtaoMaterial({
59
+ radius: config.ao.radius ?? 1.8,
60
+ distanceExponent: 1,
61
+ thickness: config.ao.distanceFalloff ?? 3.6,
62
+ scale: config.ao.intensity ?? 2.4,
63
+ samples: 16,
64
+ });
65
+ composer.addPass(ao);
66
+ }
67
+ if (config.bloom !== false) {
68
+ const b = config.bloom ?? {};
69
+ composer.addPass(new UnrealBloomPass(new THREE.Vector2(width, height), b.strength ?? 0.32, b.radius ?? 0.55, b.threshold ?? 0.85));
70
+ }
71
+ let dof = null;
72
+ if (config.dof !== undefined && config.dof !== false) {
73
+ const d = config.dof;
74
+ dof = new BokehPass(scene, camera, {
75
+ focus: d.focus ?? 18,
76
+ aperture: d.aperture ?? 0.00025,
77
+ maxblur: d.maxBlur ?? 0.01,
78
+ });
79
+ composer.addPass(dof);
80
+ }
81
+ composer.addPass(new OutputPass());
82
+ let grade = null;
83
+ if (config.grade !== false) {
84
+ grade = createGradePass(config.grade ?? {});
85
+ composer.addPass(grade);
86
+ }
87
+ const graph = { composer, grade, dof };
88
+ syncSize(graph, width, height, gl.getPixelRatio());
89
+ builtRef.current = graph;
90
+ return () => {
91
+ disposeGraph(graph);
92
+ builtRef.current = null;
93
+ };
94
+ }, [gl, scene, camera, config]);
95
+ useEffect(() => {
96
+ const prevTone = gl.toneMapping;
97
+ const prevExposure = gl.toneMappingExposure;
98
+ gl.toneMapping = TONE_MAPPING[config.toneMapping ?? "aces"];
99
+ gl.toneMappingExposure = config.exposure ?? 1;
100
+ return () => {
101
+ gl.toneMapping = prevTone;
102
+ gl.toneMappingExposure = prevExposure;
103
+ };
104
+ }, [gl, config.toneMapping, config.exposure]);
105
+ useEffect(() => {
106
+ const built = builtRef.current;
107
+ if (built === null)
108
+ return;
109
+ syncSize(built, Math.max(1, size.width), Math.max(1, size.height), gl.getPixelRatio());
110
+ }, [gl, size.width, size.height]);
111
+ useFrame((_, delta) => {
112
+ const built = builtRef.current;
113
+ if (built === null)
114
+ return;
115
+ if (built.grade !== null)
116
+ built.grade.uniforms.uTime.value += delta;
117
+ built.composer.render(delta);
118
+ }, 1);
119
+ return null;
120
+ }
@@ -0,0 +1,4 @@
1
+ import { ShaderPass } from "three-stdlib";
2
+ import type { GradeConfig } from "@jgengine/core/render/postProcessing";
3
+ /** Build the display-space colour-grade pass (lift/gain/gamma, saturation, vignette, grain). Advance `uniforms.uTime.value` each frame to animate the grain. */
4
+ export declare function createGradePass(config?: GradeConfig): ShaderPass;
@@ -0,0 +1,72 @@
1
+ import * as THREE from "three";
2
+ import { ShaderPass } from "three-stdlib";
3
+ const DEFAULT_LIFT = [0.012, 0.01, 0.018];
4
+ const DEFAULT_GAIN = [1.05, 1.02, 0.98];
5
+ const DEFAULT_GAMMA = 0.96;
6
+ const DEFAULT_SATURATION = 1.12;
7
+ const DEFAULT_VIGNETTE = 0.2;
8
+ const DEFAULT_GRAIN = 0.012;
9
+ const DEFAULT_CHROMA = 0;
10
+ const fragmentShader = /* glsl */ `
11
+ uniform sampler2D tDiffuse;
12
+ uniform vec3 uLift;
13
+ uniform vec3 uGain;
14
+ uniform float uGamma;
15
+ uniform float uSaturation;
16
+ uniform float uVignette;
17
+ uniform float uGrain;
18
+ uniform float uChroma;
19
+ uniform float uTime;
20
+ varying vec2 vUv;
21
+
22
+ float hash(vec2 p) {
23
+ return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
24
+ }
25
+
26
+ void main() {
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;
33
+ c = c + uLift;
34
+ c = c * uGain;
35
+ c = pow(max(c, 0.0), vec3(uGamma));
36
+ float luma = dot(c, vec3(0.2126, 0.7152, 0.0722));
37
+ c = mix(vec3(luma), c, uSaturation);
38
+ vec2 d = vUv - 0.5;
39
+ float vig = 1.0 - uVignette * smoothstep(0.6, 0.95, dot(d, d) * 2.2);
40
+ c *= vig;
41
+ float g = (hash(vUv + fract(uTime)) - 0.5) * uGrain * 2.0;
42
+ c += g;
43
+ gl_FragColor = vec4(clamp(c, 0.0, 1.0), 1.0);
44
+ }
45
+ `;
46
+ const vertexShader = /* glsl */ `
47
+ varying vec2 vUv;
48
+ void main() {
49
+ vUv = uv;
50
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
51
+ }
52
+ `;
53
+ /** Build the display-space colour-grade pass (lift/gain/gamma, saturation, vignette, grain). Advance `uniforms.uTime.value` each frame to animate the grain. */
54
+ export function createGradePass(config = {}) {
55
+ const lift = config.lift ?? DEFAULT_LIFT;
56
+ const gain = config.gain ?? DEFAULT_GAIN;
57
+ return new ShaderPass({
58
+ uniforms: {
59
+ tDiffuse: { value: null },
60
+ uLift: { value: new THREE.Vector3(lift[0], lift[1], lift[2]) },
61
+ uGain: { value: new THREE.Vector3(gain[0], gain[1], gain[2]) },
62
+ uGamma: { value: config.gamma ?? DEFAULT_GAMMA },
63
+ uSaturation: { value: config.saturation ?? DEFAULT_SATURATION },
64
+ uVignette: { value: config.vignette ?? DEFAULT_VIGNETTE },
65
+ uGrain: { value: config.grain ?? DEFAULT_GRAIN },
66
+ uChroma: { value: config.chromaticAberration ?? DEFAULT_CHROMA },
67
+ uTime: { value: 0 },
68
+ },
69
+ vertexShader,
70
+ fragmentShader,
71
+ });
72
+ }
@@ -1,9 +1,10 @@
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
+ export type PlayableGame = EnginePlayableGame<ComponentType, ComponentType, RenderEntity, RenderObject, ComponentType<ViewmodelProps>, ComponentType<WorldOverlayProps>>;
8
9
  export type GameRegistry = Record<string, () => Promise<PlayableGame>>;
9
10
  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
+ }
@@ -0,0 +1,7 @@
1
+ import type { BackdropConfig, LightingConfig } from "@jgengine/core/game/playableGame";
2
+ export declare function ConfiguredLighting({ lighting }: {
3
+ lighting: LightingConfig;
4
+ }): import("react").JSX.Element;
5
+ export declare function BackdropFog({ fog }: {
6
+ fog: BackdropConfig["fog"];
7
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,18 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ const DEFAULT_BACKDROP_FOG_COLOR = "#1a1c22";
3
+ function DirectionalShadowLight({ entry }) {
4
+ const size = entry.shadowCameraSize ?? 40;
5
+ return (_jsx("directionalLight", { position: [entry.position[0], entry.position[1], entry.position[2]], intensity: entry.intensity ?? 1.3, color: entry.color, castShadow: entry.castShadow ?? false, "shadow-mapSize-width": entry.shadowMapSize ?? 1024, "shadow-mapSize-height": entry.shadowMapSize ?? 1024, "shadow-camera-left": -size, "shadow-camera-right": size, "shadow-camera-top": size, "shadow-camera-bottom": -size, "shadow-camera-near": 0.5, "shadow-camera-far": Math.max(200, size * 6), "shadow-bias": entry.shadowBias ?? -0.0004, "shadow-normalBias": entry.shadowNormalBias ?? 0.02 }));
6
+ }
7
+ export function ConfiguredLighting({ lighting }) {
8
+ return (_jsxs(_Fragment, { children: [lighting.ambient !== undefined ? (_jsx("ambientLight", { color: lighting.ambient.color, intensity: lighting.ambient.intensity ?? 0.55 })) : null, lighting.hemisphere !== undefined ? (_jsx("hemisphereLight", { args: [
9
+ lighting.hemisphere.skyColor ?? "#bfe3ff",
10
+ lighting.hemisphere.groundColor ?? "#4c6b34",
11
+ lighting.hemisphere.intensity ?? 0.55,
12
+ ] })) : null, (lighting.directional ?? []).map((entry, index) => (_jsx(DirectionalShadowLight, { entry: entry }, index)))] }));
13
+ }
14
+ export function BackdropFog({ fog }) {
15
+ if (fog === undefined)
16
+ return null;
17
+ return fog.density !== undefined ? (_jsx("fogExp2", { attach: "fog", args: [fog.color ?? DEFAULT_BACKDROP_FOG_COLOR, fog.density] })) : (_jsx("fog", { attach: "fog", args: [fog.color ?? DEFAULT_BACKDROP_FOG_COLOR, fog.near ?? 10, fog.far ?? 200] }));
18
+ }
@@ -0,0 +1,14 @@
1
+ import { type ReactNode } from "react";
2
+ import type { EntitySpriteConfig, ModelConfig } from "@jgengine/core/game/playableGame";
3
+ export declare function EntitySprite({ sprite }: {
4
+ sprite: EntitySpriteConfig;
5
+ }): import("react").JSX.Element;
6
+ export declare function IsolatedEntityModel({ model, instanceId, fallback, }: {
7
+ model: ModelConfig;
8
+ instanceId?: string;
9
+ fallback?: ReactNode;
10
+ }): import("react").JSX.Element;
11
+ export declare function EntityModel({ model, instanceId }: {
12
+ model: ModelConfig;
13
+ instanceId?: string;
14
+ }): import("react").JSX.Element;