@jgengine/shell 0.11.0 → 0.13.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 (151) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/CombatPresentation.d.ts +5 -0
  3. package/dist/CombatPresentation.js +8 -0
  4. package/dist/GameHost.d.ts +27 -1
  5. package/dist/GameHost.js +83 -4
  6. package/dist/GamePlayer.d.ts +5 -0
  7. package/dist/GamePlayer.js +5 -0
  8. package/dist/GamePlayerShell.d.ts +5 -0
  9. package/dist/GamePlayerShell.js +63 -359
  10. package/dist/GameUiPreview.js +3 -0
  11. package/dist/Shell3dPresentation.d.ts +63 -0
  12. package/dist/Shell3dPresentation.js +302 -0
  13. package/dist/ShellChrome.d.ts +51 -0
  14. package/dist/ShellChrome.js +51 -0
  15. package/dist/ShellHudPresentation.d.ts +41 -0
  16. package/dist/ShellHudPresentation.js +22 -0
  17. package/dist/audio/audioEngine.d.ts +4 -0
  18. package/dist/audio/audioEngine.js +111 -26
  19. package/dist/audio/audioWire.d.ts +22 -4
  20. package/dist/audio/audioWire.js +46 -3
  21. package/dist/audio/loopParams.d.ts +20 -0
  22. package/dist/audio/loopParams.js +28 -0
  23. package/dist/camera/GameCameraRig.js +6 -1
  24. package/dist/camera/GameOrbitCamera.js +7 -1
  25. package/dist/camera/cameraCollision.d.ts +28 -0
  26. package/dist/camera/cameraCollision.js +34 -0
  27. package/dist/camera/cameraRigs.d.ts +3 -1
  28. package/dist/camera/cameraRigs.js +24 -10
  29. package/dist/camera/index.d.ts +1 -0
  30. package/dist/camera/index.js +1 -0
  31. package/dist/camera/rigMath.d.ts +27 -0
  32. package/dist/camera/rigMath.js +45 -0
  33. package/dist/defineGame.d.ts +15 -1
  34. package/dist/defineGame.js +26 -4
  35. package/dist/devtools/DevtoolsOverlay.d.ts +4 -4
  36. package/dist/devtools/DevtoolsOverlay.js +28 -12
  37. package/dist/devtools/agentBridge.js +4 -0
  38. package/dist/devtools/perfDiagnose.js +1 -1
  39. package/dist/diagnostics/RuntimeDiagnostics.d.ts +7 -0
  40. package/dist/diagnostics/RuntimeDiagnostics.js +23 -12
  41. package/dist/environment/EnvironmentScene.js +17 -3
  42. package/dist/environment/RoadJunctions.d.ts +12 -0
  43. package/dist/environment/RoadJunctions.js +69 -0
  44. package/dist/environment/RoadRibbons.d.ts +8 -2
  45. package/dist/environment/RoadRibbons.js +26 -8
  46. package/dist/environment/terrainGroundColor.d.ts +9 -0
  47. package/dist/environment/terrainGroundColor.js +22 -0
  48. package/dist/gameKit.d.ts +27 -0
  49. package/dist/gameKit.js +29 -0
  50. package/dist/map/MapMarkerBeacons.d.ts +3 -2
  51. package/dist/postfx/PostProcessing.d.ts +9 -1
  52. package/dist/postfx/PostProcessing.js +11 -4
  53. package/dist/presentationResolve.d.ts +39 -0
  54. package/dist/presentationResolve.js +53 -0
  55. package/dist/registry.d.ts +1 -0
  56. package/dist/render/PartMotion.d.ts +18 -0
  57. package/dist/render/PartMotion.js +91 -0
  58. package/dist/render/SceneModels.d.ts +9 -2
  59. package/dist/render/SceneModels.js +31 -180
  60. package/dist/render/assetBase.d.ts +17 -0
  61. package/dist/render/assetBase.js +35 -0
  62. package/dist/render/measureBounds.d.ts +34 -0
  63. package/dist/render/measureBounds.js +127 -0
  64. package/dist/render/modelLoad.js +4 -1
  65. package/dist/render/resolveModel.d.ts +15 -0
  66. package/dist/render/resolveModel.js +43 -2
  67. package/dist/render/useModelAnimation.d.ts +10 -0
  68. package/dist/render/useModelAnimation.js +198 -0
  69. package/dist/scatter/InstancedScatter.js +5 -0
  70. package/dist/scatter/scatterProxies.js +24 -0
  71. package/dist/scene/AuthoredScene.d.ts +3 -1
  72. package/dist/scene/AuthoredScene.js +7 -2
  73. package/dist/scene/CityRenderer.d.ts +2 -0
  74. package/dist/scene/CityRenderer.js +1031 -0
  75. package/dist/scene/GrassFieldRenderer.js +53 -5
  76. package/dist/scene/PoleLineRenderer.d.ts +2 -0
  77. package/dist/scene/PoleLineRenderer.js +287 -0
  78. package/dist/scene/SoilPatchRenderer.js +70 -7
  79. package/dist/scene/WaterRenderer.js +34 -10
  80. package/dist/scene/builtinSceneKindRenderers.js +4 -0
  81. package/dist/scene/cityWindowMaterial.d.ts +16 -0
  82. package/dist/scene/cityWindowMaterial.js +81 -0
  83. package/dist/scene/sceneKindRenderers.d.ts +2 -0
  84. package/dist/settings/appliedSettings.d.ts +2 -1
  85. package/dist/settings/appliedSettings.js +4 -2
  86. package/dist/structures/GeneratedBuilding.d.ts +4 -0
  87. package/dist/structures/GeneratedBuilding.js +17 -7
  88. package/dist/structures/TransformGizmo.d.ts +11 -1
  89. package/dist/structures/TransformGizmo.js +9 -5
  90. package/dist/terrain/GrassField.d.ts +11 -3
  91. package/dist/terrain/GrassField.js +16 -6
  92. package/dist/terrain/grassGeometry.d.ts +33 -1
  93. package/dist/terrain/grassGeometry.js +83 -21
  94. package/dist/terrain/grassMaterial.d.ts +18 -0
  95. package/dist/terrain/grassMaterial.js +66 -13
  96. package/dist/terrain/heightfieldGeometry.d.ts +34 -0
  97. package/dist/terrain/heightfieldGeometry.js +109 -0
  98. package/dist/terrain/index.d.ts +1 -0
  99. package/dist/terrain/index.js +1 -0
  100. package/dist/terrain/soilPatchMaterial.d.ts +15 -2
  101. package/dist/terrain/soilPatchMaterial.js +46 -8
  102. package/dist/terrain/terrainDetailMaterial.js +15 -2
  103. package/dist/water/Ocean.d.ts +7 -1
  104. package/dist/water/Ocean.js +16 -3
  105. package/dist/water/OceanConfig.d.ts +9 -0
  106. package/dist/water/OceanConfig.js +6 -0
  107. package/dist/water/OceanMaterial.d.ts +12 -0
  108. package/dist/water/OceanMaterial.js +9 -0
  109. package/dist/water/OceanShader.d.ts +2 -2
  110. package/dist/water/OceanShader.js +78 -10
  111. package/dist/world/PlaceScene.d.ts +11 -0
  112. package/dist/world/PlaceScene.js +27 -0
  113. package/dist/world/WorldHud.d.ts +3 -1
  114. package/dist/world/WorldHud.js +2 -2
  115. package/dist/world/WorldScene.js +32 -4
  116. package/dist/worldSky.d.ts +7 -1
  117. package/dist/worldSky.js +11 -1
  118. package/package.json +4 -4
  119. package/skills/game-design/SKILL.md +28 -0
  120. package/skills/game-design/agents/openai.yaml +4 -0
  121. package/skills/game-design/references/application-playbook.md +170 -0
  122. package/skills/game-design/references/field-guide.md +288 -0
  123. package/skills/jgengine/SKILL.md +8 -4
  124. package/skills/jgengine/api.md +182 -81
  125. package/skills/jgengine/capabilities.md +104 -2
  126. package/skills/jgengine/recipes/minimal-game.md +88 -0
  127. package/skills/jgengine-ui/SKILL.md +17 -10
  128. package/skills/jgengine-ui/api.md +316 -51
  129. package/skills/jgengine-ui/capabilities.md +135 -2
  130. package/skills/jgengine-ui/recipes/portable-minimap.md +150 -0
  131. package/skills/jgengine-ui/reference.md +59 -9
  132. package/skills/jgengine-ui/references/visual-scorecard.md +1 -1
  133. package/skills/jgengine-world/SKILL.md +13 -0
  134. package/skills/jgengine-world/api.md +453 -21
  135. package/skills/jgengine-world/capabilities.md +112 -2
  136. package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
  137. package/skills/jgengine-world/reference.md +43 -4
  138. package/skills/level-design/SKILL.md +54 -0
  139. package/skills/level-design/agents/openai.yaml +4 -0
  140. package/skills/level-design/references/application-playbook.md +192 -0
  141. package/skills/level-design/references/field-guide.md +337 -0
  142. package/skills/level-design/references/live-editor-loop.md +179 -0
  143. package/dist/cartridge/runtime.d.ts +0 -15
  144. package/dist/cartridge/runtime.js +0 -556
  145. package/dist/cartridge/spec.d.ts +0 -188
  146. package/dist/cartridge/spec.js +0 -19
  147. package/dist/cartridge/validate.d.ts +0 -2
  148. package/dist/cartridge/validate.js +0 -78
  149. package/dist/cartridge.d.ts +0 -145
  150. package/dist/cartridge.js +0 -246
  151. package/skills/jgengine/reference-cartridge.md +0 -72
@@ -1,77 +1,60 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Canvas } from "@react-three/fiber";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
3
2
  import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from "react";
4
3
  import { createActionStateTracker, toActionStateBindingMap, } from "@jgengine/core/input/actionBindings";
5
4
  import { RESERVED_INPUT_ACTIONS } from "./boundActionDispatch.js";
6
- import { resolveWorldSky } from "./worldSky.js";
7
- import { pointerAimFor, pointerContextMenu } from "./shellPointer.js";
8
5
  import { deriveTouchScheme, withTouchCodes, DEFAULT_TOUCH_STYLE } from "@jgengine/core/input/touchScheme";
9
- import { contextVerbInput, } from "@jgengine/core/interaction/contextMenu";
10
6
  import { normalizePointerToAxis } from "@jgengine/core/input/pointerAxis";
11
- import { createSelectionSet, isMarquee, screenRect, selectWithinRect, } from "@jgengine/core/scene/selection";
12
7
  import { createGameContext } from "@jgengine/core/runtime/gameContext";
13
- import { DEFAULT_PICKUP_RADIUS } from "@jgengine/core/game/worldItem";
14
8
  import { useDisplayProfile } from "@jgengine/react/display";
15
- import { HudViewportProvider } from "@jgengine/react/hudViewport";
16
- import { GameViewportProvider } from "@jgengine/react/gameViewport";
17
9
  import { RotateDeviceScreen } from "@jgengine/react/rotateDevice";
18
- import { GameProvider } from "@jgengine/react/provider";
19
- import { CAMERA_FRUSTUM_DEFAULTS } from "@jgengine/core/game/playableGame";
20
10
  import { createSettingsStore } from "@jgengine/core/settings/settingsModel";
21
11
  import { applyBindingOverrides, clearBindingOverride, loadBindingOverrides, saveBindingOverride, } from "@jgengine/core/input/bindingOverrides";
22
- import { playControlsActive } from "@jgengine/core/game/controlGate";
23
12
  import { orientationGateActive, orientationHintActive, resolveOrientationRequirement, } from "@jgengine/core/ui/orientation";
24
- import { sky as resolveSkyDescriptor } from "@jgengine/core/world/features";
25
- import { resolveGameLook } from "@jgengine/core/render/lookPreset";
26
- import { AudioListener, EntityAudioEmitters, ObjectAudioEmitters } from "./audio/AudioComponents.js";
13
+ import { armFallbackSeams } from "@jgengine/core/devtools/fallbackSeams";
14
+ import { readUrlFlag, subscribeUrlChange, writeUrlParam } from "@jgengine/core/devtools/urlFlags";
27
15
  import { createAudioEngine } from "./audio/audioEngine.js";
28
16
  import { attachAudioEventWire } from "./audio/audioWire.js";
29
- import { PostProcessing } from "./postfx/PostProcessing.js";
30
- import { EnvironmentLighting } from "./render/EnvironmentLighting.js";
31
- import { CollisionDebugWorld } from "./devtools/CollisionDebugWorld.js";
32
- import { DevtoolsOverlay, DevtoolsRendererProbe, withDevtoolsLatency } from "./devtools/DevtoolsOverlay.js";
33
17
  import { installAgentBridge } from "./devtools/agentBridge.js";
34
- import { GameCameraRig, PlayerFovProvider, PlayerFovSlider, resolveRigKind, rtsPanKeysConflict, } from "./camera/index.js";
35
- import { CullingProvider } from "./visibility/CullingProvider.js";
36
- import { SkyDaylight, TimeOfDayDaylight } from "./environment.js";
37
- import { resolveSkyLightOwnership, skyEmitsLights } from "./environment/skyLightingPolicy.js";
38
- import { EnvironmentScene } from "./environment/EnvironmentScene.js";
39
- import { PointerProbe } from "./pointer/PointerProbe.js";
40
- import { MarqueeBox, ContextMenuView } from "./pointer/PointerOverlays.js";
41
- import { createPointerService } from "./pointer/pointerService.js";
42
- import { CombatCameraShake, ProjectileTracers, Reticle, WorldEntityBars, WorldFloatText, WorldNameplates, WorldTelegraphs, } from "./world/WorldHud.js";
43
- import { WorldSpellVfx } from "./world/WorldVfx.js";
44
- import { RetainedVfx } from "./world/RetainedVfx.js";
45
- import { GridWorldScene } from "./world/GridWorldScene.js";
46
- import { WorldItems } from "./world/WorldItems.js";
18
+ import { withDevtoolsLatency } from "./devtools/DevtoolsOverlay.js";
19
+ import { resolveRigKind } from "./camera/index.js";
20
+ import { contextModels } from "./render/resolveModel.js";
47
21
  import { OrientationHint } from "./touch/OrientationHint.js";
48
- import { TouchControlsDock, TouchPlaySurface, touchDockClearance } from "./touch/TouchControlsOverlay.js";
49
- import { BUILT_IN_SETTING_CATEGORIES } from "@jgengine/core/settings/settingsModel";
50
- import { SettingsProvider } from "@jgengine/react/settings";
51
- import { SettingsRuntime } from "./settings/SettingsRuntime.js";
52
- import { SettingsChrome } from "./settings/SettingsChrome.js";
53
- import { AudioSettingsBridge, useGraphicsSettings, useTouchStyle } from "./settings/appliedSettings.js";
54
- import { ConfiguredLighting, BackdropFog } from "./render/SceneLighting.js";
55
- import { WorldView, RemotePlayers } from "./world/WorldScene.js";
56
- import { FrameDriver } from "./drivers/FrameDriver.js";
57
- import { HudOnlyDriver } from "./drivers/HudOnlyDriver.js";
58
- import { DiagnosticOverlay, GameUiErrorBoundary, logRuntimeError, } from "./diagnostics/RuntimeDiagnostics.js";
59
- import { GamePhaseStamp } from "./GamePhaseStamp.js";
22
+ import { useTouchStyle, useGraphicsSettings } from "./settings/appliedSettings.js";
23
+ import { logRuntimeError, } from "./diagnostics/RuntimeDiagnostics.js";
60
24
  import { EMPTY_RESERVED } from "./shellConstants.js";
61
25
  import { useShellMultiplayerSync } from "./useShellMultiplayerSync.js";
26
+ import { ShellHudPresentation } from "./ShellHudPresentation.js";
27
+ import { Shell3dPresentation } from "./Shell3dPresentation.js";
62
28
  const DEV_USER_ID = "dev-player";
63
- const PRIMARY_CLICK_MOVE_THRESHOLD_PX = 6;
64
- const DEFAULT_BACKGROUND_COLOR = "#14161b";
29
+ /** The query param that mirrors the devtools overlay into the URL — present = open. */
30
+ const DEBUG_PARAM = "debug";
65
31
  export { applyMotionImpulses } from "@jgengine/core/runtime/motionIntents";
66
32
  export { nearbyObstacles } from "@jgengine/core/movement/movementModel";
67
33
  export { resolvePhysicsTuning } from "@jgengine/core/movement/playerMovement";
68
34
  export { hasEnvironmentTerrain } from "@jgengine/core/world/terrain";
35
+ /**
36
+ * The shell body `GameHost` renders after resolving multiplayer. Hosts that resolve their own
37
+ * sessions (the dev runner) render it directly; games mount through `GameHost`.
38
+ * @internal
39
+ */
69
40
  export function GamePlayerShell({ playable, multiplayer: rawMultiplayer = null, poster = false, onContextReady, }) {
70
41
  const multiplayer = useMemo(() => (rawMultiplayer === null ? null : withDevtoolsLatency(rawMultiplayer)), [rawMultiplayer]);
71
42
  const devtoolsEnabled = playable.devtools !== false && !poster;
72
- const [devtoolsOpen, setDevtoolsOpen] = useState(false);
43
+ armFallbackSeams(devtoolsEnabled);
44
+ // `?debug` mirrors the devtools overlay into the URL: open with a link, strip the param to close.
45
+ const [devtoolsOpen, setDevtoolsOpen] = useState(() => devtoolsEnabled && readUrlFlag(DEBUG_PARAM));
73
46
  const devtoolsOpenRef = useRef(false);
74
47
  devtoolsOpenRef.current = devtoolsOpen;
48
+ useEffect(() => {
49
+ if (!devtoolsEnabled)
50
+ return;
51
+ writeUrlParam(DEBUG_PARAM, devtoolsOpen ? "1" : null);
52
+ }, [devtoolsEnabled, devtoolsOpen]);
53
+ useEffect(() => {
54
+ if (!devtoolsEnabled)
55
+ return;
56
+ return subscribeUrlChange(() => setDevtoolsOpen(readUrlFlag(DEBUG_PARAM)));
57
+ }, [devtoolsEnabled]);
75
58
  useEffect(() => installAgentBridge({
76
59
  playable,
77
60
  devtoolsEnabled,
@@ -89,15 +72,8 @@ export function GamePlayerShell({ playable, multiplayer: rawMultiplayer = null,
89
72
  const serverIdRef = useRef(null);
90
73
  const cameraDraggingRef = useRef(false);
91
74
  const primaryClickRef = useRef(false);
92
- const pointerDownRef = useRef(null);
93
75
  const pointerAxisRef = useRef(null);
94
- const marqueeStartRef = useRef(null);
95
76
  const f2HeldRef = useRef(false);
96
- const pointerService = useMemo(() => createPointerService(), []);
97
- const selection = useMemo(() => createSelectionSet(), [playable]);
98
- const [marquee, setMarquee] = useState(null);
99
- const [selectedIds, setSelectedIds] = useState(() => new Set());
100
- const [contextMenu, setContextMenu] = useState(null);
101
77
  const settingsStore = useMemo(() => createSettingsStore(), []);
102
78
  const [bindingOverrides, setBindingOverrides] = useState(() => loadBindingOverrides(playable.game.name));
103
79
  useEffect(() => {
@@ -179,10 +155,12 @@ export function GamePlayerShell({ playable, multiplayer: rawMultiplayer = null,
179
155
  useEffect(() => {
180
156
  setDiagnostics([]);
181
157
  try {
158
+ const models = contextModels(playable);
182
159
  const context = createGameContext({
183
160
  definition: playable.game,
184
161
  content: playable.content,
185
162
  player: { userId, isNew: true },
163
+ ...(models === undefined ? {} : { models }),
186
164
  });
187
165
  playable.loop.onInit(context);
188
166
  playable.loop.onNewPlayer(context);
@@ -204,315 +182,41 @@ export function GamePlayerShell({ playable, multiplayer: rawMultiplayer = null,
204
182
  useSyncExternalStore(ctx?.subscribe ?? (() => () => undefined), ctx?.version ?? (() => 0), ctx?.version ?? (() => 0));
205
183
  if (ctx === null)
206
184
  return _jsx("div", { className: "h-full w-full bg-neutral-950" });
207
- const GameUI = playable.GameUI;
208
- const WorldOverlay = playable.WorldOverlay;
209
- const controlledEntityId = ctx.player.possession.active(userId);
210
185
  const cameraConfig = playable.camera?.followEntityId !== undefined
211
186
  ? playable.camera
212
- : { ...playable.camera, followEntityId: controlledEntityId };
187
+ : { ...playable.camera, followEntityId: ctx.player.possession.active(userId) };
213
188
  const rigKind = resolveRigKind(cameraConfig);
189
+ const shared = {
190
+ playable,
191
+ ctx,
192
+ multiplayer,
193
+ tracker,
194
+ pointerAxisRef,
195
+ gateRef,
196
+ wrapperRef,
197
+ f2HeldRef,
198
+ yawRef,
199
+ pitchRef,
200
+ touchScheme,
201
+ touchSink,
202
+ orientationGate,
203
+ orientationGateEl,
204
+ coarsePointer,
205
+ diagnostics,
206
+ devtoolsEnabled,
207
+ devtoolsOpen,
208
+ setDevtoolsOpen,
209
+ reportRuntimeError,
210
+ trackPointerAxis,
211
+ deactivatePointerAxis,
212
+ };
214
213
  if (rigKind === "none" || playable.presentation === "hud") {
215
- const GameUI = playable.GameUI;
216
- return (_jsx("div", { ref: wrapperRef, tabIndex: 0, className: "relative h-full w-full bg-neutral-950 outline-none", onKeyDown: (event) => {
217
- if (event.code === "F2") {
218
- event.preventDefault();
219
- f2HeldRef.current = true;
220
- return;
221
- }
222
- if (f2HeldRef.current) {
223
- if (event.code === "KeyD" && devtoolsEnabled) {
224
- event.preventDefault();
225
- setDevtoolsOpen((current) => !current);
226
- }
227
- return;
228
- }
229
- if (event.code === "Tab" || event.code === "Space")
230
- event.preventDefault();
231
- if (playControlsActive(ctx))
232
- tracker.handleDown(event.code);
233
- }, onKeyUp: (event) => {
234
- if (event.code === "F2") {
235
- f2HeldRef.current = false;
236
- return;
237
- }
238
- if (playControlsActive(ctx))
239
- tracker.handleUp(event.code);
240
- }, onBlur: () => {
241
- f2HeldRef.current = false;
242
- tracker.reset();
243
- }, onPointerDown: () => audioEngine.resume(), onPointerMove: trackPointerAxis, onPointerLeave: deactivatePointerAxis, onPointerCancel: deactivatePointerAxis, children: _jsxs(GameViewportProvider, { platforms: playable.platforms, children: [_jsx(HudOnlyDriver, { ctx: ctx, playable: playable, tracker: tracker, pointerAxisRef: pointerAxisRef, gateRef: gateRef, onRuntimeError: reportRuntimeError }), !orientationGate && coarsePointer && touchScheme !== null && touchScheme.gestures !== null && playControlsActive(ctx) ? (_jsx(TouchPlaySurface, { scheme: touchScheme, sink: touchSink, yawRef: yawRef, pitchRef: pitchRef, maxPitch: 0, onPrimaryTap: () => undefined })) : null, _jsx(GameUiErrorBoundary, { onRuntimeError: reportRuntimeError, children: _jsxs(GameProvider, { context: ctx, children: [_jsx(GamePhaseStamp, {}), _jsx(HudViewportProvider, { platforms: playable.platforms, config: playable.hudFit, userScale: graphics.uiScale, children: orientationGate ? null : _jsx(GameUI, {}) })] }) }), orientationGateEl, devtoolsEnabled ? (_jsx(DevtoolsOverlay, { open: devtoolsOpen, ctx: ctx, playable: playable, multiplayer: multiplayer })) : null, _jsx(DiagnosticOverlay, { diagnostics: diagnostics, gameName: playable.game.name })] }) }));
214
+ return (_jsx(ShellHudPresentation, { ...shared, uiScale: graphics.uiScale, onPointerResumeAudio: () => audioEngine.resume() }));
244
215
  }
245
- const firstPerson = rigKind === "first";
246
- const showReticle = (firstPerson && playable.camera?.firstPerson?.reticle !== false) || rigKind === "shoulder";
247
- const rtsPanKeysEnabled = !rtsPanKeysConflict(playable.game.input);
248
- const worldBars = playable.worldHealthBars;
249
- const barsStatId = worldBars === undefined || worldBars === false
250
- ? null
251
- : worldBars === true
252
- ? "health"
253
- : worldBars.statId ?? "health";
254
- const barsRoles = worldBars === undefined || worldBars === true || worldBars === false ? undefined : worldBars.roles;
255
- const barsMaxDistance = worldBars === undefined || worldBars === true || worldBars === false
256
- ? undefined
257
- : worldBars.maxDistance;
258
- const nameplates = playable.nameplates;
259
- const nameplatesStatId = nameplates === undefined || nameplates === false
260
- ? null
261
- : nameplates === true
262
- ? "health"
263
- : nameplates.statId ?? "health";
264
- const nameplatesRoles = nameplates === undefined || nameplates === true || nameplates === false ? undefined : nameplates.roles;
265
- const nameplatesMaxDistance = nameplates === undefined || nameplates === true || nameplates === false ? undefined : nameplates.maxDistance;
266
- const resolveEntityRole = (entity) => playable.content.entityById?.(entity.name)?.role;
267
- const pointer = playable.pointer;
268
- const pointerUsesLeft = pointer !== undefined && (pointer.select === true || pointer.moveCommand !== undefined);
269
- const selectFilter = pointer?.selectFilter;
270
- const worldSky = resolveWorldSky(playable.game.world);
271
- const world = playable.game.world;
272
- const biomeBands = world?.kind === "environment" ? world.terrain?.biomeBands : undefined;
273
- const AutoEnvironment = playable.environment ??
274
- (world?.kind === "environment"
275
- ? () => _jsx(EnvironmentScene, { feature: world })
276
- : world?.kind === "biomes" || world?.kind === "voxel" || world?.kind === "plots" || world?.kind === "tilemap"
277
- ? () => _jsx(GridWorldScene, { feature: world })
278
- : undefined);
279
- const resolvedLook = resolveGameLook({
280
- look: playable.look,
281
- lighting: playable.lighting,
282
- backdrop: playable.backdrop,
283
- postProcessing: playable.postProcessing,
284
- hasWorldSky: worldSky !== undefined,
285
- });
286
- const cinematicLook = (playable.look ?? "cinematic") !== "flat";
287
- const backdrop = resolvedLook.backdrop;
288
- const backdropSky = backdrop?.sky !== undefined ? resolveSkyDescriptor(backdrop.sky) : undefined;
289
- const effectiveSky = backdropSky ?? worldSky;
290
- const backgroundColor = backdrop?.background ?? (effectiveSky === undefined ? DEFAULT_BACKGROUND_COLOR : undefined);
291
- const lighting = resolvedLook.lighting;
292
- const orthographic = playable.camera?.projection === "orthographic";
293
- const localXY = (event) => {
294
- const rect = wrapperRef.current?.getBoundingClientRect();
295
- return rect === undefined
296
- ? { x: event.clientX, y: event.clientY }
297
- : { x: event.clientX - rect.left, y: event.clientY - rect.top };
298
- };
299
- const commitSelection = (ids) => {
300
- selection.replace(ids);
301
- setSelectedIds(new Set(ids));
302
- };
303
- const finishMarquee = (rect) => {
304
- const candidates = [];
305
- for (const entity of ctx.scene.entity.list()) {
306
- if (selectFilter !== undefined && !selectFilter(entity.id))
307
- continue;
308
- const screen = pointerService.screenOf(entity.position);
309
- if (screen !== null)
310
- candidates.push({ id: entity.id, x: screen.x, y: screen.y });
311
- }
312
- commitSelection(selectWithinRect(candidates, rect));
313
- };
314
- /** DOM UI (menus, HUD buttons) sits in the same wrapper as the canvas — only canvas-targeted clicks are world input. */
315
- const isWorldPointerTarget = (event) => event.target instanceof HTMLCanvasElement;
316
- const handlePointerDown = (event) => {
317
- if (isWorldPointerTarget(event))
318
- wrapperRef.current?.focus();
319
- trackPointerAxis(event);
320
- audioEngine.resume();
321
- if (contextMenu !== null)
322
- setContextMenu(null);
323
- if (event.button === 0 && isWorldPointerTarget(event)) {
324
- const point = localXY(event);
325
- pointerDownRef.current = point;
326
- if (pointer?.select === true)
327
- marqueeStartRef.current = point;
328
- }
329
- };
330
- const handlePointerMove = (event) => {
331
- trackPointerAxis(event);
332
- if (pointer?.select !== true)
333
- return;
334
- const start = marqueeStartRef.current;
335
- if (start === null || (event.buttons & 1) === 0)
336
- return;
337
- const point = localXY(event);
338
- setMarquee(screenRect(start.x, start.y, point.x, point.y));
339
- };
340
- const handlePointerUp = (event) => {
341
- if (event.button !== 0 || pointerDownRef.current === null)
342
- return;
343
- const start = pointerDownRef.current;
344
- pointerDownRef.current = null;
345
- marqueeStartRef.current = null;
346
- const end = localXY(event);
347
- const rect = screenRect(start.x, start.y, end.x, end.y);
348
- const wasMarquee = marquee !== null && isMarquee(rect, PRIMARY_CLICK_MOVE_THRESHOLD_PX);
349
- setMarquee(null);
350
- if (pointer?.select === true && wasMarquee) {
351
- finishMarquee(rect);
352
- return;
353
- }
354
- if (cameraDraggingRef.current)
355
- return;
356
- if (pointer?.grabWorldItems === true) {
357
- const hit = pointerService.worldHit();
358
- const itemInstanceId = hit?.entity ?? null;
359
- const record = itemInstanceId === null ? null : ctx.scene.worldItem.get(itemInstanceId);
360
- if (record !== null && itemInstanceId !== null) {
361
- const itemEntity = ctx.scene.entity.get(itemInstanceId);
362
- const localPlayer = ctx.scene.entity.get(ctx.player.userId);
363
- const pickupRadius = playable.worldItem?.pickupRadius ?? DEFAULT_PICKUP_RADIUS;
364
- const withinRadius = itemEntity !== null &&
365
- localPlayer !== null &&
366
- Math.hypot(localPlayer.position[0] - itemEntity.position[0], localPlayer.position[1] - itemEntity.position[1], localPlayer.position[2] - itemEntity.position[2]) <= pickupRadius;
367
- if (withinRadius) {
368
- ctx.scene.worldItem.pickup(itemInstanceId, ctx.player.userId);
369
- return;
370
- }
371
- }
372
- }
373
- if (pointer?.select === true) {
374
- const hit = pointerService.worldHit();
375
- if (hit !== null && hit.entity !== null && (selectFilter === undefined || selectFilter(hit.entity))) {
376
- commitSelection([hit.entity]);
377
- }
378
- else {
379
- commitSelection([]);
380
- }
381
- return;
382
- }
383
- if (pointer?.moveCommand !== undefined) {
384
- const hit = pointerService.worldHit();
385
- if (hit !== null && ctx.game.commands.has(pointer.moveCommand)) {
386
- ctx.game.commands.run(pointer.moveCommand, { point: hit.point, entity: hit.entity, object: hit.object });
387
- }
388
- return;
389
- }
390
- const moved = (end.x - start.x) ** 2 + (end.y - start.y) ** 2;
391
- if (moved <= PRIMARY_CLICK_MOVE_THRESHOLD_PX * PRIMARY_CLICK_MOVE_THRESHOLD_PX)
392
- primaryClickRef.current = true;
393
- };
394
- const handleContextMenu = (event) => {
395
- if (pointer === undefined)
396
- return;
397
- if (!isWorldPointerTarget(event))
398
- return;
399
- event.preventDefault();
400
- const hit = pointerService.worldHit();
401
- if (hit === null)
402
- return;
403
- if (pointer.contextMenu === true && (hit.entity !== null || hit.object !== null)) {
404
- const menu = pointerContextMenu(ctx, playable, hit);
405
- if (menu !== null) {
406
- const point = localXY(event);
407
- setContextMenu({ menu, x: point.x, y: point.y });
216
+ return (_jsx(Shell3dPresentation, { ...shared, primaryClickRef: primaryClickRef, cameraDraggingRef: cameraDraggingRef, serverIdRef: serverIdRef, remotePlayers: remotePlayers, touchStyle: touchStyle, compact: compact, graphics: graphics, settingsStore: settingsStore, bindingOverrides: bindingOverrides, rebindAction: rebindAction, resetActionBinding: resetActionBinding, audioEngine: audioEngine, poster: poster, posterFrozen: posterFrozen, onPosterSettled: () => {
217
+ if (posterSettledRef.current)
408
218
  return;
409
- }
410
- }
411
- if (pointer.select === true && pointer.orderCommand !== undefined && selection.size() > 0) {
412
- if (ctx.game.commands.has(pointer.orderCommand)) {
413
- ctx.game.commands.run(pointer.orderCommand, { selection: selection.list(), point: hit.point });
414
- }
415
- return;
416
- }
417
- if (pointer.secondaryCommand !== undefined && ctx.game.commands.has(pointer.secondaryCommand)) {
418
- const aim = (pointer.aim === true ? pointerAimFor(ctx, pointerService) : undefined) ?? {
419
- yaw: yawRef.current,
420
- pitch: pitchRef.current,
421
- };
422
- ctx.game.commands.run(pointer.secondaryCommand, { point: hit.point, entity: hit.entity, object: hit.object, aim });
423
- }
424
- };
425
- const handleVerbPick = (verb) => {
426
- const state = contextMenu;
427
- setContextMenu(null);
428
- if (state !== null && ctx.game.commands.has(verb.command)) {
429
- ctx.game.commands.run(verb.command, contextVerbInput(state.menu, verb));
430
- }
431
- };
432
- const controlsActive = playControlsActive(ctx);
433
- const settingsDisabled = playable.settings === false;
434
- const settingsConfig = playable.settings === false || playable.settings === undefined ? {} : playable.settings;
435
- const settingsSurface = settingsDisabled ? false : settingsConfig.surface ?? false;
436
- const settingsVariant = settingsConfig.variant ?? "panel";
437
- const hideCategories = settingsDisabled ? BUILT_IN_SETTING_CATEGORIES : settingsConfig.hide ?? [];
438
- const fovControlEnabled = !orthographic && playable.camera?.playerFov?.control !== false;
439
- const settingsHostsFov = !settingsDisabled && !hideCategories.includes("gameplay") && fovControlEnabled;
440
- const settingsActions = settingsDisabled
441
- ? []
442
- : (settingsConfig.actions ?? []).map((action) => ({
443
- id: action.id,
444
- label: action.label,
445
- kind: action.kind ?? "default",
446
- description: action.description,
447
- run: () => action.run(ctx),
448
- }));
449
- const touchScale = compact ? 0.88 : 1;
450
- const dockMounted = !poster &&
451
- !orientationGate &&
452
- coarsePointer &&
453
- controlsActive &&
454
- touchScheme !== null &&
455
- (touchScheme.joystick !== null || touchScheme.buttons.length > 0);
456
- return (_jsx(SettingsProvider, { store: settingsStore, children: _jsxs(PlayerFovProvider, { config: playable.camera, orthographic: orthographic, children: [_jsx(AudioSettingsBridge, { store: settingsStore, engine: audioEngine, buses: playable.audio?.buses }), _jsx(SettingsRuntime, { variant: settingsVariant, surface: settingsSurface, actions: settingsActions, input: playable.game.input ?? {}, buses: playable.audio?.buses, extra: settingsConfig.extra ?? [], categories: settingsConfig.categories ?? [], hide: hideCategories, fovEnabled: fovControlEnabled, hideBindings: settingsConfig.hideBindings ?? [], touchStyle: coarsePointer &&
457
- touchScheme !== null &&
458
- (touchScheme.joystick !== null || touchScheme.buttons.length > 0), overrides: bindingOverrides, rebind: rebindAction, resetBinding: resetActionBinding, children: _jsx("div", { ref: wrapperRef, tabIndex: 0, ...(poster && posterFrozen ? { "data-poster-ready": "" } : {}), className: "relative h-full w-full bg-neutral-950 outline-none", style: {
459
- "--jg-hud-dock-clearance": `${dockMounted ? touchDockClearance(touchScheme, touchScale) : 0}px`,
460
- }, onKeyDown: (event) => {
461
- if (event.code === "F2") {
462
- event.preventDefault();
463
- f2HeldRef.current = true;
464
- return;
465
- }
466
- if (f2HeldRef.current) {
467
- if (event.code === "KeyD" && devtoolsEnabled) {
468
- event.preventDefault();
469
- document.exitPointerLock?.();
470
- setDevtoolsOpen((current) => !current);
471
- }
472
- return;
473
- }
474
- if (event.code === "Tab" || event.code === "Space")
475
- event.preventDefault();
476
- if (playControlsActive(ctx))
477
- tracker.handleDown(event.code);
478
- }, onKeyUp: (event) => {
479
- if (event.code === "F2") {
480
- f2HeldRef.current = false;
481
- return;
482
- }
483
- if (playControlsActive(ctx))
484
- tracker.handleUp(event.code);
485
- }, onBlur: () => {
486
- f2HeldRef.current = false;
487
- tracker.reset();
488
- }, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onPointerLeave: deactivatePointerAxis, onPointerCancel: deactivatePointerAxis, onContextMenu: handleContextMenu, onWheel: (event) => {
489
- if (ctx === null || !event.shiftKey)
490
- return;
491
- event.preventDefault();
492
- if (event.deltaY < 0 && ctx.game.commands.has("ui.hotbarScrollNext")) {
493
- ctx.game.commands.run("ui.hotbarScrollNext", {});
494
- }
495
- else if (event.deltaY > 0 && ctx.game.commands.has("ui.hotbarScrollPrev")) {
496
- ctx.game.commands.run("ui.hotbarScrollPrev", {});
497
- }
498
- }, children: _jsxs(GameViewportProvider, { platforms: playable.platforms, children: [_jsxs(Canvas, { frameloop: poster && posterFrozen ? "demand" : "always", orthographic: orthographic, camera: orthographic
499
- ? {
500
- zoom: playable.camera?.frustum?.zoom ?? CAMERA_FRUSTUM_DEFAULTS.zoom,
501
- near: playable.camera?.frustum?.near ?? CAMERA_FRUSTUM_DEFAULTS.near,
502
- far: playable.camera?.frustum?.far ?? CAMERA_FRUSTUM_DEFAULTS.far,
503
- }
504
- : {
505
- fov: playable.camera?.frustum?.fov ?? CAMERA_FRUSTUM_DEFAULTS.fov,
506
- near: playable.camera?.frustum?.near ?? CAMERA_FRUSTUM_DEFAULTS.near,
507
- far: playable.camera?.frustum?.far ?? CAMERA_FRUSTUM_DEFAULTS.far,
508
- }, shadows: graphics.shadows, dpr: graphics.dpr, gl: { preserveDrawingBuffer: true }, style: { touchAction: "none" }, children: [backgroundColor !== undefined ? _jsx("color", { attach: "background", args: [backgroundColor] }) : null, cinematicLook ? _jsx(EnvironmentLighting, {}) : null, lighting !== undefined ? (_jsx(ConfiguredLighting, { lighting: lighting })) : effectiveSky === undefined ? (_jsxs(_Fragment, { children: [_jsx("ambientLight", { intensity: 0.55 }), _jsx("directionalLight", { position: [10, 16, 6], intensity: 1.3 })] })) : null, effectiveSky !== undefined ? (effectiveSky.timeOfDay ? (_jsx(TimeOfDayDaylight, { sky: effectiveSky, clock: ctx.time, bands: effectiveSky === worldSky ? biomeBands : undefined, lights: skyEmitsLights(resolveSkyLightOwnership(lighting !== undefined)) })) : (_jsx(SkyDaylight, { sky: effectiveSky, bands: effectiveSky === worldSky ? biomeBands : undefined, lights: skyEmitsLights(resolveSkyLightOwnership(lighting !== undefined)) }))) : null, _jsx(BackdropFog, { fog: backdrop?.fog }), _jsxs(GameProvider, { context: ctx, children: [_jsx(CullingProvider, { config: playable.visibility, children: _jsx(WorldView, { entitySprites: playable.entitySprites, entityModels: playable.entityModels, objectModels: playable.objectModels, objectStyles: playable.objectStyles, environment: AutoEnvironment, assets: playable.game.assets, renderEntity: playable.renderEntity, renderObject: playable.renderObject, selectedIds: selectedIds, hideLocalActor: firstPerson }) }), WorldOverlay !== undefined ? _jsx(WorldOverlay, { ctx: ctx }) : null, barsStatId !== null ? (_jsx(WorldEntityBars, { statId: barsStatId, roles: barsRoles, resolveRole: resolveEntityRole, ...(barsMaxDistance === undefined ? {} : { maxDistance: barsMaxDistance }) })) : null, nameplatesStatId !== null ? (_jsx(WorldNameplates, { statId: nameplatesStatId, roles: nameplatesRoles, resolveRole: resolveEntityRole, ...(nameplatesMaxDistance === undefined ? {} : { maxDistance: nameplatesMaxDistance }) })) : null, _jsx(WorldItems, { config: playable.worldItem }), _jsx(WorldTelegraphs, {}), _jsx(WorldSpellVfx, {}), _jsx(RetainedVfx, {}), _jsx(WorldFloatText, {}), _jsx(ProjectileTracers, {}), devtoolsEnabled ? _jsx(CollisionDebugWorld, {}) : null, _jsx(CombatCameraShake, {}), _jsx(AudioListener, { engine: audioEngine }), _jsx(EntityAudioEmitters, { engine: audioEngine, entitySounds: playable.entitySounds }), _jsx(ObjectAudioEmitters, { engine: audioEngine, objectSounds: playable.objectSounds }), _jsx(GameCameraRig, { yawRef: yawRef, pitchRef: pitchRef, config: cameraConfig, viewmodel: playable.viewmodel, pointerControls: pointerUsesLeft, panKeysEnabled: rtsPanKeysEnabled, director: ctx.camera, onDragChange: (dragging) => {
509
- cameraDraggingRef.current = dragging;
510
- } }), _jsx(PointerProbe, { service: pointerService })] }), _jsx(RemotePlayers, { rows: remotePlayers }), _jsx(FrameDriver, { ctx: ctx, playable: playable, tracker: tracker, yawRef: yawRef, pitchRef: pitchRef, primaryClickRef: primaryClickRef, pointerAxisRef: pointerAxisRef, gateRef: gateRef, onRuntimeError: reportRuntimeError, multiplayer: multiplayer, serverIdRef: serverIdRef, pointerService: pointerService, pointerAim: pointer?.aim === true, pingCommand: pointer?.pingCommand, poster: poster, onPosterSettled: () => {
511
- if (posterSettledRef.current)
512
- return;
513
- posterSettledRef.current = true;
514
- setPosterFrozen(true);
515
- } }), _jsx(DevtoolsRendererProbe, {}), resolvedLook.postProcessing !== undefined && resolvedLook.postProcessing.enabled !== false ? (_jsx(PostProcessing, { config: resolvedLook.postProcessing })) : null] }), !poster && !orientationGate && coarsePointer && controlsActive && touchScheme !== null && (touchScheme.gestures !== null || touchScheme.look) ? (_jsx(TouchPlaySurface, { scheme: touchScheme, sink: touchSink, yawRef: yawRef, pitchRef: pitchRef, maxPitch: playable.camera?.firstPerson?.maxPitch ?? 1.45, onPrimaryTap: () => {
516
- primaryClickRef.current = true;
517
- } })) : null, _jsx(GameUiErrorBoundary, { onRuntimeError: reportRuntimeError, children: _jsxs(GameProvider, { context: ctx, children: [_jsx(GamePhaseStamp, {}), _jsx(HudViewportProvider, { platforms: playable.platforms, config: playable.hudFit, userScale: graphics.uiScale, children: orientationGate ? null : _jsx(GameUI, {}) })] }) }), !poster && !orientationGate && showReticle ? _jsx(Reticle, {}) : null, dockMounted && touchScheme !== null ? (_jsx(TouchControlsDock, { scheme: touchScheme, sink: touchSink, style: touchStyle, scale: touchScale })) : null, orientationGateEl, marquee !== null ? _jsx(MarqueeBox, { rect: marquee }) : null, contextMenu !== null ? (_jsx(ContextMenuView, { menu: contextMenu.menu, x: contextMenu.x, y: contextMenu.y, onPick: handleVerbPick, onClose: () => setContextMenu(null) })) : null, devtoolsEnabled ? (_jsx(DevtoolsOverlay, { open: devtoolsOpen, ctx: ctx, playable: playable, multiplayer: multiplayer })) : null, poster ? null : _jsx(DiagnosticOverlay, { diagnostics: diagnostics, gameName: playable.game.name }), poster || orthographic || settingsHostsFov ? null : _jsx(PlayerFovSlider, {}), poster ? null : _jsx(SettingsChrome, {})] }) }) })] }) }));
219
+ posterSettledRef.current = true;
220
+ setPosterFrozen(true);
221
+ } }));
518
222
  }
@@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
3
3
  import { createGameContext } from "@jgengine/core/runtime/gameContext";
4
4
  import { GameProvider } from "@jgengine/react/provider";
5
5
  import { HudViewportProvider } from "@jgengine/react/hudViewport";
6
+ import { contextModels } from "./render/resolveModel.js";
6
7
  const PREVIEW_USER_ID = "ui-preview";
7
8
  const TICK_STEP = 1 / 60;
8
9
  function runTicks(ctx, playable, seconds) {
@@ -37,10 +38,12 @@ export const defaultUiScenario = (ctx, playable) => {
37
38
  export function GameUiPreview({ playable, scenario = defaultUiScenario, }) {
38
39
  const [ctx, setCtx] = useState(null);
39
40
  useEffect(() => {
41
+ const models = contextModels(playable);
40
42
  const context = createGameContext({
41
43
  definition: playable.game,
42
44
  content: playable.content,
43
45
  player: { userId: PREVIEW_USER_ID, isNew: true },
46
+ ...(models === undefined ? {} : { models }),
44
47
  });
45
48
  playable.loop.onInit(context);
46
49
  playable.loop.onNewPlayer(context);
@@ -0,0 +1,63 @@
1
+ import { type Dispatch, type MutableRefObject, type RefObject, type SetStateAction } from "react";
2
+ import type { PointerAxisState } from "@jgengine/core/input/pointerAxis";
3
+ import type { ActionStateTracker } from "@jgengine/core/input/actionBindings";
4
+ import type { BindingOverrides } from "@jgengine/core/input/bindingOverrides";
5
+ import type { TouchScheme, TouchStyle } from "@jgengine/core/input/touchScheme";
6
+ import { type GraphicsQuality, type SettingsStore } from "@jgengine/core/settings/settingsModel";
7
+ import type { PresencePoseRow } from "@jgengine/core/runtime/transport";
8
+ import type { GameContext } from "@jgengine/core/runtime/gameContext";
9
+ import type { AudioEngine } from "./audio/audioEngine.js";
10
+ import type { ShellMultiplayer } from "./multiplayer.js";
11
+ import type { PlayableGame } from "./registry.js";
12
+ import type { RuntimeDiagnostic } from "./diagnostics/RuntimeDiagnostics.js";
13
+ /** 3D play surface: canvas, world overlays, and shared chrome. @internal */
14
+ export declare function Shell3dPresentation({ playable, ctx, multiplayer, tracker, pointerAxisRef, gateRef, wrapperRef, f2HeldRef, yawRef, pitchRef, primaryClickRef, cameraDraggingRef, serverIdRef, remotePlayers, touchScheme, touchSink, touchStyle, orientationGate, orientationGateEl, coarsePointer, compact, graphics, settingsStore, bindingOverrides, rebindAction, resetActionBinding, diagnostics, devtoolsEnabled, devtoolsOpen, setDevtoolsOpen, reportRuntimeError, trackPointerAxis, deactivatePointerAxis, audioEngine, poster, posterFrozen, onPosterSettled, }: {
15
+ playable: PlayableGame;
16
+ ctx: GameContext;
17
+ multiplayer: ShellMultiplayer | null;
18
+ tracker: ActionStateTracker<string>;
19
+ pointerAxisRef: MutableRefObject<PointerAxisState | null>;
20
+ gateRef: MutableRefObject<boolean>;
21
+ wrapperRef: RefObject<HTMLDivElement | null>;
22
+ f2HeldRef: MutableRefObject<boolean>;
23
+ yawRef: MutableRefObject<number>;
24
+ pitchRef: MutableRefObject<number>;
25
+ primaryClickRef: MutableRefObject<boolean>;
26
+ cameraDraggingRef: MutableRefObject<boolean>;
27
+ serverIdRef: MutableRefObject<string | null>;
28
+ remotePlayers: PresencePoseRow[];
29
+ touchScheme: TouchScheme | null;
30
+ touchSink: {
31
+ onCodeDown: (code: string) => void;
32
+ onCodeUp: (code: string) => void;
33
+ };
34
+ touchStyle: TouchStyle;
35
+ orientationGate: boolean;
36
+ orientationGateEl: React.ReactNode;
37
+ coarsePointer: boolean;
38
+ compact: boolean;
39
+ graphics: {
40
+ shadows: boolean;
41
+ dpr: number;
42
+ uiScale: number;
43
+ quality: GraphicsQuality;
44
+ };
45
+ settingsStore: SettingsStore;
46
+ bindingOverrides: BindingOverrides;
47
+ rebindAction: (action: string, code: string) => void;
48
+ resetActionBinding: (action: string) => void;
49
+ diagnostics: RuntimeDiagnostic[];
50
+ devtoolsEnabled: boolean;
51
+ devtoolsOpen: boolean;
52
+ setDevtoolsOpen: Dispatch<SetStateAction<boolean>>;
53
+ reportRuntimeError: (error: unknown, phase: string, componentStack?: string) => void;
54
+ trackPointerAxis: (event: {
55
+ clientX: number;
56
+ clientY: number;
57
+ }) => void;
58
+ deactivatePointerAxis: () => void;
59
+ audioEngine: AudioEngine;
60
+ poster: boolean;
61
+ posterFrozen: boolean;
62
+ onPosterSettled: () => void;
63
+ }): import("react").JSX.Element;