@jgengine/shell 0.18.1 → 0.21.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 (261) hide show
  1. package/CHANGELOG.md +504 -7
  2. package/dist/GameHost.d.ts +12 -1
  3. package/dist/GameHost.js +14 -1
  4. package/dist/GamePlayerShell.js +132 -17
  5. package/dist/Shell3dPresentation.d.ts +5 -11
  6. package/dist/Shell3dPresentation.js +73 -11
  7. package/dist/ShellChrome.d.ts +2 -1
  8. package/dist/ShellChrome.js +22 -6
  9. package/dist/ShellHudPresentation.d.ts +20 -2
  10. package/dist/ShellHudPresentation.js +41 -6
  11. package/dist/audio/AudioComponents.js +6 -2
  12. package/dist/audio/audioEngine.d.ts +24 -0
  13. package/dist/audio/audioEngine.js +231 -83
  14. package/dist/audio/audioWire.d.ts +3 -0
  15. package/dist/audio/audioWire.js +9 -3
  16. package/dist/audio/loopParams.d.ts +39 -0
  17. package/dist/audio/loopParams.js +43 -0
  18. package/dist/audio/synthEngine.d.ts +7 -1
  19. package/dist/audio/synthEngine.js +103 -38
  20. package/dist/camera/GameCameraRig.js +3 -2
  21. package/dist/camera/GameFirstPersonCamera.d.ts +6 -4
  22. package/dist/camera/GameFirstPersonCamera.js +68 -27
  23. package/dist/camera/Viewports.d.ts +34 -0
  24. package/dist/camera/Viewports.js +123 -0
  25. package/dist/camera/cameraRigs.js +76 -62
  26. package/dist/camera/chaseHeading.d.ts +9 -0
  27. package/dist/camera/chaseHeading.js +10 -0
  28. package/dist/camera/customMuzzle.d.ts +14 -0
  29. package/dist/camera/customMuzzle.js +68 -0
  30. package/dist/camera/index.d.ts +1 -0
  31. package/dist/camera/index.js +1 -0
  32. package/dist/camera/orbitCameraMath.js +2 -2
  33. package/dist/camera/rigMath.d.ts +82 -4
  34. package/dist/camera/rigMath.js +133 -15
  35. package/dist/camera/rigResolve.d.ts +3 -0
  36. package/dist/camera/rigResolve.js +11 -0
  37. package/dist/combat/ProjectileModels.d.ts +31 -0
  38. package/dist/combat/ProjectileModels.js +70 -0
  39. package/dist/commandSink.d.ts +1 -2
  40. package/dist/commandSink.js +3 -2
  41. package/dist/defineGame.d.ts +19 -3
  42. package/dist/defineGame.js +39 -7
  43. package/dist/devtools/DevtoolsOverlay.d.ts +2 -69
  44. package/dist/devtools/DevtoolsOverlay.js +15 -167
  45. package/dist/devtools/DevtoolsRuntime.d.ts +7 -0
  46. package/dist/devtools/DevtoolsRuntime.js +61 -0
  47. package/dist/devtools/agentBridge.d.ts +6 -0
  48. package/dist/devtools/agentBridge.js +16 -2
  49. package/dist/devtools/devtoolsReports.d.ts +68 -0
  50. package/dist/devtools/devtoolsReports.js +100 -0
  51. package/dist/devtools/latencyInstrumentation.d.ts +3 -0
  52. package/dist/devtools/latencyInstrumentation.js +11 -0
  53. package/dist/devtools/playtestProbe.d.ts +4 -0
  54. package/dist/devtools/playtestProbe.js +22 -0
  55. package/dist/diagnostics/FpsCounter.d.ts +17 -0
  56. package/dist/diagnostics/FpsCounter.js +32 -0
  57. package/dist/drivers/FrameDriver.js +35 -20
  58. package/dist/drivers/FrameRateLimiter.d.ts +9 -0
  59. package/dist/drivers/FrameRateLimiter.js +39 -0
  60. package/dist/drivers/HudOnlyDriver.d.ts +6 -1
  61. package/dist/drivers/HudOnlyDriver.js +32 -14
  62. package/dist/environment/DayNightSky.js +5 -5
  63. package/dist/environment/Daylight.d.ts +35 -1
  64. package/dist/environment/Daylight.js +98 -28
  65. package/dist/environment/EnvironmentScene.js +11 -10
  66. package/dist/environment/VolumetricClouds.js +20 -2
  67. package/dist/environment/daylightCycle.d.ts +20 -0
  68. package/dist/environment/daylightCycle.js +59 -7
  69. package/dist/environment/sunShadowMath.d.ts +45 -0
  70. package/dist/environment/sunShadowMath.js +50 -0
  71. package/dist/input/gamepadPoll.d.ts +47 -0
  72. package/dist/input/gamepadPoll.js +142 -0
  73. package/dist/input/gamepadSource.d.ts +11 -1
  74. package/dist/input/gamepadSource.js +117 -71
  75. package/dist/input/mouseLook.js +2 -1
  76. package/dist/input/padHaptics.d.ts +17 -0
  77. package/dist/input/padHaptics.js +32 -0
  78. package/dist/input/pointerLock.d.ts +11 -0
  79. package/dist/input/pointerLock.js +19 -0
  80. package/dist/inputSink.d.ts +6 -6
  81. package/dist/inputSink.js +60 -21
  82. package/dist/lazyShell3dPresentation.d.ts +9 -0
  83. package/dist/lazyShell3dPresentation.js +24 -0
  84. package/dist/materialOverride.d.ts +42 -0
  85. package/dist/materialOverride.js +95 -36
  86. package/dist/multiplayer.d.ts +33 -3
  87. package/dist/multiplayer.js +105 -22
  88. package/dist/pointer/pointerService.d.ts +2 -0
  89. package/dist/pointer/pointerService.js +12 -2
  90. package/dist/postfx/PostProcessing.d.ts +11 -1
  91. package/dist/postfx/PostProcessing.js +14 -42
  92. package/dist/postfx/gradeShader.d.ts +1 -1
  93. package/dist/postfx/gradeShader.js +30 -2
  94. package/dist/postfx/shadowPrepass.d.ts +3 -0
  95. package/dist/postfx/shadowPrepass.js +18 -0
  96. package/dist/postfx/stylizePass.d.ts +23 -0
  97. package/dist/postfx/stylizePass.js +216 -0
  98. package/dist/presentationRecovery.d.ts +75 -0
  99. package/dist/presentationRecovery.js +204 -0
  100. package/dist/presentationRecovery3d.d.ts +4 -0
  101. package/dist/presentationRecovery3d.js +32 -0
  102. package/dist/presentationResolve.d.ts +8 -1
  103. package/dist/presentationResolve.js +5 -2
  104. package/dist/render/CascadedShadows.d.ts +7 -2
  105. package/dist/render/CascadedShadows.js +98 -20
  106. package/dist/render/EnvironmentLighting.js +74 -35
  107. package/dist/render/HairCards.d.ts +7 -0
  108. package/dist/render/HairCards.js +23 -0
  109. package/dist/render/SceneModels.js +52 -78
  110. package/dist/render/SkinnedInstances.d.ts +91 -0
  111. package/dist/render/SkinnedInstances.js +249 -0
  112. package/dist/render/TextureFiltering.d.ts +16 -0
  113. package/dist/render/TextureFiltering.js +64 -0
  114. package/dist/render/authoredSurfaceMaterial.d.ts +43 -0
  115. package/dist/render/authoredSurfaceMaterial.js +108 -0
  116. package/dist/render/displayColor.d.ts +33 -0
  117. package/dist/render/displayColor.js +138 -0
  118. package/dist/render/materialAppearanceSignals.d.ts +23 -0
  119. package/dist/render/materialAppearanceSignals.js +58 -0
  120. package/dist/render/materialAsset.d.ts +98 -0
  121. package/dist/render/materialAsset.js +454 -0
  122. package/dist/render/measureBounds.js +16 -3
  123. package/dist/render/measureCollisionMesh.js +8 -1
  124. package/dist/render/modelAssets.d.ts +8 -0
  125. package/dist/render/modelAssets.js +43 -0
  126. package/dist/render/modelBindPose.d.ts +5 -0
  127. package/dist/render/modelBindPose.js +35 -0
  128. package/dist/render/modelLoad.d.ts +32 -0
  129. package/dist/render/modelLoad.js +70 -1
  130. package/dist/render/modelNodeVisibility.d.ts +6 -0
  131. package/dist/render/modelNodeVisibility.js +18 -0
  132. package/dist/render/modelRender.d.ts +18 -1
  133. package/dist/render/modelRender.js +81 -3
  134. package/dist/render/projectedOverdraw.d.ts +35 -0
  135. package/dist/render/projectedOverdraw.js +204 -0
  136. package/dist/render/resolveModel.d.ts +12 -2
  137. package/dist/render/resolveModel.js +25 -4
  138. package/dist/render/rigNode.d.ts +26 -0
  139. package/dist/render/rigNode.js +32 -0
  140. package/dist/render/spriteRender.d.ts +4 -0
  141. package/dist/render/spriteRender.js +5 -0
  142. package/dist/render/staticShapeInstances.d.ts +34 -0
  143. package/dist/render/staticShapeInstances.js +78 -0
  144. package/dist/render/useFootIk.d.ts +53 -4
  145. package/dist/render/useFootIk.js +262 -70
  146. package/dist/render/useModelAnimation.d.ts +50 -0
  147. package/dist/render/useModelAnimation.js +245 -99
  148. package/dist/render/useModelInstance.d.ts +25 -0
  149. package/dist/render/useModelInstance.js +43 -0
  150. package/dist/scatter/scatterModels.d.ts +3 -2
  151. package/dist/scatter/scatterModels.js +15 -7
  152. package/dist/scene/AuthoredScene.d.ts +23 -6
  153. package/dist/scene/AuthoredScene.js +46 -9
  154. package/dist/scene/CityRenderer.js +6 -6
  155. package/dist/scene/GeneratedAssetRenderer.js +11 -42
  156. package/dist/settings/QuickControls.js +1 -1
  157. package/dist/settings/SettingsMenu.js +11 -18
  158. package/dist/settings/SettingsRuntime.d.ts +5 -0
  159. package/dist/settings/SettingsRuntime.js +29 -2
  160. package/dist/settings/appliedSettings.d.ts +9 -3
  161. package/dist/settings/appliedSettings.js +6 -5
  162. package/dist/settings/settingsController.d.ts +3 -0
  163. package/dist/settings/settingsController.js +47 -3
  164. package/dist/shellControlSuspension.d.ts +18 -0
  165. package/dist/shellControlSuspension.js +28 -0
  166. package/dist/shellInputPublication.d.ts +28 -0
  167. package/dist/shellInputPublication.js +49 -0
  168. package/dist/shellPointerInput.d.ts +18 -0
  169. package/dist/shellPointerInput.js +49 -0
  170. package/dist/structures/BuildingKitBatch.d.ts +3 -1
  171. package/dist/structures/BuildingKitBatch.js +35 -56
  172. package/dist/structures/BuildingRenderBudget.d.ts +23 -0
  173. package/dist/structures/BuildingRenderBudget.js +54 -0
  174. package/dist/structures/GeneratedBuilding.d.ts +3 -15
  175. package/dist/structures/GeneratedBuilding.js +33 -22
  176. package/dist/structures/buildingKitMaterials.d.ts +20 -0
  177. package/dist/structures/buildingKitMaterials.js +70 -0
  178. package/dist/structures/buildingSpatialBatch.d.ts +11 -0
  179. package/dist/structures/buildingSpatialBatch.js +45 -0
  180. package/dist/structures/buildingSurface.d.ts +21 -0
  181. package/dist/structures/buildingSurface.js +134 -0
  182. package/dist/terrain/GrassField.d.ts +2 -1
  183. package/dist/terrain/GrassField.js +59 -5
  184. package/dist/terrain/grassGeometry.d.ts +10 -0
  185. package/dist/terrain/grassGeometry.js +55 -0
  186. package/dist/terrain/grassMaterial.js +2 -2
  187. package/dist/terrain/terrainDetailMaterial.d.ts +3 -3
  188. package/dist/terrain/terrainDetailMaterial.js +7 -7
  189. package/dist/touch/TouchControlsOverlay.js +4 -4
  190. package/dist/useShellMultiplayerSync.d.ts +1 -1
  191. package/dist/useShellMultiplayerSync.js +9 -3
  192. package/dist/vfx/ParticleField.d.ts +23 -20
  193. package/dist/vfx/ParticleField.js +168 -50
  194. package/dist/visibility/CullingProvider.js +2 -62
  195. package/dist/visibility/cullingDriver.d.ts +9 -0
  196. package/dist/visibility/cullingDriver.js +68 -0
  197. package/dist/weather/AuthoredWeatherLayer.d.ts +16 -0
  198. package/dist/weather/AuthoredWeatherLayer.js +31 -0
  199. package/dist/weather/DustField.d.ts +2 -1
  200. package/dist/weather/DustField.js +17 -3
  201. package/dist/weather/FireSpreadLayer.d.ts +4 -2
  202. package/dist/weather/FireSpreadLayer.js +5 -11
  203. package/dist/weather/LightningStrike.d.ts +4 -1
  204. package/dist/weather/LightningStrike.js +13 -17
  205. package/dist/weather/RainField.d.ts +2 -1
  206. package/dist/weather/RainField.js +17 -3
  207. package/dist/weather/RainImpactField.d.ts +16 -0
  208. package/dist/weather/RainImpactField.js +89 -0
  209. package/dist/weather/SnowField.d.ts +2 -1
  210. package/dist/weather/SnowField.js +17 -3
  211. package/dist/weather/WeatherLayer.d.ts +11 -2
  212. package/dist/weather/WeatherLayer.js +30 -6
  213. package/dist/weather/index.d.ts +2 -0
  214. package/dist/weather/index.js +2 -0
  215. package/dist/weather/weatherMath.d.ts +2 -0
  216. package/dist/weather/weatherMath.js +8 -0
  217. package/dist/weather/weatherShelter.d.ts +18 -0
  218. package/dist/weather/weatherShelter.js +75 -0
  219. package/dist/weather/weatherUniforms.d.ts +40 -1
  220. package/dist/weather/weatherUniforms.js +29 -9
  221. package/dist/world/AuthoredFlocks.d.ts +22 -0
  222. package/dist/world/AuthoredFlocks.js +87 -0
  223. package/dist/world/StaticObjectModels.d.ts +8 -0
  224. package/dist/world/StaticObjectModels.js +75 -0
  225. package/dist/world/WorldHud.d.ts +14 -6
  226. package/dist/world/WorldHud.js +25 -12
  227. package/dist/world/WorldParticles.d.ts +10 -23
  228. package/dist/world/WorldParticles.js +210 -62
  229. package/dist/world/WorldScene.d.ts +3 -1
  230. package/dist/world/WorldScene.js +41 -26
  231. package/dist/world/staticObjectBatches.d.ts +35 -0
  232. package/dist/world/staticObjectBatches.js +163 -0
  233. package/dist/world/worldBarSamples.d.ts +37 -33
  234. package/dist/world/worldBarSamples.js +117 -75
  235. package/package.json +4 -4
  236. package/skills/ce-handoff/SKILL.md +2 -2
  237. package/skills/game-design/SKILL.md +2 -2
  238. package/skills/jgengine/SKILL.md +28 -6
  239. package/skills/jgengine/api.md +139 -99
  240. package/skills/jgengine/capabilities.md +28 -0
  241. package/skills/jgengine/recipes/minimal-game.md +10 -2
  242. package/skills/jgengine-ui/SKILL.md +16 -17
  243. package/skills/jgengine-ui/api.md +603 -365
  244. package/skills/jgengine-ui/capabilities.md +131 -1
  245. package/skills/jgengine-ui/reference-primitives.md +179 -0
  246. package/skills/jgengine-ui/reference.md +4 -81
  247. package/skills/jgengine-world/SKILL.md +25 -4
  248. package/skills/jgengine-world/api.md +1045 -655
  249. package/skills/jgengine-world/capabilities.md +246 -9
  250. package/skills/jgengine-world/recipes/character-animation.md +62 -0
  251. package/skills/jgengine-world/recipes/controls.md +48 -0
  252. package/skills/jgengine-world/recipes/flight-feel.md +76 -0
  253. package/skills/jgengine-world/recipes/npc-senses.md +55 -0
  254. package/skills/jgengine-world/recipes/vehicle-feel.md +87 -0
  255. package/skills/jgengine-world/reference.md +180 -4
  256. package/skills/jgengine-world/visibility.md +139 -0
  257. package/skills/level-design/SKILL.md +1 -1
  258. package/dist/render/testFixtures/proceduralSpriteAtlas.d.ts +0 -15
  259. package/dist/render/testFixtures/proceduralSpriteAtlas.js +0 -30
  260. package/dist/render/testFixtures/proceduralTileLayer.d.ts +0 -13
  261. package/dist/render/testFixtures/proceduralTileLayer.js +0 -30
@@ -2,7 +2,21 @@ import { devtools } from "@jgengine/core/devtools/devtools";
2
2
  import { getSaveEndpoint } from "@jgengine/core/devtools/saveEndpoint";
3
3
  import { isHudAnchor, listActiveHudLayouts, } from "@jgengine/core/ui/hudLayout";
4
4
  import { listHudPanelTypes, resolvePanelResize, resizePanelSize, } from "@jgengine/core/ui/hudDocument";
5
- import { buildFullReport, buildLeanReport } from "./DevtoolsOverlay.js";
5
+ import { buildFullReport, buildLeanReport } from "./devtoolsReports.js";
6
+ /**
7
+ * @internal
8
+ * Resolves true once `isLive()` reports a mounted editor host, polling every `intervalMs` up to
9
+ * `timeoutMs`. `editor_summon` awaits this so the next RPC verb in a `drive` batch finds the editor.
10
+ */
11
+ export async function waitForEditorHost(isLive, timeoutMs = 15_000, intervalMs = 100, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
12
+ for (let waited = 0;; waited += intervalMs) {
13
+ if (isLive())
14
+ return true;
15
+ if (waited >= timeoutMs)
16
+ return false;
17
+ await sleep(intervalMs);
18
+ }
19
+ }
6
20
  async function saveScene(editorHost) {
7
21
  const endpoint = getSaveEndpoint();
8
22
  if (endpoint === null)
@@ -239,7 +253,7 @@ export function installAgentBridge(options) {
239
253
  return { ok: false, error: "no editor summoner on this page — open with ?mode=editor" };
240
254
  }
241
255
  window.__jgengineSummonEditor();
242
- return { ok: true, live: false };
256
+ return waitForEditorHost(() => window.__jgengineEditorHost !== undefined).then((live) => live ? { ok: true, live: true } : { ok: false, error: "editor did not mount within 15s" });
243
257
  }
244
258
  case "save_scene": {
245
259
  const editorHost = window.__jgengineEditorHost;
@@ -0,0 +1,68 @@
1
+ import { type DevtoolsSnapshot } from "@jgengine/core/devtools/devtools";
2
+ import type { PlayableGame } from "../registry.js";
3
+ /** @internal */
4
+ export declare function buildLeanReport(playable: PlayableGame): {
5
+ game: string;
6
+ at: number;
7
+ why: string | null;
8
+ frame: {
9
+ fps: number;
10
+ avgFrameMs: number;
11
+ p95FrameMs: number;
12
+ maxFrameMs: number;
13
+ avgSimMs: number;
14
+ maxSimMs: number;
15
+ avgOutsideMs: number;
16
+ maxOutsideMs: number;
17
+ longFrames: number;
18
+ samples: number;
19
+ phases: {
20
+ name: string;
21
+ avgMs: number;
22
+ maxMs: number;
23
+ pctOfSim: number;
24
+ }[];
25
+ } | null;
26
+ render: import("@jgengine/core/devtools/types").RenderSample | null;
27
+ latency: import("@jgengine/core/devtools/types").LatencyStats | null;
28
+ longFrameSummary: {
29
+ count: number;
30
+ culprits: {
31
+ culprit: string;
32
+ count: number;
33
+ }[];
34
+ maxFrameMs: number;
35
+ maxDrawCalls: number | undefined;
36
+ maxGeometries: number | undefined;
37
+ } | null;
38
+ longFrames: {
39
+ at: number;
40
+ frameMs: number;
41
+ simMs: number;
42
+ outsideMs: number;
43
+ culprit: string;
44
+ reason: string;
45
+ phases: {
46
+ name: string;
47
+ ms: number;
48
+ }[];
49
+ probes: Record<string, unknown>;
50
+ render: import("@jgengine/core/devtools/types").RenderSample | null;
51
+ }[];
52
+ probes: Record<string, unknown>;
53
+ logs: import("@jgengine/core/devtools/types").DevtoolsLogEntry[] | undefined;
54
+ controls: {
55
+ name: string;
56
+ value: unknown;
57
+ initial: unknown;
58
+ }[] | undefined;
59
+ discovered: {
60
+ id: string;
61
+ value: unknown;
62
+ }[] | undefined;
63
+ discoveredCount: number;
64
+ };
65
+ /** @internal */
66
+ export declare function buildFullReport(playable: PlayableGame): DevtoolsSnapshot & {
67
+ game: string;
68
+ };
@@ -0,0 +1,100 @@
1
+ import { devtools } from "@jgengine/core/devtools/devtools";
2
+ import { diagnose } from "./perfDiagnose.js";
3
+ function roundMs(value, digits = 1) {
4
+ const scale = 10 ** digits;
5
+ return Math.round(value * scale) / scale;
6
+ }
7
+ function compactLongFrame(event) {
8
+ return {
9
+ at: event.at,
10
+ frameMs: roundMs(event.frameMs),
11
+ simMs: roundMs(event.simMs),
12
+ outsideMs: roundMs(event.outsideMs),
13
+ culprit: event.culprit,
14
+ reason: event.reason,
15
+ phases: event.phases.slice(0, 4).map((phase) => ({
16
+ name: phase.name,
17
+ ms: roundMs(phase.ms),
18
+ })),
19
+ probes: event.probes,
20
+ render: event.render,
21
+ };
22
+ }
23
+ function summarizeLongFrames(events) {
24
+ if (events.length === 0)
25
+ return null;
26
+ const byCulprit = new Map();
27
+ let maxFrameMs = 0;
28
+ let maxDraws = 0;
29
+ let maxGeometries = 0;
30
+ for (const event of events) {
31
+ byCulprit.set(event.culprit, (byCulprit.get(event.culprit) ?? 0) + 1);
32
+ maxFrameMs = Math.max(maxFrameMs, event.frameMs);
33
+ if (event.render !== null) {
34
+ maxDraws = Math.max(maxDraws, event.render.drawCalls);
35
+ maxGeometries = Math.max(maxGeometries, event.render.geometries);
36
+ }
37
+ }
38
+ const culprits = [...byCulprit.entries()]
39
+ .sort((a, b) => b[1] - a[1])
40
+ .map(([culprit, count]) => ({ culprit, count }));
41
+ return {
42
+ count: events.length,
43
+ culprits,
44
+ maxFrameMs: roundMs(maxFrameMs),
45
+ maxDrawCalls: maxDraws > 0 ? maxDraws : undefined,
46
+ maxGeometries: maxGeometries > 0 ? maxGeometries : undefined,
47
+ };
48
+ }
49
+ /** @internal */
50
+ export function buildLeanReport(playable) {
51
+ const snap = devtools.snapshot();
52
+ const frame = snap.frame;
53
+ const longs = snap.longFrames;
54
+ const why = frame !== null ? diagnose(frame, longs) : null;
55
+ const changedControls = snap.controls
56
+ .filter((control) => !Object.is(control.value, control.initial))
57
+ .map((control) => ({ name: control.name, value: control.value, initial: control.initial }));
58
+ const enabledDiscovered = snap.discovered
59
+ .filter((entry) => entry.enabled)
60
+ .map((entry) => ({ id: entry.id, value: entry.value }));
61
+ const hotLogs = snap.logs.filter((entry) => entry.level === "warn" || entry.level === "error").slice(-20);
62
+ return {
63
+ game: playable.game.name,
64
+ at: snap.at,
65
+ why,
66
+ frame: frame === null
67
+ ? null
68
+ : {
69
+ fps: roundMs(frame.fps, 1),
70
+ avgFrameMs: roundMs(frame.avgFrameMs),
71
+ p95FrameMs: roundMs(frame.p95FrameMs),
72
+ maxFrameMs: roundMs(frame.maxFrameMs),
73
+ avgSimMs: roundMs(frame.avgSimMs),
74
+ maxSimMs: roundMs(frame.maxSimMs),
75
+ avgOutsideMs: roundMs(frame.avgOutsideMs),
76
+ maxOutsideMs: roundMs(frame.maxOutsideMs),
77
+ longFrames: frame.longFrames,
78
+ samples: frame.samples,
79
+ phases: frame.phases.slice(0, 8).map((phase) => ({
80
+ name: phase.name,
81
+ avgMs: roundMs(phase.avgMs),
82
+ maxMs: roundMs(phase.maxMs),
83
+ pctOfSim: Math.round(phase.pctOfSim),
84
+ })),
85
+ },
86
+ render: snap.render,
87
+ latency: snap.latency,
88
+ longFrameSummary: summarizeLongFrames(longs),
89
+ longFrames: longs.slice(-6).map(compactLongFrame),
90
+ probes: snap.probes,
91
+ logs: hotLogs.length > 0 ? hotLogs : undefined,
92
+ controls: changedControls.length > 0 ? changedControls : undefined,
93
+ discovered: enabledDiscovered.length > 0 ? enabledDiscovered : undefined,
94
+ discoveredCount: snap.discovered.length,
95
+ };
96
+ }
97
+ /** @internal */
98
+ export function buildFullReport(playable) {
99
+ return { game: playable.game.name, ...devtools.snapshot() };
100
+ }
@@ -0,0 +1,3 @@
1
+ import type { ShellMultiplayer } from "../multiplayer.js";
2
+ /** @internal */
3
+ export declare function withDevtoolsLatency(multiplayer: ShellMultiplayer): ShellMultiplayer;
@@ -0,0 +1,11 @@
1
+ import { instrumentLatency } from "@jgengine/core/devtools/devtools";
2
+ /** @internal */
3
+ export function withDevtoolsLatency(multiplayer) {
4
+ return {
5
+ ...multiplayer,
6
+ backend: {
7
+ ...instrumentLatency(multiplayer.backend, ["pushFeedEntry"]),
8
+ transport: instrumentLatency(multiplayer.backend.transport, ["joinServer", "leaveServer", "runCommand"]),
9
+ },
10
+ };
11
+ }
@@ -0,0 +1,4 @@
1
+ type Probe = () => Record<string, number>;
2
+ /** @internal Attach a live capture probe for one shell boot; cleanup retires retained readers. */
3
+ export declare function attachPlaytestProbe(host: object, read: Probe): () => void;
4
+ export {};
@@ -0,0 +1,22 @@
1
+ /** @internal Attach a live capture probe for one shell boot; cleanup retires retained readers. */
2
+ export function attachPlaytestProbe(host, read) {
3
+ const target = host;
4
+ let active = true;
5
+ const probe = () => {
6
+ if (!active)
7
+ return {};
8
+ try {
9
+ const value = read();
10
+ return value !== null && typeof value === "object" ? value : {};
11
+ }
12
+ catch {
13
+ return {};
14
+ }
15
+ };
16
+ target.__jgProbe = probe;
17
+ return () => {
18
+ active = false;
19
+ if (target.__jgProbe === probe)
20
+ delete target.__jgProbe;
21
+ };
22
+ }
@@ -0,0 +1,17 @@
1
+ import { type ReactElement } from "react";
2
+ /** Mutable frame tally shared between the in-Canvas probe and the DOM readout. */
3
+ export interface FrameTally {
4
+ frames: number;
5
+ }
6
+ /** Counts rendered frames. Mount inside the Canvas. @internal */
7
+ export declare function FpsProbe({ tally }: {
8
+ tally: FrameTally;
9
+ }): null;
10
+ /**
11
+ * Player-facing frames-per-second readout, sampled twice a second from rendered frames (not
12
+ * display refreshes), so it reflects the frame-rate limit and GPU load.
13
+ * @internal shell-internal; shown by the `graphics.showFps` setting.
14
+ */
15
+ export declare function FpsCounter({ tally }: {
16
+ tally: FrameTally;
17
+ }): ReactElement;
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useFrame } from "@react-three/fiber";
3
+ import { useEffect, useRef } from "react";
4
+ /** Counts rendered frames. Mount inside the Canvas. @internal */
5
+ export function FpsProbe({ tally }) {
6
+ useFrame(() => {
7
+ tally.frames += 1;
8
+ });
9
+ return null;
10
+ }
11
+ /**
12
+ * Player-facing frames-per-second readout, sampled twice a second from rendered frames (not
13
+ * display refreshes), so it reflects the frame-rate limit and GPU load.
14
+ * @internal shell-internal; shown by the `graphics.showFps` setting.
15
+ */
16
+ export function FpsCounter({ tally }) {
17
+ const label = useRef(null);
18
+ useEffect(() => {
19
+ let last = performance.now();
20
+ tally.frames = 0;
21
+ const timer = setInterval(() => {
22
+ const now = performance.now();
23
+ const fps = (tally.frames * 1000) / Math.max(1, now - last);
24
+ tally.frames = 0;
25
+ last = now;
26
+ if (label.current !== null)
27
+ label.current.textContent = `${fps < 10 ? fps.toFixed(1) : Math.round(fps)} FPS`;
28
+ }, 500);
29
+ return () => clearInterval(timer);
30
+ }, [tally]);
31
+ return (_jsx("div", { className: "pointer-events-none absolute bottom-1 right-1 z-50 rounded bg-black/60 px-2 py-0.5 font-mono text-xs tabular-nums text-emerald-300", children: _jsx("span", { ref: label, children: "-- FPS" }) }));
32
+ }
@@ -6,6 +6,8 @@ import { pointerAimFor } from "../shellPointer.js";
6
6
  import { shellDrivesPlayerPose } from "../shellMovement.js";
7
7
  import { steerYaw } from "@jgengine/core/movement/steering";
8
8
  import { stepPlayerMovement, resolvePlayerMovementTuning } from "@jgengine/core/movement/playerMovement";
9
+ import { localPlayers } from "@jgengine/core/runtime/localPlayers";
10
+ import { seatCameraYaw } from "../camera/Viewports.js";
9
11
  import { isServerAuthoritative } from "@jgengine/core/runtime/adapter";
10
12
  import { resolveCommandSink } from "../commandSink.js";
11
13
  import { inputFramesEqual, resolveInputSink } from "../inputSink.js";
@@ -27,12 +29,12 @@ export function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryC
27
29
  const repeatFiredAtRef = useRef(new Map());
28
30
  const slotActions = useMemo(() => findHotbarSlotActions(playable.game.input), [playable]);
29
31
  const hotbarId = useMemo(() => hotbarIdFor(playable), [playable]);
30
- const movementTuning = useMemo(() => resolvePlayerMovementTuning({
31
- collision: playable.collision,
32
- movement: playable.movement,
33
- physics: playable.game.physics,
34
- world: playable.game.world,
35
- }), [playable]);
32
+ const movementTuning = useMemo(() => ({ ...resolvePlayerMovementTuning({
33
+ collision: playable.collision,
34
+ movement: playable.movement,
35
+ physics: playable.game.physics,
36
+ world: playable.game.world,
37
+ }), authoritativeStep: true }), [playable]);
36
38
  const autoPickupRadius = useMemo(() => {
37
39
  const cfg = playable.worldItem?.autoPickup;
38
40
  if (cfg === undefined || cfg === false)
@@ -57,7 +59,6 @@ export function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryC
57
59
  }).send(frame),
58
60
  }), [multiplayer, serverAuthoritative, serverIdRef]);
59
61
  const lastSentInputRef = useRef(null);
60
- const inputActions = useMemo(() => Object.keys(playable.game.input ?? {}), [playable]);
61
62
  const predictionRef = useRef(createPredictionBuffer({
62
63
  initial: [0, 0, 0],
63
64
  maxTicks: 120,
@@ -105,7 +106,7 @@ export function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryC
105
106
  inputSink.send(frame);
106
107
  };
107
108
  if (gateRef.current || !playControlsActive(ctx)) {
108
- ctx.input.publish(heldActionsFor(tracker, NO_ACTIONS));
109
+ ctx.input.publish(heldActionsFor(tracker, NO_ACTIONS), { reset: true });
109
110
  ctx.input.publishAnalog(null);
110
111
  sendInput();
111
112
  return;
@@ -114,7 +115,7 @@ export function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryC
114
115
  try {
115
116
  let endPhase = devtools.profile.begin("time+input");
116
117
  const dt = Math.min(rawDt, 0.05);
117
- ctx.input.publish(heldActionsFor(tracker, inputActions));
118
+ ctx.input.publish(heldActionsFor(tracker, tracker.actions()), {});
118
119
  ctx.input.publishPointer(pointerAxisRef.current);
119
120
  ctx.input.publishAnalog(analogRef.current);
120
121
  sendInput();
@@ -123,22 +124,35 @@ export function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryC
123
124
  yawRef.current = steerYaw(yawRef.current, turnInput, TURN_SPEED, dt);
124
125
  endPhase();
125
126
  const playerId = ctx.player.possession.active(ctx.player.userId);
126
- ctx.sim.advance(dt, (stepDt) => {
127
- const gameDt = ctx.time.advance(stepDt);
128
- ctx.sim.runStages("beforeMovement", stepDt);
127
+ ctx.sim.advance(dt, (stepDt, _tick, gameDt) => {
128
+ ctx.input.beginStep();
129
+ const movementDt = serverAuthoritative ? stepDt : gameDt;
130
+ if (!serverAuthoritative)
131
+ ctx.sim.runStages("beforeMovement", stepDt);
129
132
  const player = ctx.scene.entity.get(playerId);
130
133
  // Server-authoritative sessions still run the deterministic local step as a prediction; the
131
134
  // replicated pose is the confirmation that the transport reconciles on the next world diff.
132
- if (player !== null && drivesPose) {
135
+ if (player !== null && drivesPose && movementDt > 0) {
133
136
  const endPose = devtools.profile.begin("pose");
134
- stepPlayerMovement(ctx, ctx.player.userId, { held: ctx.input.held(), pointer: ctx.input.pointer(), analog: ctx.input.analog() }, stepDt, movementTuning, yawRef.current, pitchRef.current);
137
+ stepPlayerMovement(ctx, ctx.player.userId, { held: ctx.input.held(), pointer: ctx.input.pointer(), analog: ctx.input.analog() }, movementDt, movementTuning, yawRef.current, pitchRef.current);
135
138
  const predicted = ctx.scene.entity.get(playerId);
136
139
  if (predicted !== null) {
137
- predictionRef.current.record({ held: ctx.input.held(), pointer: ctx.input.pointer(), analog: ctx.input.analog(), tick: ctx.sim.tick() }, stepDt, predicted.position);
140
+ predictionRef.current.record({ held: ctx.input.held(), pointer: ctx.input.pointer(), analog: ctx.input.analog(), tick: ctx.sim.tick() }, movementDt, predicted.position);
138
141
  }
139
142
  endPose();
140
143
  }
141
- ctx.sim.runStages("afterMovement", stepDt);
144
+ if (drivesPose && !serverAuthoritative && movementDt > 0) {
145
+ const table = localPlayers(ctx);
146
+ const seats = table.slots();
147
+ for (let index = 1; index < seats.length; index += 1) {
148
+ const seat = table.local(seats[index].slotId);
149
+ if (seat === null)
150
+ continue;
151
+ stepPlayerMovement(ctx, seat.userId, { held: seat.input.held(), pointer: null, analog: seat.input.analog() }, movementDt, movementTuning, seatCameraYaw(ctx, seat.userId));
152
+ }
153
+ }
154
+ if (!serverAuthoritative)
155
+ ctx.sim.runStages("afterMovement", stepDt);
142
156
  if (autoPickupRadius !== null && !serverAuthoritative) {
143
157
  const endPickup = devtools.profile.begin("pickup");
144
158
  const self = ctx.scene.entity.get(playerId);
@@ -155,18 +169,19 @@ export function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryC
155
169
  });
156
170
  advanceBehaviors(ctx, gameDt);
157
171
  }
158
- ctx.sim.runStages("afterTick", stepDt);
159
- });
172
+ if (!serverAuthoritative)
173
+ ctx.sim.runStages("afterTick", stepDt);
174
+ }, { advanceTime: !serverAuthoritative });
160
175
  endPhase = devtools.profile.begin("actions");
161
176
  if (tracker.wasPressed("tabTarget")) {
162
177
  if (ctx.game.commands.has("target.cycle"))
163
- ctx.game.commands.run("target.cycle", {});
178
+ commandSink.run("target.cycle", {});
164
179
  else
165
180
  ctx.scene.entity.cycleTarget(playerId, { filter: "hostile" });
166
181
  }
167
182
  if (tracker.wasPressed("clearTarget")) {
168
183
  if (ctx.game.commands.has("target.clear"))
169
- ctx.game.commands.run("target.clear", {});
184
+ commandSink.run("target.clear", {});
170
185
  else
171
186
  ctx.scene.entity.setTarget(playerId, null);
172
187
  }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Drives the Canvas at a capped frame rate. Mount only with `frameloop="never"`: it runs its
3
+ * own requestAnimationFrame loop and advances R3F on the refreshes `paceFrame` admits, so
4
+ * simulation, useFrame and rendering all run at the cap with correct deltas.
5
+ * @internal shell-internal; driven by the player's frame-rate limit setting.
6
+ */
7
+ export declare function FrameRateLimiter({ fps }: {
8
+ fps: number;
9
+ }): null;
@@ -0,0 +1,39 @@
1
+ import { useThree } from "@react-three/fiber";
2
+ import { useEffect } from "react";
3
+ import { paceFrame } from "@jgengine/core/settings/frameRateLimit";
4
+ /**
5
+ * Drives the Canvas at a capped frame rate. Mount only with `frameloop="never"`: it runs its
6
+ * own requestAnimationFrame loop and advances R3F on the refreshes `paceFrame` admits, so
7
+ * simulation, useFrame and rendering all run at the cap with correct deltas.
8
+ * @internal shell-internal; driven by the player's frame-rate limit setting.
9
+ */
10
+ export function FrameRateLimiter({ fps }) {
11
+ const advance = useThree((state) => state.advance);
12
+ const get = useThree((state) => state.get);
13
+ useEffect(() => {
14
+ if (fps <= 0)
15
+ return;
16
+ // R3F's "never" mode reads the timestamp as seconds on its own clock; continue that clock.
17
+ const base = get().clock.elapsedTime;
18
+ let start = null;
19
+ let anchor = 0;
20
+ let frame = requestAnimationFrame(function tick(now) {
21
+ frame = requestAnimationFrame(tick);
22
+ if (start === null) {
23
+ start = now;
24
+ anchor = now;
25
+ }
26
+ const next = paceFrame(now, anchor, fps);
27
+ if (next === null)
28
+ return;
29
+ anchor = next;
30
+ advance(base + (now - start) / 1000, true);
31
+ });
32
+ return () => {
33
+ cancelAnimationFrame(frame);
34
+ // Hand the clock back to R3F's own loop in milliseconds so its next delta is one frame.
35
+ get().clock.oldTime = performance.now();
36
+ };
37
+ }, [fps, advance, get]);
38
+ return null;
39
+ }
@@ -1,9 +1,14 @@
1
1
  import type { GameContext } from "@jgengine/core/runtime/gameContext";
2
2
  import type { PointerAxisState } from "@jgengine/core/input/pointerAxis";
3
3
  import type { ActionStateTracker } from "@jgengine/core/input/actionBindings";
4
+ import type { ShellMultiplayer } from "../multiplayer.js";
4
5
  import type { PlayableGame } from "../registry.js";
5
- export declare function HudOnlyDriver({ ctx, playable, tracker, pointerAxisRef, gateRef, onRuntimeError, }: {
6
+ export declare function HudOnlyDriver({ ctx, multiplayer, serverIdRef, playable, tracker, pointerAxisRef, gateRef, onRuntimeError, }: {
6
7
  ctx: GameContext;
8
+ multiplayer: ShellMultiplayer | null;
9
+ serverIdRef: {
10
+ current: string | null;
11
+ };
7
12
  playable: PlayableGame;
8
13
  tracker: ActionStateTracker<string>;
9
14
  pointerAxisRef: {
@@ -3,12 +3,17 @@ import { dispatchBoundAction, heldActionsFor, shouldFireBoundAction } from "../b
3
3
  import { advanceBehaviors } from "@jgengine/core/scene/behaviorRuntime";
4
4
  import { playControlsActive } from "@jgengine/core/game/controlGate";
5
5
  import { devtools } from "@jgengine/core/devtools/devtools";
6
+ import { isServerAuthoritative } from "@jgengine/core/runtime/adapter";
7
+ import { resolveCommandSink } from "../commandSink.js";
8
+ import { inputFramesEqual, resolveInputSink } from "../inputSink.js";
6
9
  import { EMPTY_RESERVED, NO_ACTIONS } from "../shellConstants.js";
7
- export function HudOnlyDriver({ ctx, playable, tracker, pointerAxisRef, gateRef, onRuntimeError, }) {
10
+ export function HudOnlyDriver({ ctx, multiplayer, serverIdRef, playable, tracker, pointerAxisRef, gateRef, onRuntimeError, }) {
8
11
  const hasReportedTickError = useRef(false);
9
12
  const repeatFiredAtRef = useRef(new Map());
10
13
  const lastFrameRef = useRef(null);
11
14
  const inputActions = useMemo(() => Object.keys(playable.game.input ?? {}), [playable]);
15
+ const lastSentInputRef = useRef(null);
16
+ const serverAuthoritative = isServerAuthoritative(playable.game.multiplayer) && multiplayer !== null;
12
17
  useEffect(() => {
13
18
  let frameId;
14
19
  const tick = (now) => {
@@ -17,8 +22,19 @@ export function HudOnlyDriver({ ctx, playable, tracker, pointerAxisRef, gateRef,
17
22
  lastFrameRef.current = now;
18
23
  if (last === null)
19
24
  return;
25
+ const sendInput = () => {
26
+ if (!serverAuthoritative || serverIdRef.current === null)
27
+ return;
28
+ const frame = { held: ctx.input.held(), pointer: ctx.input.pointer(), analog: ctx.input.analog() };
29
+ if (lastSentInputRef.current !== null && inputFramesEqual(lastSentInputRef.current, frame))
30
+ return;
31
+ lastSentInputRef.current = frame;
32
+ resolveInputSink({ serverAuthoritative, backend: multiplayer?.backend ?? null, serverId: serverIdRef.current }).send(frame);
33
+ };
20
34
  if (gateRef.current || !playControlsActive(ctx)) {
21
- ctx.input.publish(heldActionsFor(tracker, NO_ACTIONS));
35
+ ctx.input.publish(heldActionsFor(tracker, NO_ACTIONS), { reset: true });
36
+ ctx.input.publishAnalog(null);
37
+ sendInput();
22
38
  return;
23
39
  }
24
40
  const rawDt = (now - last) / 1000;
@@ -26,26 +42,28 @@ export function HudOnlyDriver({ ctx, playable, tracker, pointerAxisRef, gateRef,
26
42
  try {
27
43
  let endPhase = devtools.profile.begin("time+input");
28
44
  const dt = Math.min(rawDt, 0.05);
29
- ctx.input.publish(heldActionsFor(tracker, inputActions));
45
+ ctx.input.publish(heldActionsFor(tracker, tracker.actions()), {});
30
46
  ctx.input.publishPointer(pointerAxisRef.current);
47
+ sendInput();
31
48
  endPhase();
32
- ctx.sim.advance(dt, (stepDt) => {
33
- const gameDt = ctx.time.advance(stepDt);
34
- ctx.sim.runStages("beforeMovement", stepDt);
35
- ctx.sim.runStages("afterMovement", stepDt);
36
- devtools.profile.measure("onTick", () => {
37
- playable.loop.onTick(ctx, gameDt);
49
+ if (!serverAuthoritative)
50
+ ctx.sim.advance(dt, (stepDt, _tick, gameDt) => {
51
+ ctx.input.beginStep();
52
+ ctx.sim.runStages("beforeMovement", stepDt);
53
+ ctx.sim.runStages("afterMovement", stepDt);
54
+ devtools.profile.measure("onTick", () => {
55
+ playable.loop.onTick(ctx, gameDt);
56
+ });
57
+ advanceBehaviors(ctx, gameDt);
58
+ ctx.sim.runStages("afterTick", stepDt);
38
59
  });
39
- advanceBehaviors(ctx, gameDt);
40
- ctx.sim.runStages("afterTick", stepDt);
41
- });
42
60
  endPhase = devtools.profile.begin("actions");
43
61
  const nowMs = performance.now();
44
62
  for (const action of inputActions) {
45
63
  if (!shouldFireBoundAction(tracker, action, playable.game.input, repeatFiredAtRef.current, nowMs))
46
64
  continue;
47
65
  repeatFiredAtRef.current.set(action, nowMs);
48
- dispatchBoundAction(ctx, action, 0, 0, { yaw: 0, pitch: 0 }, EMPTY_RESERVED);
66
+ dispatchBoundAction(ctx, action, 0, 0, { yaw: 0, pitch: 0 }, EMPTY_RESERVED, resolveCommandSink(ctx, { serverAuthoritative, backend: multiplayer?.backend ?? null, serverId: serverIdRef.current }));
49
67
  }
50
68
  tracker.endFrame();
51
69
  endPhase();
@@ -60,6 +78,6 @@ export function HudOnlyDriver({ ctx, playable, tracker, pointerAxisRef, gateRef,
60
78
  };
61
79
  frameId = requestAnimationFrame(tick);
62
80
  return () => cancelAnimationFrame(frameId);
63
- }, [ctx, playable, tracker, pointerAxisRef, gateRef, onRuntimeError, inputActions]);
81
+ }, [ctx, playable, tracker, pointerAxisRef, gateRef, onRuntimeError, inputActions, multiplayer, serverIdRef, serverAuthoritative]);
64
82
  return null;
65
83
  }
@@ -2,7 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  import { useFrame } from "@react-three/fiber";
3
3
  import { useMemo, useRef } from "react";
4
4
  import * as THREE from "three";
5
- import { SkyDome } from "./Daylight.js";
5
+ import { setSkyDomeColors, SkyDome, SunLight } from "./Daylight.js";
6
6
  import { daylightStateAt } from "./daylightCycle.js";
7
7
  const HEMI_GROUND = "#4c6b34";
8
8
  /**
@@ -22,22 +22,22 @@ export function DayNightSky({ cycle, radius, hazeStrength, sunGlowStrength, ligh
22
22
  const hemiRef = useRef(null);
23
23
  const initial = useMemo(() => cycle.sample(), [cycle]);
24
24
  const initialSun = useMemo(() => daylightStateAt(initial.dayFraction).sunPosition, [initial.dayFraction]);
25
+ const sunDirectionRef = useRef(initialSun);
25
26
  useFrame(() => {
26
27
  const sample = cycle.sample();
27
28
  const geometry = daylightStateAt(sample.dayFraction);
28
29
  const material = skyMaterialRef.current;
29
30
  if (material !== null) {
30
- material.uniforms.topColor.value.set(sample.lightColor);
31
- material.uniforms.bottomColor.value.set(sample.color);
31
+ setSkyDomeColors(material, sample.lightColor, sample.color);
32
32
  material.uniforms.uSunColor.value.set(sample.lightColor);
33
33
  material.uniforms.uSunDirection.value
34
34
  .set(geometry.sunPosition[0], geometry.sunPosition[1], geometry.sunPosition[2])
35
35
  .normalize();
36
36
  material.uniforms.uSunIntensity.value = sample.intensity;
37
37
  }
38
+ sunDirectionRef.current = geometry.sunPosition;
38
39
  const sun = sunRef.current;
39
40
  if (sun !== null) {
40
- sun.position.set(geometry.sunPosition[0], geometry.sunPosition[1], geometry.sunPosition[2]);
41
41
  sun.intensity = sample.intensity * keyLightIntensity;
42
42
  sun.color.set(sample.lightColor);
43
43
  }
@@ -47,5 +47,5 @@ export function DayNightSky({ cycle, radius, hazeStrength, sunGlowStrength, ligh
47
47
  hemi.color.set(sample.lightColor);
48
48
  }
49
49
  });
50
- return (_jsxs(_Fragment, { children: [_jsx(SkyDome, { topColor: initial.lightColor, horizonColor: initial.color, sunDirection: initialSun, sunColor: initial.lightColor, sunIntensity: initial.intensity, materialRef: skyMaterialRef, ...(radius === undefined ? {} : { radius }), ...(hazeStrength === undefined ? {} : { hazeStrength }), ...(sunGlowStrength === undefined ? {} : { sunGlowStrength }) }), lights ? (_jsxs(_Fragment, { children: [_jsx("hemisphereLight", { ref: hemiRef, args: [initial.lightColor, HEMI_GROUND, initial.intensity * ambientIntensity] }), _jsx("directionalLight", { ref: sunRef, position: initialSun, intensity: initial.intensity * keyLightIntensity, color: initial.lightColor, castShadow: true, "shadow-mapSize-width": 2048, "shadow-mapSize-height": 2048, "shadow-camera-left": -90, "shadow-camera-right": 90, "shadow-camera-top": 90, "shadow-camera-bottom": -90, "shadow-camera-near": 10, "shadow-camera-far": 520, "shadow-bias": -0.0004, "shadow-normalBias": 0.02 })] })) : null] }));
50
+ return (_jsxs(_Fragment, { children: [_jsx(SkyDome, { topColor: initial.lightColor, horizonColor: initial.color, sunDirection: initialSun, sunColor: initial.lightColor, sunIntensity: initial.intensity, materialRef: skyMaterialRef, ...(radius === undefined ? {} : { radius }), ...(hazeStrength === undefined ? {} : { hazeStrength }), ...(sunGlowStrength === undefined ? {} : { sunGlowStrength }) }), lights ? (_jsxs(_Fragment, { children: [_jsx("hemisphereLight", { ref: hemiRef, args: [initial.lightColor, HEMI_GROUND, initial.intensity * ambientIntensity] }), _jsx(SunLight, { direction: initialSun, directionRef: sunDirectionRef, lightRef: sunRef, intensity: initial.intensity * keyLightIntensity, color: initial.lightColor })] })) : null] }));
51
51
  }