@jgengine/shell 0.18.0 → 0.19.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.
- package/CHANGELOG.md +265 -2
- package/dist/GamePlayerShell.js +27 -4
- package/dist/JoinGate.d.ts +17 -0
- package/dist/JoinGate.js +14 -0
- package/dist/Shell3dPresentation.d.ts +9 -8
- package/dist/Shell3dPresentation.js +57 -8
- package/dist/ShellChrome.d.ts +2 -0
- package/dist/ShellChrome.js +6 -0
- package/dist/ShellHudPresentation.d.ts +9 -1
- package/dist/ShellHudPresentation.js +21 -0
- package/dist/TerritoryOverlay.d.ts +21 -0
- package/dist/TerritoryOverlay.js +9 -0
- package/dist/audio/AudioComponents.js +13 -2
- package/dist/audio/audioEngine.d.ts +15 -1
- package/dist/audio/audioEngine.js +132 -11
- package/dist/audio/audioWire.d.ts +3 -0
- package/dist/audio/audioWire.js +7 -1
- package/dist/audio/loopParams.d.ts +39 -0
- package/dist/audio/loopParams.js +43 -0
- package/dist/camera/GameCameraRig.js +1 -1
- package/dist/camera/GameFirstPersonCamera.d.ts +4 -2
- package/dist/camera/GameFirstPersonCamera.js +52 -15
- package/dist/camera/Viewports.d.ts +34 -0
- package/dist/camera/Viewports.js +123 -0
- package/dist/camera/cameraRigs.js +77 -62
- package/dist/camera/orbitCameraMath.js +2 -2
- package/dist/camera/rigMath.d.ts +82 -4
- package/dist/camera/rigMath.js +133 -15
- package/dist/defineGame.d.ts +9 -0
- package/dist/defineGame.js +38 -6
- package/dist/devtools/DevtoolsOverlay.js +2 -0
- package/dist/devtools/KeysPanel.js +3 -3
- package/dist/devtools/agentBridge.d.ts +6 -0
- package/dist/devtools/agentBridge.js +15 -1
- package/dist/diagnostics/FpsCounter.d.ts +17 -0
- package/dist/diagnostics/FpsCounter.js +32 -0
- package/dist/drivers/FrameDriver.d.ts +5 -1
- package/dist/drivers/FrameDriver.js +74 -26
- package/dist/drivers/FrameRateLimiter.d.ts +9 -0
- package/dist/drivers/FrameRateLimiter.js +39 -0
- package/dist/drivers/HudOnlyDriver.js +10 -5
- package/dist/environment/DayNightSky.js +5 -5
- package/dist/environment/Daylight.d.ts +35 -1
- package/dist/environment/Daylight.js +98 -28
- package/dist/environment/EnvironmentScene.js +1 -1
- package/dist/environment/VolumetricClouds.js +20 -2
- package/dist/environment/daylightCycle.d.ts +20 -0
- package/dist/environment/daylightCycle.js +59 -7
- package/dist/environment/sunShadowMath.d.ts +45 -0
- package/dist/environment/sunShadowMath.js +50 -0
- package/dist/input/gamepadMerge.d.ts +10 -0
- package/dist/input/gamepadMerge.js +12 -0
- package/dist/input/gamepadPoll.d.ts +47 -0
- package/dist/input/gamepadPoll.js +142 -0
- package/dist/input/gamepadSource.d.ts +22 -0
- package/dist/input/gamepadSource.js +153 -0
- package/dist/input/mouseLook.js +2 -1
- package/dist/input/padHaptics.d.ts +17 -0
- package/dist/input/padHaptics.js +32 -0
- package/dist/input/pointerLock.d.ts +11 -0
- package/dist/input/pointerLock.js +19 -0
- package/dist/materialOverride.d.ts +4 -1
- package/dist/materialOverride.js +10 -1
- package/dist/postfx/PostProcessing.d.ts +11 -8
- package/dist/postfx/PostProcessing.js +38 -16
- package/dist/postfx/gradeShader.js +13 -0
- package/dist/postfx/stylizePass.d.ts +23 -0
- package/dist/postfx/stylizePass.js +216 -0
- package/dist/render/CascadedShadows.d.ts +11 -0
- package/dist/render/CascadedShadows.js +19 -8
- package/dist/render/EnvironmentLighting.d.ts +4 -1
- package/dist/render/EnvironmentLighting.js +45 -9
- package/dist/render/SceneLighting.d.ts +16 -2
- package/dist/render/SceneLighting.js +56 -6
- package/dist/render/SceneModels.js +45 -9
- package/dist/render/SkinnedInstances.d.ts +91 -0
- package/dist/render/SkinnedInstances.js +249 -0
- package/dist/render/TextureFiltering.d.ts +16 -0
- package/dist/render/TextureFiltering.js +64 -0
- package/dist/render/displayColor.d.ts +33 -0
- package/dist/render/displayColor.js +138 -0
- package/dist/render/modelLoad.d.ts +39 -0
- package/dist/render/modelLoad.js +89 -1
- package/dist/render/modelRender.d.ts +3 -0
- package/dist/render/modelRender.js +6 -2
- package/dist/render/resolveModel.js +4 -0
- package/dist/render/useFootIk.d.ts +58 -0
- package/dist/render/useFootIk.js +216 -0
- package/dist/render/useModelAnimation.d.ts +26 -0
- package/dist/render/useModelAnimation.js +184 -107
- package/dist/scene/AuthoredScene.d.ts +9 -0
- package/dist/scene/AuthoredScene.js +17 -1
- package/dist/scene/CityRenderer.js +666 -241
- package/dist/scene/cityWindowMaterial.d.ts +8 -6
- package/dist/scene/cityWindowMaterial.js +80 -21
- package/dist/settings/QuickControls.js +1 -1
- package/dist/settings/SettingsMenu.js +1 -1
- package/dist/settings/appliedSettings.d.ts +10 -2
- package/dist/settings/appliedSettings.js +9 -5
- package/dist/settings/settingsController.d.ts +3 -0
- package/dist/settings/settingsController.js +47 -3
- package/dist/structures/BuildingKitBatch.js +26 -22
- package/dist/structures/GeneratedBuilding.d.ts +3 -15
- package/dist/structures/GeneratedBuilding.js +21 -15
- package/dist/structures/buildingSurface.d.ts +21 -0
- package/dist/structures/buildingSurface.js +134 -0
- package/dist/terrain/terrainDetailMaterial.js +7 -3
- package/dist/useShellMultiplayerSync.d.ts +9 -2
- package/dist/useShellMultiplayerSync.js +30 -8
- package/dist/visibility/CullingProvider.d.ts +2 -1
- package/dist/visibility/CullingProvider.js +7 -2
- package/dist/world/GridWorldScene.js +4 -0
- package/dist/world/TileLayerRenderer.d.ts +9 -0
- package/dist/world/TileLayerRenderer.js +64 -0
- package/dist/world/WorldParticles.d.ts +31 -0
- package/dist/world/WorldParticles.js +128 -0
- package/dist/world/WorldScene.d.ts +2 -1
- package/dist/world/WorldScene.js +46 -4
- package/dist/world/entityPose.d.ts +4 -0
- package/dist/world/entityPose.js +9 -0
- package/dist/world/tileLayerInstances.d.ts +11 -0
- package/dist/world/tileLayerInstances.js +28 -0
- package/dist/worldSync.d.ts +5 -1
- package/dist/worldSync.js +25 -4
- package/package.json +4 -4
- package/skills/ce-handoff/SKILL.md +2 -2
- package/skills/game-design/SKILL.md +11 -1
- package/skills/jgengine/SKILL.md +9 -5
- package/skills/jgengine/api.md +205 -101
- package/skills/jgengine/capabilities.md +56 -0
- package/skills/jgengine/recipes/minimal-game.md +3 -0
- package/skills/jgengine-ui/SKILL.md +20 -15
- package/skills/jgengine-ui/api.md +538 -326
- package/skills/jgengine-ui/capabilities.md +37 -0
- package/skills/jgengine-ui/reference-primitives.md +89 -0
- package/skills/jgengine-ui/reference.md +2 -78
- package/skills/jgengine-world/SKILL.md +21 -3
- package/skills/jgengine-world/api.md +1172 -625
- package/skills/jgengine-world/capabilities.md +229 -3
- package/skills/jgengine-world/recipes/character-animation.md +39 -0
- package/skills/jgengine-world/recipes/controls.md +44 -0
- package/skills/jgengine-world/recipes/flight-feel.md +76 -0
- package/skills/jgengine-world/recipes/npc-senses.md +43 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +85 -0
- package/skills/jgengine-world/reference.md +31 -2
- package/skills/jgengine-world/visibility.md +112 -0
- package/skills/level-design/SKILL.md +1 -1
- package/skills/jgengine-ui/references/visual-scorecard.md +0 -73
package/dist/camera/rigMath.d.ts
CHANGED
|
@@ -136,6 +136,23 @@ export interface ResolvedChase {
|
|
|
136
136
|
velocityYawResponse: number;
|
|
137
137
|
/** Exponential smoothing rate the anchor yaw follows the body's facing with (#1370). `Infinity` restores the legacy rigid follow. */
|
|
138
138
|
yawResponse: number;
|
|
139
|
+
fovBase: number;
|
|
140
|
+
fovMax: number;
|
|
141
|
+
fovSpeedForMax: number;
|
|
142
|
+
/** Exponential FOV smoothing rate; `Infinity` applies the curve unsmoothed. */
|
|
143
|
+
fovResponse: number;
|
|
144
|
+
distanceExtra: number;
|
|
145
|
+
distanceSpeedForMax: number;
|
|
146
|
+
/** Fraction of body pitch the boom follows; 0 keeps a level boom. */
|
|
147
|
+
pitchBlend: number;
|
|
148
|
+
pitchMax: number;
|
|
149
|
+
pitchResponse: number;
|
|
150
|
+
fovKickDecay: number;
|
|
151
|
+
fovKickMax: number;
|
|
152
|
+
lookBackAction: string | null;
|
|
153
|
+
collision: boolean;
|
|
154
|
+
collisionRadius: number;
|
|
155
|
+
collisionMinDistance: number;
|
|
139
156
|
}
|
|
140
157
|
/** @internal */
|
|
141
158
|
export declare function resolveChase(config: ChaseCameraConfig | undefined): ResolvedChase;
|
|
@@ -143,6 +160,10 @@ export declare function resolveChase(config: ChaseCameraConfig | undefined): Res
|
|
|
143
160
|
* @internal
|
|
144
161
|
*/
|
|
145
162
|
export declare function leadFollowPoint(follow: Vec3, previous: Vec3, dt: number, resolved: ResolvedChase): Vec3;
|
|
163
|
+
/** Velocity-lead the follow point along a known planar velocity, clamped to `leadMax`.
|
|
164
|
+
* @internal
|
|
165
|
+
*/
|
|
166
|
+
export declare function leadFromVelocity(follow: Vec3, velocity: Vec3, resolved: ResolvedChase): Vec3;
|
|
146
167
|
/** One smoothing step of the chase rig's turn-bank roll (#286.10): roll opposes yaw rate, clamped and exponentially damped.
|
|
147
168
|
* @internal
|
|
148
169
|
*/
|
|
@@ -159,14 +180,71 @@ export declare function resolveObserver(config: ObserverCameraConfig | undefined
|
|
|
159
180
|
* @internal
|
|
160
181
|
*/
|
|
161
182
|
export declare function observerPose(subject: Vec3, angle: number, resolved: ResolvedObserver, fov: number): CameraPose;
|
|
162
|
-
/** Desired chase-camera position behind a vehicle facing `yaw` (before spring smoothing).
|
|
183
|
+
/** Desired chase-camera position behind a vehicle facing `yaw` (before spring smoothing). `pitch` (nose-up positive) tilts the boom around the vehicle's right axis.
|
|
184
|
+
* @internal
|
|
185
|
+
*/
|
|
186
|
+
export declare function chaseDesiredPosition(follow: Vec3, yaw: number, resolved: ResolvedChase, pitch?: number, distance?: number): Vec3;
|
|
187
|
+
/** Camera offset from the follow point for a boom `distance` back and `height` up, tilted by `pitch` (nose-up positive).
|
|
188
|
+
* @internal
|
|
189
|
+
*/
|
|
190
|
+
export declare function chaseBoomOffset(yaw: number, pitch: number, distance: number, height: number): Vec3;
|
|
191
|
+
/** Look point ahead of / above a chased vehicle, tilted by `pitch` like the boom.
|
|
163
192
|
* @internal
|
|
164
193
|
*/
|
|
165
|
-
export declare function
|
|
166
|
-
/**
|
|
194
|
+
export declare function chaseLookAt(follow: Vec3, yaw: number, resolved: ResolvedChase, pitch?: number): Vec3;
|
|
195
|
+
/** Mutable per-rig chase state; {@link stepChase} advances it in place. */
|
|
196
|
+
export interface ChaseRigState {
|
|
197
|
+
/** Smoothed camera offset from the led follow point; springing the offset, not the world position, keeps the boom length at any constant speed. */
|
|
198
|
+
offset: Vec3 | null;
|
|
199
|
+
anchorYaw: number | null;
|
|
200
|
+
lastYaw: number | null;
|
|
201
|
+
lastFollow: Vec3 | null;
|
|
202
|
+
roll: number;
|
|
203
|
+
pitch: number;
|
|
204
|
+
fov: number | null;
|
|
205
|
+
kick: number;
|
|
206
|
+
lookingBack: boolean;
|
|
207
|
+
}
|
|
208
|
+
/** @internal */
|
|
209
|
+
export declare function createChaseRigState(): ChaseRigState;
|
|
210
|
+
/** What the chase rig reads from its target each frame. */
|
|
211
|
+
export interface ChaseSample {
|
|
212
|
+
follow: Vec3;
|
|
213
|
+
/** Body heading (radians). */
|
|
214
|
+
yaw: number;
|
|
215
|
+
/** Body `rotationX` (three.js convention: positive = nose down). */
|
|
216
|
+
bodyPitch: number;
|
|
217
|
+
/** Sim velocity when the entity publishes one; `null` falls back to the frame-to-frame position delta. */
|
|
218
|
+
velocity: Vec3 | null;
|
|
219
|
+
lookBack: boolean;
|
|
220
|
+
/** FOV impulse (degrees) queued since the last frame. */
|
|
221
|
+
fovKick: number;
|
|
222
|
+
}
|
|
223
|
+
/** One frame of chase output from {@link stepChase}. */
|
|
224
|
+
export interface ChaseStepResult {
|
|
225
|
+
pose: CameraPose;
|
|
226
|
+
/** Camera roll to apply after the pose, radians. */
|
|
227
|
+
roll: number;
|
|
228
|
+
/** Forward chase yaw to report to the shell; unaffected by look-back. */
|
|
229
|
+
anchorYaw: number;
|
|
230
|
+
/** Planar speed used for FOV, distance and shake. */
|
|
231
|
+
speed: number;
|
|
232
|
+
}
|
|
233
|
+
/** Pulls a desired camera position in toward `pivot` when something blocks the boom. */
|
|
234
|
+
export type ChaseBoomClamp = (pivot: Vec3, desired: Vec3) => Vec3;
|
|
235
|
+
/**
|
|
236
|
+
* One frame of the chase rig: speed from sim velocity, smoothed FOV plus decaying kicks, drift-lag
|
|
237
|
+
* anchor yaw, pitch follow, distance by speed, look-back, and a boom spring that eases the offset
|
|
238
|
+
* from the target rather than its world position, so the camera holds its distance at speed.
|
|
239
|
+
* @internal
|
|
240
|
+
*/
|
|
241
|
+
export declare function stepChase(state: ChaseRigState, sample: ChaseSample, resolved: ResolvedChase, dt: number, clampBoom?: ChaseBoomClamp): ChaseStepResult;
|
|
242
|
+
/**
|
|
243
|
+
* Where a blocked boom should put the camera: `hitDistance` along the pivot→desired ray, less
|
|
244
|
+
* `radius`, but never nearer the pivot than `minDistance`. `null` hit returns `desired` unchanged.
|
|
167
245
|
* @internal
|
|
168
246
|
*/
|
|
169
|
-
export declare function
|
|
247
|
+
export declare function clampBoomToHit(pivot: Vec3, desired: Vec3, hitDistance: number | null, radius: number, minDistance: number): Vec3;
|
|
170
248
|
/**
|
|
171
249
|
* Drift-lag (#1051): the yaw the chase anchor should aim before temporal
|
|
172
250
|
* smoothing. When the target slides, its planar `velocity` diverges from its
|
package/dist/camera/rigMath.js
CHANGED
|
@@ -253,6 +253,9 @@ export function lockOnPose(player, target, config, fov) {
|
|
|
253
253
|
const DEFAULT_CHASE_YAW_RESPONSE = 5;
|
|
254
254
|
/** @internal */
|
|
255
255
|
export function resolveChase(config) {
|
|
256
|
+
const fovSpeedForMax = config?.fov?.speedForMax ?? 24;
|
|
257
|
+
const collision = config?.collision ?? true;
|
|
258
|
+
const collisionBlock = typeof collision === "object" ? collision : undefined;
|
|
256
259
|
return {
|
|
257
260
|
distance: config?.distance ?? 6,
|
|
258
261
|
height: config?.height ?? 2.6,
|
|
@@ -268,16 +271,39 @@ export function resolveChase(config) {
|
|
|
268
271
|
velocityYawMinSpeed: config?.velocityYaw?.minSpeed ?? 4,
|
|
269
272
|
velocityYawResponse: config?.velocityYaw?.response ?? 6,
|
|
270
273
|
yawResponse: config?.yawResponse ?? DEFAULT_CHASE_YAW_RESPONSE,
|
|
274
|
+
fovBase: config?.fov?.base ?? 55,
|
|
275
|
+
fovMax: config?.fov?.max ?? 78,
|
|
276
|
+
fovSpeedForMax,
|
|
277
|
+
fovResponse: config?.fov?.response ?? 6,
|
|
278
|
+
distanceExtra: config?.distanceBySpeed?.extra ?? 0,
|
|
279
|
+
distanceSpeedForMax: config?.distanceBySpeed?.speedForMax ?? fovSpeedForMax,
|
|
280
|
+
pitchBlend: config?.pitchFollow === undefined ? 0 : config.pitchFollow.blend ?? 1,
|
|
281
|
+
pitchMax: config?.pitchFollow?.max ?? 0.6,
|
|
282
|
+
pitchResponse: config?.pitchFollow?.response ?? 5,
|
|
283
|
+
fovKickDecay: config?.fovKick?.decay ?? 7,
|
|
284
|
+
fovKickMax: config?.fovKick?.max ?? 20,
|
|
285
|
+
lookBackAction: config?.lookBackAction ?? null,
|
|
286
|
+
collision: collision !== false,
|
|
287
|
+
collisionRadius: collisionBlock?.radius ?? 0.3,
|
|
288
|
+
collisionMinDistance: collisionBlock?.minDistance ?? 0.8,
|
|
271
289
|
};
|
|
272
290
|
}
|
|
273
291
|
/** Velocity-lead the follow point (#286.9): aim `leadTime` seconds along the target's frame velocity, clamped to `leadMax`.
|
|
274
292
|
* @internal
|
|
275
293
|
*/
|
|
276
294
|
export function leadFollowPoint(follow, previous, dt, resolved) {
|
|
277
|
-
if (
|
|
295
|
+
if (dt <= 0)
|
|
278
296
|
return follow;
|
|
279
|
-
|
|
280
|
-
|
|
297
|
+
return leadFromVelocity(follow, { x: (follow.x - previous.x) / dt, y: 0, z: (follow.z - previous.z) / dt }, resolved);
|
|
298
|
+
}
|
|
299
|
+
/** Velocity-lead the follow point along a known planar velocity, clamped to `leadMax`.
|
|
300
|
+
* @internal
|
|
301
|
+
*/
|
|
302
|
+
export function leadFromVelocity(follow, velocity, resolved) {
|
|
303
|
+
if (resolved.leadTime <= 0)
|
|
304
|
+
return follow;
|
|
305
|
+
let leadX = velocity.x * resolved.leadTime;
|
|
306
|
+
let leadZ = velocity.z * resolved.leadTime;
|
|
281
307
|
const magnitude = Math.hypot(leadX, leadZ);
|
|
282
308
|
if (magnitude > resolved.leadMax) {
|
|
283
309
|
leadX *= resolved.leadMax / magnitude;
|
|
@@ -327,28 +353,120 @@ export function observerPose(subject, angle, resolved, fov) {
|
|
|
327
353
|
fov,
|
|
328
354
|
};
|
|
329
355
|
}
|
|
330
|
-
/** Desired chase-camera position behind a vehicle facing `yaw` (before spring smoothing).
|
|
356
|
+
/** Desired chase-camera position behind a vehicle facing `yaw` (before spring smoothing). `pitch` (nose-up positive) tilts the boom around the vehicle's right axis.
|
|
331
357
|
* @internal
|
|
332
358
|
*/
|
|
333
|
-
export function chaseDesiredPosition(follow, yaw, resolved) {
|
|
334
|
-
const
|
|
359
|
+
export function chaseDesiredPosition(follow, yaw, resolved, pitch = 0, distance = resolved.distance) {
|
|
360
|
+
const offset = chaseBoomOffset(yaw, pitch, distance, resolved.height);
|
|
361
|
+
return { x: follow.x + offset.x, y: follow.y + offset.y, z: follow.z + offset.z };
|
|
362
|
+
}
|
|
363
|
+
/** Camera offset from the follow point for a boom `distance` back and `height` up, tilted by `pitch` (nose-up positive).
|
|
364
|
+
* @internal
|
|
365
|
+
*/
|
|
366
|
+
export function chaseBoomOffset(yaw, pitch, distance, height) {
|
|
367
|
+
const forward = forwardVector(yaw);
|
|
368
|
+
const cos = Math.cos(pitch);
|
|
369
|
+
const sin = Math.sin(pitch);
|
|
370
|
+
const along = -distance * cos - height * sin;
|
|
371
|
+
return { x: forward.x * along, y: -distance * sin + height * cos, z: forward.z * along };
|
|
372
|
+
}
|
|
373
|
+
/** Look point ahead of / above a chased vehicle, tilted by `pitch` like the boom.
|
|
374
|
+
* @internal
|
|
375
|
+
*/
|
|
376
|
+
export function chaseLookAt(follow, yaw, resolved, pitch = 0) {
|
|
377
|
+
const forward = forwardVector(yaw);
|
|
378
|
+
const cos = Math.cos(pitch);
|
|
379
|
+
const sin = Math.sin(pitch);
|
|
380
|
+
const along = 2 * cos - resolved.lookHeight * sin;
|
|
335
381
|
return {
|
|
336
|
-
x: follow.x
|
|
337
|
-
y: follow.y + resolved.
|
|
338
|
-
z: follow.z
|
|
382
|
+
x: follow.x + forward.x * along,
|
|
383
|
+
y: follow.y + 2 * sin + resolved.lookHeight * cos,
|
|
384
|
+
z: follow.z + forward.z * along,
|
|
339
385
|
};
|
|
340
386
|
}
|
|
341
|
-
/**
|
|
387
|
+
/** @internal */
|
|
388
|
+
export function createChaseRigState() {
|
|
389
|
+
return { offset: null, anchorYaw: null, lastYaw: null, lastFollow: null, roll: 0, pitch: 0, fov: null, kick: 0, lookingBack: false };
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* One frame of the chase rig: speed from sim velocity, smoothed FOV plus decaying kicks, drift-lag
|
|
393
|
+
* anchor yaw, pitch follow, distance by speed, look-back, and a boom spring that eases the offset
|
|
394
|
+
* from the target rather than its world position, so the camera holds its distance at speed.
|
|
342
395
|
* @internal
|
|
343
396
|
*/
|
|
344
|
-
export function
|
|
345
|
-
const
|
|
397
|
+
export function stepChase(state, sample, resolved, dt, clampBoom) {
|
|
398
|
+
const { follow, yaw } = sample;
|
|
399
|
+
const last = state.lastFollow ?? follow;
|
|
400
|
+
const velocity = sample.velocity ?? (dt > 0 ? { x: (follow.x - last.x) / dt, y: 0, z: (follow.z - last.z) / dt } : { x: 0, y: 0, z: 0 });
|
|
401
|
+
state.lastFollow = follow;
|
|
402
|
+
const speed = Math.hypot(velocity.x, velocity.z);
|
|
403
|
+
const lastYaw = state.lastYaw ?? yaw;
|
|
404
|
+
state.lastYaw = yaw;
|
|
405
|
+
state.roll = bankRollStep(state.roll, yaw, lastYaw, dt, resolved);
|
|
406
|
+
const targetFov = speedToFov(speed, { base: resolved.fovBase, max: resolved.fovMax, speedForMax: resolved.fovSpeedForMax });
|
|
407
|
+
state.fov =
|
|
408
|
+
state.fov === null || !Number.isFinite(resolved.fovResponse)
|
|
409
|
+
? targetFov
|
|
410
|
+
: state.fov + (targetFov - state.fov) * smoothBlend(dt, resolved.fovResponse);
|
|
411
|
+
state.kick = clamp(state.kick * Math.exp(-resolved.fovKickDecay * dt) + sample.fovKick, -resolved.fovKickMax, resolved.fovKickMax);
|
|
412
|
+
let targetYaw = yaw;
|
|
413
|
+
let response = resolved.yawResponse;
|
|
414
|
+
if (resolved.velocityYawBlend > 0) {
|
|
415
|
+
targetYaw = velocityYawTarget(yaw, velocity, resolved);
|
|
416
|
+
response = resolved.velocityYawResponse;
|
|
417
|
+
}
|
|
418
|
+
const anchorYaw = Number.isFinite(response)
|
|
419
|
+
? smoothYaw(state.anchorYaw ?? targetYaw, targetYaw, response, dt)
|
|
420
|
+
: targetYaw;
|
|
421
|
+
state.anchorYaw = anchorYaw;
|
|
422
|
+
const pitchTarget = clamp(-sample.bodyPitch * resolved.pitchBlend, -resolved.pitchMax, resolved.pitchMax);
|
|
423
|
+
state.pitch =
|
|
424
|
+
resolved.pitchBlend <= 0 ? 0 : state.pitch + (pitchTarget - state.pitch) * smoothBlend(dt, resolved.pitchResponse);
|
|
425
|
+
const snap = state.offset === null || sample.lookBack !== state.lookingBack;
|
|
426
|
+
state.lookingBack = sample.lookBack;
|
|
427
|
+
const boomYaw = sample.lookBack ? anchorYaw + Math.PI : anchorYaw;
|
|
428
|
+
const boomPitch = sample.lookBack ? -state.pitch : state.pitch;
|
|
429
|
+
const extra = resolved.distanceSpeedForMax > 0 ? resolved.distanceExtra * clamp(speed / resolved.distanceSpeedForMax, 0, 1) : 0;
|
|
430
|
+
const led = sample.lookBack ? follow : leadFromVelocity(follow, velocity, resolved);
|
|
431
|
+
const desiredOffset = chaseBoomOffset(boomYaw, boomPitch, resolved.distance + extra, resolved.height);
|
|
432
|
+
let offset = snap || state.offset === null ? desiredOffset : springArmStep(state.offset, desiredOffset, resolved.springDamping, dt);
|
|
433
|
+
if (clampBoom !== undefined) {
|
|
434
|
+
const up = chaseLookAt(led, boomYaw, resolved, boomPitch);
|
|
435
|
+
const pivot = { x: led.x, y: up.y, z: led.z };
|
|
436
|
+
const wanted = { x: led.x + offset.x, y: led.y + offset.y, z: led.z + offset.z };
|
|
437
|
+
const allowed = clampBoom(pivot, wanted);
|
|
438
|
+
offset = { x: allowed.x - led.x, y: allowed.y - led.y, z: allowed.z - led.z };
|
|
439
|
+
}
|
|
440
|
+
state.offset = offset;
|
|
346
441
|
return {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
442
|
+
pose: {
|
|
443
|
+
position: { x: led.x + offset.x, y: led.y + offset.y, z: led.z + offset.z },
|
|
444
|
+
lookAt: chaseLookAt(led, boomYaw, resolved, boomPitch),
|
|
445
|
+
fov: state.fov + state.kick,
|
|
446
|
+
},
|
|
447
|
+
roll: sample.lookBack ? -state.roll : state.roll,
|
|
448
|
+
anchorYaw,
|
|
449
|
+
speed,
|
|
350
450
|
};
|
|
351
451
|
}
|
|
452
|
+
/**
|
|
453
|
+
* Where a blocked boom should put the camera: `hitDistance` along the pivot→desired ray, less
|
|
454
|
+
* `radius`, but never nearer the pivot than `minDistance`. `null` hit returns `desired` unchanged.
|
|
455
|
+
* @internal
|
|
456
|
+
*/
|
|
457
|
+
export function clampBoomToHit(pivot, desired, hitDistance, radius, minDistance) {
|
|
458
|
+
if (hitDistance === null)
|
|
459
|
+
return desired;
|
|
460
|
+
const dx = desired.x - pivot.x;
|
|
461
|
+
const dy = desired.y - pivot.y;
|
|
462
|
+
const dz = desired.z - pivot.z;
|
|
463
|
+
const length = Math.hypot(dx, dy, dz);
|
|
464
|
+
if (length <= 1e-6)
|
|
465
|
+
return desired;
|
|
466
|
+
const allowed = clamp(hitDistance - radius, Math.min(minDistance, length), length);
|
|
467
|
+
const scale = allowed / length;
|
|
468
|
+
return { x: pivot.x + dx * scale, y: pivot.y + dy * scale, z: pivot.z + dz * scale };
|
|
469
|
+
}
|
|
352
470
|
/**
|
|
353
471
|
* Drift-lag (#1051): the yaw the chase anchor should aim before temporal
|
|
354
472
|
* smoothing. When the target slides, its planar `velocity` diverges from its
|
package/dist/defineGame.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type ComponentType } from "react";
|
|
2
|
+
import type { EditorDocument } from "@jgengine/core/editor/types";
|
|
2
3
|
import { type GameDefinitionConfig, type GameLoop } from "@jgengine/core/game/defineGame";
|
|
4
|
+
import type { BackdropConfig } from "@jgengine/core/game/playableGame";
|
|
3
5
|
import type { GameContext, GameContextContent } from "@jgengine/core/runtime/gameContext";
|
|
4
6
|
import type { ModelAssetRef } from "@jgengine/core/scene/assetCatalog";
|
|
5
7
|
import type { ModelConfig } from "@jgengine/core/game/playableGame";
|
|
@@ -19,6 +21,13 @@ type PresentationFields = Omit<PlayableGame, "game" | "content" | "loop" | "Game
|
|
|
19
21
|
sceneScatterModels?: Record<string, string | ModelConfig>;
|
|
20
22
|
};
|
|
21
23
|
export type GameConfig<TAssetRef extends ModelAssetRef = ModelAssetRef> = EngineFields<TAssetRef> & PresentationFields;
|
|
24
|
+
/**
|
|
25
|
+
* The scene document's authored sky fills `backdrop.sky` when the game did not set one, so editor
|
|
26
|
+
* lighting-workspace edits render in `place()` worlds too. A document environment carrying only
|
|
27
|
+
* point lights yields no sky fields and leaves the world's sky alone.
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export declare function withDocumentSky(backdrop: BackdropConfig | undefined, doc: EditorDocument | undefined): BackdropConfig | undefined;
|
|
22
31
|
/**
|
|
23
32
|
* The one public authoring entry point: compose engine fields (systems, world, physics, input) and
|
|
24
33
|
* presentation fields (camera, HUD, audio, authored scene) into a `PlayableGame` ready for `GameHost`.
|
package/dist/defineGame.js
CHANGED
|
@@ -3,6 +3,8 @@ import { useMemo } from "react";
|
|
|
3
3
|
import { defineGameDefinition as defineEngineGame, } from "@jgengine/core/game/defineGame";
|
|
4
4
|
import { syncLifecyclePhase } from "@jgengine/core/game/gamePhase";
|
|
5
5
|
import { offline } from "@jgengine/core/runtime/adapter";
|
|
6
|
+
import { lightingFromDocument, skyFromDocument } from "@jgengine/core/editor/environment";
|
|
7
|
+
import { resolveGameLook } from "@jgengine/core/render/lookPreset";
|
|
6
8
|
import { EnvironmentScene } from "./environment.js";
|
|
7
9
|
import { terrainGroundColorSampler } from "./environment/terrainGroundColor.js";
|
|
8
10
|
import { AuthoredScene } from "./scene/AuthoredScene.js";
|
|
@@ -12,6 +14,23 @@ function worldBackdrop(feature) {
|
|
|
12
14
|
return _jsx(EnvironmentScene, { feature: feature });
|
|
13
15
|
};
|
|
14
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* The scene document's authored sky fills `backdrop.sky` when the game did not set one, so editor
|
|
19
|
+
* lighting-workspace edits render in `place()` worlds too. A document environment carrying only
|
|
20
|
+
* point lights yields no sky fields and leaves the world's sky alone.
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export function withDocumentSky(backdrop, doc) {
|
|
24
|
+
if (backdrop?.sky !== undefined || doc === undefined)
|
|
25
|
+
return backdrop;
|
|
26
|
+
const sky = skyFromDocument(doc);
|
|
27
|
+
if (sky === undefined || Object.keys(sky).length === 0)
|
|
28
|
+
return backdrop;
|
|
29
|
+
return { ...backdrop, sky };
|
|
30
|
+
}
|
|
31
|
+
function isEnvironmentSource(value) {
|
|
32
|
+
return typeof value === "object" && value !== null && "kind" in value && ["gradient", "hdri", "cube"].includes(value.kind);
|
|
33
|
+
}
|
|
15
34
|
function authoredSceneOverlay(document, placement, scatterModels, assets, world, Vfx) {
|
|
16
35
|
const terrain = world !== undefined && world.kind === "environment" ? world.terrain : undefined;
|
|
17
36
|
return function AuthoredSceneOverlay(props) {
|
|
@@ -28,12 +47,19 @@ const emptyUi = () => null;
|
|
|
28
47
|
* @capability define-game single public game-authoring path — compose systems, world, presentation, and authored scene in one definition
|
|
29
48
|
*/
|
|
30
49
|
export function defineGame(config) {
|
|
31
|
-
const { content, loop, GameUI, environment, camera, multiplayer, editorLayers, editorCatalogs, scenePlacement, sceneScatterModels, WorldOverlay, viewmodel, renderEntity, renderObject, entitySprites, entityModels, objectModels, hotbarSelection, prompts, pointer, touch, orientation, worldHealthBars, nameplates, presentationEffects, audio, entitySounds, objectSounds, worldItem, collision, movement, lighting, backdrop, postProcessing, shadows, presentation, objectStyles, devtools, capture, platforms, hudFit, ...engineFields } = config;
|
|
50
|
+
const { content, loop, GameUI, environment, camera, multiplayer, editorLayers, editorCatalogs, scenePlacement, sceneScatterModels, WorldOverlay, viewmodel, renderEntity, renderObject, entitySprites, entityModels, objectModels, hotbarSelection, prompts, pointer, touch, gamepad, localPlayers, viewports, orientation, worldHealthBars, nameplates, presentationEffects, audio, entitySounds, objectSounds, worldItem, collision, movement, lighting: authoredLighting, environmentSource, backdrop, look, postProcessing, shadows, graphics, presentation, objectStyles, devtools, capture, platforms, hudFit, ...engineFields } = config;
|
|
32
51
|
const game = defineEngineGame({
|
|
33
52
|
...engineFields,
|
|
34
53
|
multiplayer: multiplayer ?? offline(),
|
|
35
54
|
loop,
|
|
36
55
|
});
|
|
56
|
+
const resolvedLook = resolveGameLook({
|
|
57
|
+
look,
|
|
58
|
+
lighting: authoredLighting,
|
|
59
|
+
backdrop: withDocumentSky(backdrop, editorLayers),
|
|
60
|
+
postProcessing,
|
|
61
|
+
hasWorldSky: editorLayers?.environment?.preset !== undefined,
|
|
62
|
+
});
|
|
37
63
|
function withPhaseSync(inner) {
|
|
38
64
|
return (ctx, ...args) => {
|
|
39
65
|
inner?.(ctx, ...args);
|
|
@@ -53,8 +79,9 @@ export function defineGame(config) {
|
|
|
53
79
|
onDispose: composed?.onDispose?.bind(composed) ?? noop,
|
|
54
80
|
},
|
|
55
81
|
GameUI: GameUI ?? emptyUi,
|
|
56
|
-
environment: environment
|
|
57
|
-
|
|
82
|
+
environment: isEnvironmentSource(environment) ? undefined :
|
|
83
|
+
environment ??
|
|
84
|
+
(game.world?.kind === "environment" ? worldBackdrop(game.world) : undefined),
|
|
58
85
|
camera: camera ?? { perspective: "third" },
|
|
59
86
|
editorLayers,
|
|
60
87
|
editorCatalogs,
|
|
@@ -71,6 +98,9 @@ export function defineGame(config) {
|
|
|
71
98
|
prompts,
|
|
72
99
|
pointer,
|
|
73
100
|
touch,
|
|
101
|
+
gamepad,
|
|
102
|
+
localPlayers,
|
|
103
|
+
viewports,
|
|
74
104
|
orientation,
|
|
75
105
|
worldHealthBars,
|
|
76
106
|
nameplates,
|
|
@@ -81,10 +111,12 @@ export function defineGame(config) {
|
|
|
81
111
|
worldItem,
|
|
82
112
|
collision,
|
|
83
113
|
movement,
|
|
84
|
-
lighting,
|
|
85
|
-
|
|
86
|
-
|
|
114
|
+
lighting: resolvedLook.lighting ?? (editorLayers === undefined ? undefined : lightingFromDocument(editorLayers) ?? (editorLayers.environment?.pointLights === undefined ? undefined : { point: editorLayers.environment.pointLights })),
|
|
115
|
+
environmentSource: environmentSource ?? (isEnvironmentSource(environment) ? environment : undefined),
|
|
116
|
+
backdrop: resolvedLook.backdrop,
|
|
117
|
+
postProcessing: resolvedLook.postProcessing,
|
|
87
118
|
shadows,
|
|
119
|
+
graphics,
|
|
88
120
|
presentation,
|
|
89
121
|
objectStyles,
|
|
90
122
|
devtools,
|
|
@@ -5,6 +5,7 @@ import { devtools, instrumentLatency, } from "@jgengine/core/devtools/devtools";
|
|
|
5
5
|
import { fallbackSeamsSnapshot } from "@jgengine/core/devtools/fallbackSeams";
|
|
6
6
|
import { textureErrorsSnapshot } from "@jgengine/core/devtools/textureErrors";
|
|
7
7
|
import { MOVEMENT_TUNING } from "@jgengine/core/movement/movementModel";
|
|
8
|
+
import { heavyModels } from "../render/modelLoad.js";
|
|
8
9
|
import { collisionDebug } from "./collisionDebug.js";
|
|
9
10
|
import { readStoredOverrides } from "./devtoolsOverrides.js";
|
|
10
11
|
import { diagnose } from "./perfDiagnose.js";
|
|
@@ -212,6 +213,7 @@ export function DevtoolsOverlay({ open, ctx, playable, multiplayer, }) {
|
|
|
212
213
|
devtools.probes.register("objects", () => ctx.scene.object.list().length),
|
|
213
214
|
devtools.probes.register("fallbacks", () => fallbackSeamsSnapshot()),
|
|
214
215
|
devtools.probes.register("textureErrors", () => textureErrorsSnapshot()),
|
|
216
|
+
devtools.probes.register("heavyModels", () => heavyModels()),
|
|
215
217
|
];
|
|
216
218
|
return () => {
|
|
217
219
|
for (const dispose of disposers)
|
|
@@ -2,15 +2,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { bindingLabel } from "@jgengine/core/input/actionBindings";
|
|
3
3
|
function flattenCodes(codes) {
|
|
4
4
|
if (Array.isArray(codes)) {
|
|
5
|
-
return [{ codes: codes.map(bindingLabel).join(" / "), mode: "press" }];
|
|
5
|
+
return [{ codes: codes.map((code) => bindingLabel(code)).join(" / "), mode: "press" }];
|
|
6
6
|
}
|
|
7
7
|
const modes = codes;
|
|
8
8
|
const result = [];
|
|
9
9
|
if (modes.hold !== undefined && modes.hold.length > 0) {
|
|
10
|
-
result.push({ codes: modes.hold.map(bindingLabel).join(" / "), mode: "hold" });
|
|
10
|
+
result.push({ codes: modes.hold.map((code) => bindingLabel(code)).join(" / "), mode: "hold" });
|
|
11
11
|
}
|
|
12
12
|
if (modes.toggle !== undefined && modes.toggle.length > 0) {
|
|
13
|
-
result.push({ codes: modes.toggle.map(bindingLabel).join(" / "), mode: "toggle" });
|
|
13
|
+
result.push({ codes: modes.toggle.map((code) => bindingLabel(code)).join(" / "), mode: "toggle" });
|
|
14
14
|
}
|
|
15
15
|
return result;
|
|
16
16
|
}
|
|
@@ -15,6 +15,12 @@ type EditorHost = {
|
|
|
15
15
|
gameId: string;
|
|
16
16
|
handle(request: AgentBridgeRequest): unknown;
|
|
17
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* @internal
|
|
20
|
+
* Resolves true once `isLive()` reports a mounted editor host, polling every `intervalMs` up to
|
|
21
|
+
* `timeoutMs`. `editor_summon` awaits this so the next RPC verb in a `drive` batch finds the editor.
|
|
22
|
+
*/
|
|
23
|
+
export declare function waitForEditorHost(isLive: () => boolean, timeoutMs?: number, intervalMs?: number, sleep?: (ms: number) => Promise<void>): Promise<boolean>;
|
|
18
24
|
declare global {
|
|
19
25
|
interface Window {
|
|
20
26
|
__jgengineAgent?: {
|
|
@@ -3,6 +3,20 @@ 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
5
|
import { buildFullReport, buildLeanReport } from "./DevtoolsOverlay.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,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
|
+
}
|
|
@@ -4,8 +4,9 @@ import type { ActionStateTracker } from "@jgengine/core/input/actionBindings";
|
|
|
4
4
|
import type { PointerService } from "../pointer/pointerService.js";
|
|
5
5
|
import type { ShellMultiplayer } from "../multiplayer.js";
|
|
6
6
|
import type { PlayableGame } from "../registry.js";
|
|
7
|
+
import type { AuthoritativeFrameHandler } from "../worldSync.js";
|
|
7
8
|
export declare const POSTER_SETTLE_SECONDS = 1.6;
|
|
8
|
-
export declare function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryClickRef, pointerAxisRef, analogRef, gateRef, onRuntimeError, multiplayer, serverIdRef, pointerService, pointerAim, pingCommand, poster, onPosterSettled, }: {
|
|
9
|
+
export declare function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryClickRef, pointerAxisRef, analogRef, gateRef, onRuntimeError, multiplayer, serverIdRef, pointerService, pointerAim, pingCommand, poster, onPosterSettled, authoritativeFrameRef, }: {
|
|
9
10
|
ctx: GameContext;
|
|
10
11
|
playable: PlayableGame;
|
|
11
12
|
tracker: ActionStateTracker<string>;
|
|
@@ -37,4 +38,7 @@ export declare function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef,
|
|
|
37
38
|
pingCommand: string | undefined;
|
|
38
39
|
poster: boolean;
|
|
39
40
|
onPosterSettled: () => void;
|
|
41
|
+
authoritativeFrameRef: {
|
|
42
|
+
current: AuthoritativeFrameHandler | null;
|
|
43
|
+
};
|
|
40
44
|
}): null;
|