@jgengine/shell 0.19.0 → 0.22.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 +364 -0
- package/dist/GameHost.d.ts +12 -1
- package/dist/GameHost.js +14 -1
- package/dist/GamePlayerShell.js +113 -15
- package/dist/Shell3dPresentation.d.ts +2 -2
- package/dist/Shell3dPresentation.js +49 -7
- package/dist/ShellChrome.d.ts +2 -1
- package/dist/ShellChrome.js +22 -6
- package/dist/ShellHudPresentation.d.ts +11 -1
- package/dist/ShellHudPresentation.js +20 -6
- package/dist/audio/audioEngine.d.ts +16 -0
- package/dist/audio/audioEngine.js +178 -89
- package/dist/audio/audioWire.js +2 -2
- package/dist/audio/synthEngine.d.ts +7 -1
- package/dist/audio/synthEngine.js +103 -38
- package/dist/camera/GameCameraRig.js +2 -1
- package/dist/camera/GameFirstPersonCamera.d.ts +2 -2
- package/dist/camera/GameFirstPersonCamera.js +17 -13
- package/dist/camera/cameraRigs.js +8 -2
- package/dist/camera/chaseHeading.d.ts +9 -0
- package/dist/camera/chaseHeading.js +10 -0
- package/dist/camera/customMuzzle.d.ts +14 -0
- package/dist/camera/customMuzzle.js +68 -0
- package/dist/camera/index.d.ts +1 -0
- package/dist/camera/index.js +1 -0
- package/dist/camera/rigResolve.d.ts +3 -0
- package/dist/camera/rigResolve.js +11 -0
- package/dist/combat/ProjectileModels.d.ts +31 -0
- package/dist/combat/ProjectileModels.js +70 -0
- package/dist/commandSink.d.ts +1 -2
- package/dist/commandSink.js +3 -2
- package/dist/defineGame.d.ts +12 -5
- package/dist/defineGame.js +25 -10
- package/dist/devtools/DevtoolsOverlay.d.ts +2 -69
- package/dist/devtools/DevtoolsOverlay.js +14 -168
- package/dist/devtools/DevtoolsRuntime.d.ts +7 -0
- package/dist/devtools/DevtoolsRuntime.js +61 -0
- package/dist/devtools/agentBridge.js +1 -1
- package/dist/devtools/devtoolsReports.d.ts +68 -0
- package/dist/devtools/devtoolsReports.js +100 -0
- package/dist/devtools/latencyInstrumentation.d.ts +3 -0
- package/dist/devtools/latencyInstrumentation.js +11 -0
- package/dist/devtools/playtestProbe.d.ts +4 -0
- package/dist/devtools/playtestProbe.js +22 -0
- package/dist/drivers/FrameDriver.js +25 -21
- package/dist/drivers/HudOnlyDriver.d.ts +6 -1
- package/dist/drivers/HudOnlyDriver.js +32 -14
- package/dist/environment/EnvironmentScene.js +19 -15
- package/dist/environment/terrainGroundColor.d.ts +5 -4
- package/dist/environment/terrainGroundColor.js +19 -8
- package/dist/inputSink.d.ts +6 -6
- package/dist/inputSink.js +60 -21
- package/dist/lazyShell3dPresentation.d.ts +9 -0
- package/dist/lazyShell3dPresentation.js +24 -0
- package/dist/materialOverride.d.ts +42 -0
- package/dist/materialOverride.js +95 -36
- package/dist/multiplayer.d.ts +33 -3
- package/dist/multiplayer.js +105 -22
- package/dist/pointer/pointerService.d.ts +2 -0
- package/dist/pointer/pointerService.js +12 -2
- package/dist/postfx/PostProcessing.d.ts +10 -0
- package/dist/postfx/PostProcessing.js +9 -41
- package/dist/postfx/gradeShader.d.ts +1 -1
- package/dist/postfx/gradeShader.js +30 -2
- package/dist/postfx/shadowPrepass.d.ts +3 -0
- package/dist/postfx/shadowPrepass.js +18 -0
- package/dist/presentationRecovery.d.ts +75 -0
- package/dist/presentationRecovery.js +204 -0
- package/dist/presentationRecovery3d.d.ts +4 -0
- package/dist/presentationRecovery3d.js +32 -0
- package/dist/presentationResolve.d.ts +8 -1
- package/dist/presentationResolve.js +5 -2
- package/dist/render/CascadedShadows.d.ts +7 -2
- package/dist/render/CascadedShadows.js +98 -20
- package/dist/render/EnvironmentLighting.js +74 -35
- package/dist/render/HairCards.d.ts +7 -0
- package/dist/render/HairCards.js +23 -0
- package/dist/render/SceneModels.js +51 -77
- package/dist/render/authoredSurfaceMaterial.d.ts +43 -0
- package/dist/render/authoredSurfaceMaterial.js +108 -0
- package/dist/render/materialAppearanceSignals.d.ts +23 -0
- package/dist/render/materialAppearanceSignals.js +58 -0
- package/dist/render/materialAsset.d.ts +98 -0
- package/dist/render/materialAsset.js +454 -0
- package/dist/render/measureBounds.js +16 -3
- package/dist/render/measureCollisionMesh.js +8 -1
- package/dist/render/modelAssets.d.ts +8 -0
- package/dist/render/modelAssets.js +43 -0
- package/dist/render/modelBindPose.d.ts +5 -0
- package/dist/render/modelBindPose.js +35 -0
- package/dist/render/modelLoad.d.ts +3 -0
- package/dist/render/modelLoad.js +9 -0
- package/dist/render/modelNodeVisibility.d.ts +6 -0
- package/dist/render/modelNodeVisibility.js +18 -0
- package/dist/render/modelRender.d.ts +18 -1
- package/dist/render/modelRender.js +81 -3
- package/dist/render/projectedOverdraw.d.ts +35 -0
- package/dist/render/projectedOverdraw.js +204 -0
- package/dist/render/resolveModel.d.ts +12 -2
- package/dist/render/resolveModel.js +21 -4
- package/dist/render/rigNode.d.ts +26 -0
- package/dist/render/rigNode.js +32 -0
- package/dist/render/spriteRender.d.ts +4 -0
- package/dist/render/spriteRender.js +5 -0
- package/dist/render/staticShapeInstances.d.ts +34 -0
- package/dist/render/staticShapeInstances.js +78 -0
- package/dist/render/useFootIk.js +76 -11
- package/dist/render/useModelAnimation.d.ts +28 -2
- package/dist/render/useModelAnimation.js +194 -69
- package/dist/render/useModelInstance.d.ts +25 -0
- package/dist/render/useModelInstance.js +43 -0
- package/dist/scatter/scatterModels.d.ts +3 -2
- package/dist/scatter/scatterModels.js +15 -7
- package/dist/scene/AuthoredScene.d.ts +14 -6
- package/dist/scene/AuthoredScene.js +30 -9
- package/dist/scene/GeneratedAssetRenderer.js +11 -42
- package/dist/scene/GrassFieldRenderer.js +20 -2
- package/dist/settings/SettingsMenu.js +11 -18
- package/dist/settings/SettingsRuntime.d.ts +5 -0
- package/dist/settings/SettingsRuntime.js +29 -2
- package/dist/shellControlSuspension.d.ts +18 -0
- package/dist/shellControlSuspension.js +28 -0
- package/dist/shellInputPublication.d.ts +28 -0
- package/dist/shellInputPublication.js +49 -0
- package/dist/shellPointerInput.d.ts +18 -0
- package/dist/shellPointerInput.js +49 -0
- package/dist/structures/BuildingKitBatch.d.ts +3 -1
- package/dist/structures/BuildingKitBatch.js +34 -59
- package/dist/structures/BuildingRenderBudget.d.ts +23 -0
- package/dist/structures/BuildingRenderBudget.js +54 -0
- package/dist/structures/GeneratedBuilding.js +13 -8
- package/dist/structures/buildingKitMaterials.d.ts +20 -0
- package/dist/structures/buildingKitMaterials.js +70 -0
- package/dist/structures/buildingSpatialBatch.d.ts +11 -0
- package/dist/structures/buildingSpatialBatch.js +45 -0
- package/dist/terrain/CarvedTerrain.d.ts +2 -1
- package/dist/terrain/CarvedTerrain.js +2 -2
- package/dist/terrain/GrassField.d.ts +3 -1
- package/dist/terrain/GrassField.js +61 -6
- package/dist/terrain/grassGeometry.d.ts +13 -0
- package/dist/terrain/grassGeometry.js +57 -1
- package/dist/terrain/grassMaterial.js +2 -2
- package/dist/terrain/index.d.ts +1 -0
- package/dist/terrain/index.js +1 -0
- package/dist/terrain/terrainDetailMaterial.d.ts +3 -3
- package/dist/terrain/terrainDetailMaterial.js +7 -7
- package/dist/terrain/terrainMath.d.ts +2 -0
- package/dist/terrain/terrainMath.js +2 -0
- package/dist/terrain/terrainPaintColor.d.ts +11 -0
- package/dist/terrain/terrainPaintColor.js +47 -0
- package/dist/touch/TouchControlsOverlay.js +4 -4
- package/dist/useShellMultiplayerSync.js +4 -0
- package/dist/vfx/ParticleField.d.ts +23 -20
- package/dist/vfx/ParticleField.js +168 -50
- package/dist/visibility/CullingProvider.js +2 -62
- package/dist/visibility/cullingDriver.d.ts +9 -0
- package/dist/visibility/cullingDriver.js +68 -0
- package/dist/weather/AuthoredWeatherLayer.d.ts +16 -0
- package/dist/weather/AuthoredWeatherLayer.js +31 -0
- package/dist/weather/DustField.d.ts +2 -1
- package/dist/weather/DustField.js +17 -3
- package/dist/weather/FireSpreadLayer.d.ts +4 -2
- package/dist/weather/FireSpreadLayer.js +5 -11
- package/dist/weather/LightningStrike.d.ts +4 -1
- package/dist/weather/LightningStrike.js +13 -17
- package/dist/weather/RainField.d.ts +2 -1
- package/dist/weather/RainField.js +17 -3
- package/dist/weather/RainImpactField.d.ts +16 -0
- package/dist/weather/RainImpactField.js +89 -0
- package/dist/weather/SnowField.d.ts +2 -1
- package/dist/weather/SnowField.js +17 -3
- package/dist/weather/WeatherLayer.d.ts +11 -2
- package/dist/weather/WeatherLayer.js +30 -6
- package/dist/weather/index.d.ts +2 -0
- package/dist/weather/index.js +2 -0
- package/dist/weather/weatherMath.d.ts +2 -0
- package/dist/weather/weatherMath.js +8 -0
- package/dist/weather/weatherShelter.d.ts +18 -0
- package/dist/weather/weatherShelter.js +75 -0
- package/dist/weather/weatherUniforms.d.ts +40 -1
- package/dist/weather/weatherUniforms.js +29 -9
- package/dist/world/AuthoredFlocks.d.ts +22 -0
- package/dist/world/AuthoredFlocks.js +87 -0
- package/dist/world/StaticObjectModels.d.ts +8 -0
- package/dist/world/StaticObjectModels.js +75 -0
- package/dist/world/WorldHud.d.ts +14 -6
- package/dist/world/WorldHud.js +25 -12
- package/dist/world/WorldParticles.d.ts +10 -23
- package/dist/world/WorldParticles.js +209 -61
- package/dist/world/WorldScene.d.ts +3 -1
- package/dist/world/WorldScene.js +41 -26
- package/dist/world/staticObjectBatches.d.ts +35 -0
- package/dist/world/staticObjectBatches.js +163 -0
- package/dist/world/worldBarSamples.d.ts +37 -33
- package/dist/world/worldBarSamples.js +117 -75
- package/package.json +4 -4
- package/skills/ce-handoff/SKILL.md +1 -1
- package/skills/game-design/SKILL.md +2 -0
- package/skills/jgengine/SKILL.md +23 -2
- package/skills/jgengine/api.md +28 -17
- package/skills/jgengine/recipes/minimal-game.md +7 -2
- package/skills/jgengine-ui/SKILL.md +3 -1
- package/skills/jgengine-ui/api.md +219 -70
- package/skills/jgengine-ui/capabilities.md +119 -1
- package/skills/jgengine-ui/reference-primitives.md +93 -3
- package/skills/jgengine-ui/reference.md +2 -0
- package/skills/jgengine-world/SKILL.md +12 -3
- package/skills/jgengine-world/api.md +201 -58
- package/skills/jgengine-world/capabilities.md +116 -3
- package/skills/jgengine-world/recipes/character-animation.md +31 -8
- package/skills/jgengine-world/recipes/controls.md +4 -0
- package/skills/jgengine-world/recipes/npc-senses.md +12 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +3 -1
- package/skills/jgengine-world/reference.md +167 -5
- package/skills/jgengine-world/visibility.md +28 -1
|
@@ -10,18 +10,14 @@ import { useEntityRenderCues } from "../render/useEntityRenderCues.js";
|
|
|
10
10
|
import { usePlayerFov } from "./PlayerFov.js";
|
|
11
11
|
import { GAME_SIM_FRAME_PRIORITY, ORBIT_CAMERA_FRAME_PRIORITY } from "./orbitCameraMath.js";
|
|
12
12
|
import { requestRawPointerLock } from "../input/pointerLock.js";
|
|
13
|
+
import { readRegisteredFirstPersonMuzzle, registerTrackedFirstPersonMuzzle } from "./customMuzzle.js";
|
|
13
14
|
const DEFAULT_SENSITIVITY = 0.0025;
|
|
14
15
|
const DEFAULT_MAX_PITCH = 1.45;
|
|
15
16
|
const VIEWMODEL_ORIGIN = new THREE.Vector3(0.34, -0.26, -0.72);
|
|
16
17
|
const MUZZLE_TIP_LOCAL = new THREE.Vector3(0, 0.03, -0.61);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export function readFirstPersonMuzzle(target) {
|
|
21
|
-
if (!muzzleTracked)
|
|
22
|
-
return false;
|
|
23
|
-
target.copy(muzzleWorld);
|
|
24
|
-
return true;
|
|
18
|
+
/** Presentation muzzle in world space; camera-scoped when supplied, false leaves target unchanged. */
|
|
19
|
+
export function readFirstPersonMuzzle(target, camera) {
|
|
20
|
+
return readRegisteredFirstPersonMuzzle(target, camera);
|
|
25
21
|
}
|
|
26
22
|
export function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId, viewmodel, weapon, }) {
|
|
27
23
|
const eyeHeight = config?.eyeHeight ?? DEFAULT_EYE_HEIGHT;
|
|
@@ -107,9 +103,17 @@ export function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId
|
|
|
107
103
|
}
|
|
108
104
|
function FirstPersonViewmodel({ camera, viewmodel: Viewmodel, cuesRef, poseRef, }) {
|
|
109
105
|
const groupRef = useRef(null);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
const muzzleWorld = useRef(new THREE.Vector3());
|
|
107
|
+
const muzzleTracked = useRef(false);
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
muzzleTracked.current = false;
|
|
110
|
+
return registerTrackedFirstPersonMuzzle(camera, (target) => {
|
|
111
|
+
if (!muzzleTracked.current)
|
|
112
|
+
return false;
|
|
113
|
+
target.copy(muzzleWorld.current);
|
|
114
|
+
return true;
|
|
115
|
+
});
|
|
116
|
+
}, [camera]);
|
|
113
117
|
useFrame(() => {
|
|
114
118
|
const group = groupRef.current;
|
|
115
119
|
if (group === null)
|
|
@@ -128,8 +132,8 @@ function FirstPersonViewmodel({ camera, viewmodel: Viewmodel, cuesRef, poseRef,
|
|
|
128
132
|
group.rotateZ(pose?.roll ?? 0);
|
|
129
133
|
group.scale.set(squash, squash, 1);
|
|
130
134
|
group.updateMatrixWorld();
|
|
131
|
-
group.localToWorld(muzzleWorld.copy(MUZZLE_TIP_LOCAL));
|
|
132
|
-
muzzleTracked = true;
|
|
135
|
+
group.localToWorld(muzzleWorld.current.copy(MUZZLE_TIP_LOCAL));
|
|
136
|
+
muzzleTracked.current = true;
|
|
133
137
|
}, GAME_SIM_FRAME_PRIORITY);
|
|
134
138
|
if (Viewmodel !== undefined) {
|
|
135
139
|
return (_jsx("group", { ref: groupRef, children: _jsx(Viewmodel, { cuesRef: cuesRef }) }));
|
|
@@ -10,6 +10,7 @@ import { usePlayerFov } from "./PlayerFov.js";
|
|
|
10
10
|
import { useCameraShake } from "./shakeChannel.js";
|
|
11
11
|
import { applyCameraBlendStep, captureCameraBlendFrom, createCameraBlendScratch, } from "./cameraBlendMath.js";
|
|
12
12
|
import { requestRawPointerLock } from "../input/pointerLock.js";
|
|
13
|
+
import { resolveChaseHeading } from "./chaseHeading.js";
|
|
13
14
|
export { applyCameraBlendStep, captureCameraBlendFrom, createCameraBlendScratch, } from "./cameraBlendMath.js";
|
|
14
15
|
export const CAMERA_RIG_FRAME_PRIORITY = ORBIT_CAMERA_FRAME_PRIORITY;
|
|
15
16
|
export const CAMERA_POST_FRAME_PRIORITY = ORBIT_CAMERA_FRAME_PRIORITY + 0.5;
|
|
@@ -436,6 +437,7 @@ export function ChaseRig(props) {
|
|
|
436
437
|
const shake = useCameraShake();
|
|
437
438
|
const stateRef = useRef(createChaseRigState());
|
|
438
439
|
const viewRef = useRef(null);
|
|
440
|
+
const inputSeededRef = useRef(false);
|
|
439
441
|
useFrame((_, dt) => {
|
|
440
442
|
const tuning = ctx.camera.chaseTuning();
|
|
441
443
|
const merged = tuning === null ? config : { ...config, ...tuning };
|
|
@@ -448,7 +450,11 @@ export function ChaseRig(props) {
|
|
|
448
450
|
}
|
|
449
451
|
const entity = followId === null ? null : ctx.scene.entity.get(followId);
|
|
450
452
|
const follow = entity === null ? { x: 0, y: 0, z: 0 } : { x: entity.position[0], y: entity.position[1], z: entity.position[2] };
|
|
451
|
-
|
|
453
|
+
if (!inputSeededRef.current && merged?.headingSource === "input" && view === "chase" && (entity !== null || props.config?.initialYaw !== undefined)) {
|
|
454
|
+
props.yawRef.current = resolveChaseHeading(merged, entity?.rotationY ?? 0, undefined, props.config?.initialYaw);
|
|
455
|
+
inputSeededRef.current = true;
|
|
456
|
+
}
|
|
457
|
+
const yaw = resolveChaseHeading(merged, entity?.rotationY ?? 0, props.yawRef.current);
|
|
452
458
|
const v = entity?.velocity;
|
|
453
459
|
const published = v !== undefined && (v[0] !== 0 || v[1] !== 0 || v[2] !== 0);
|
|
454
460
|
const lookBack = resolved.lookBackAction !== null && ctx.input.isDown(resolved.lookBackAction);
|
|
@@ -478,7 +484,7 @@ export function ChaseRig(props) {
|
|
|
478
484
|
lookBack: lookBack && view === "chase",
|
|
479
485
|
fovKick: ctx.camera.takeFovKick(),
|
|
480
486
|
}, resolved, dt, view === "chase" ? clampBoom : undefined);
|
|
481
|
-
props.yawRef.current = step.anchorYaw;
|
|
487
|
+
props.yawRef.current = resolveChaseHeading(merged, step.anchorYaw, props.yawRef.current);
|
|
482
488
|
if (view !== "chase") {
|
|
483
489
|
const seat = view === "cockpit"
|
|
484
490
|
? merged?.seatOffsets?.cockpit ?? { x: 0, y: 1.2, z: 0.1 }
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ChaseCameraConfig } from "@jgengine/core/game/playableGame";
|
|
2
|
+
/**
|
|
3
|
+
* Select a chase heading without feeding target facing or camera smoothing back into input-owned yaw.
|
|
4
|
+
* An undefined input yaw seeds from `initialYaw`, or the acquired target's body heading.
|
|
5
|
+
* Seat views always use body heading; omitting `headingSource` preserves body follow.
|
|
6
|
+
*
|
|
7
|
+
* @capability chase-heading keep camera-relative input heading independent of body facing while retaining vehicle and seat camera follow
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveChaseHeading(config: Pick<ChaseCameraConfig, "headingSource" | "view"> | undefined, bodyYaw: number, inputYaw: number | undefined, initialYaw?: number): number;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select a chase heading without feeding target facing or camera smoothing back into input-owned yaw.
|
|
3
|
+
* An undefined input yaw seeds from `initialYaw`, or the acquired target's body heading.
|
|
4
|
+
* Seat views always use body heading; omitting `headingSource` preserves body follow.
|
|
5
|
+
*
|
|
6
|
+
* @capability chase-heading keep camera-relative input heading independent of body facing while retaining vehicle and seat camera follow
|
|
7
|
+
*/
|
|
8
|
+
export function resolveChaseHeading(config, bodyYaw, inputYaw, initialYaw) {
|
|
9
|
+
return config?.headingSource === "input" && (config.view ?? "chase") === "chase" ? inputYaw ?? initialYaw ?? bodyYaw : bodyYaw;
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Camera, Vector3 } from "three";
|
|
2
|
+
/** Writes a live presentation muzzle in world space; false means this rig has no muzzle available. */
|
|
3
|
+
export type FirstPersonMuzzleReader = (target: Vector3) => boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Register a custom rig's presentation muzzle for its Three.js camera. Newest available custom
|
|
6
|
+
* reader wins over that camera's stock muzzle; call the returned cleanup on unmount.
|
|
7
|
+
*
|
|
8
|
+
* @capability custom-viewmodel-muzzle align tracer origins with a custom viewmodel's live muzzle without changing authoritative shots
|
|
9
|
+
*/
|
|
10
|
+
export declare function registerFirstPersonMuzzle(camera: Camera, reader: FirstPersonMuzzleReader): () => void;
|
|
11
|
+
/** @internal Stock rig registration shares camera isolation and owner-scoped cleanup. */
|
|
12
|
+
export declare function registerTrackedFirstPersonMuzzle(camera: Camera, reader: FirstPersonMuzzleReader): () => void;
|
|
13
|
+
/** @internal Unscoped legacy reads consider the newest 64 registrations per priority. */
|
|
14
|
+
export declare function readRegisteredFirstPersonMuzzle(target: Vector3, camera?: Camera): boolean;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Camera, Vector3 } from "three";
|
|
2
|
+
const MAX_READERS = 64;
|
|
3
|
+
const custom = new WeakMap();
|
|
4
|
+
const tracked = new WeakMap();
|
|
5
|
+
const legacyCustom = [];
|
|
6
|
+
const legacyTracked = [];
|
|
7
|
+
function register(camera, reader, scoped, legacy) {
|
|
8
|
+
const readers = scoped.get(camera) ?? [];
|
|
9
|
+
if (readers.length >= MAX_READERS)
|
|
10
|
+
throw new RangeError("Too many muzzle readers for one camera");
|
|
11
|
+
const entry = { reader, scratch: new Vector3(), reading: false, disposed: false };
|
|
12
|
+
readers.push(entry);
|
|
13
|
+
scoped.set(camera, readers);
|
|
14
|
+
legacy.push(entry);
|
|
15
|
+
let disposed = false;
|
|
16
|
+
return () => {
|
|
17
|
+
if (disposed)
|
|
18
|
+
return;
|
|
19
|
+
disposed = true;
|
|
20
|
+
entry.disposed = true;
|
|
21
|
+
readers.splice(readers.indexOf(entry), 1);
|
|
22
|
+
legacy.splice(legacy.indexOf(entry), 1);
|
|
23
|
+
if (readers.length === 0)
|
|
24
|
+
scoped.delete(camera);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Register a custom rig's presentation muzzle for its Three.js camera. Newest available custom
|
|
29
|
+
* reader wins over that camera's stock muzzle; call the returned cleanup on unmount.
|
|
30
|
+
*
|
|
31
|
+
* @capability custom-viewmodel-muzzle align tracer origins with a custom viewmodel's live muzzle without changing authoritative shots
|
|
32
|
+
*/
|
|
33
|
+
export function registerFirstPersonMuzzle(camera, reader) {
|
|
34
|
+
return register(camera, reader, custom, legacyCustom);
|
|
35
|
+
}
|
|
36
|
+
/** @internal Stock rig registration shares camera isolation and owner-scoped cleanup. */
|
|
37
|
+
export function registerTrackedFirstPersonMuzzle(camera, reader) {
|
|
38
|
+
return register(camera, reader, tracked, legacyTracked);
|
|
39
|
+
}
|
|
40
|
+
function read(readers, target) {
|
|
41
|
+
if (readers === undefined)
|
|
42
|
+
return false;
|
|
43
|
+
const pending = readers.slice(-MAX_READERS);
|
|
44
|
+
for (let i = pending.length - 1; i >= 0; i--) {
|
|
45
|
+
const entry = pending[i];
|
|
46
|
+
if (entry.reading || entry.disposed)
|
|
47
|
+
continue;
|
|
48
|
+
entry.reading = true;
|
|
49
|
+
try {
|
|
50
|
+
if (!entry.reader(entry.scratch))
|
|
51
|
+
continue;
|
|
52
|
+
const { x, y, z } = entry.scratch;
|
|
53
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z))
|
|
54
|
+
continue;
|
|
55
|
+
target.copy(entry.scratch);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
entry.reading = false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
/** @internal Unscoped legacy reads consider the newest 64 registrations per priority. */
|
|
65
|
+
export function readRegisteredFirstPersonMuzzle(target, camera) {
|
|
66
|
+
return read(camera === undefined ? legacyCustom : custom.get(camera), target) ||
|
|
67
|
+
read(camera === undefined ? legacyTracked : tracked.get(camera), target);
|
|
68
|
+
}
|
package/dist/camera/index.d.ts
CHANGED
|
@@ -9,3 +9,4 @@ export { ControllerCameraShake, type ControllerCameraShakeProps } from "./Contro
|
|
|
9
9
|
export { rtsPanKeysConflict } from "./rigMath.js";
|
|
10
10
|
export { GAME_SIM_FRAME_PRIORITY } from "./orbitCameraMath.js";
|
|
11
11
|
export { CAMERA_TRANSPARENT_USERDATA, isCameraOccluderTransparent, type CameraOccluder, } from "./cameraCollision.js";
|
|
12
|
+
export { registerFirstPersonMuzzle, type FirstPersonMuzzleReader } from "./customMuzzle.js";
|
package/dist/camera/index.js
CHANGED
|
@@ -9,3 +9,4 @@ export { ControllerCameraShake } from "./ControllerCameraShake.js";
|
|
|
9
9
|
export { rtsPanKeysConflict } from "./rigMath.js";
|
|
10
10
|
export { GAME_SIM_FRAME_PRIORITY } from "./orbitCameraMath.js";
|
|
11
11
|
export { CAMERA_TRANSPARENT_USERDATA, isCameraOccluderTransparent, } from "./cameraCollision.js";
|
|
12
|
+
export { registerFirstPersonMuzzle } from "./customMuzzle.js";
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { CameraRigKind, GameCameraConfig } from "@jgengine/core/game/playableGame";
|
|
2
|
+
import type { CameraRigOverride } from "@jgengine/core/runtime/cameraDirector";
|
|
3
|
+
/** @internal */
|
|
4
|
+
export declare function resolveRuntimeCameraConfig(config: GameCameraConfig | undefined, override: CameraRigOverride | null | undefined): GameCameraConfig | undefined;
|
|
2
5
|
/**
|
|
3
6
|
* Resolves which rig mounts from a `GameCameraConfig`. Precedence, most to least
|
|
4
7
|
* specific: an explicit `rig` field always wins; then `perspective: "first"`
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/** @internal */
|
|
2
|
+
export function resolveRuntimeCameraConfig(config, override) {
|
|
3
|
+
if (override === null || override === undefined)
|
|
4
|
+
return config;
|
|
5
|
+
const merged = { ...config, ...override.config, rig: override.kind };
|
|
6
|
+
for (const key of ["firstPerson", "topDown", "rts", "shoulder", "lockOn", "chase", "observer", "turntable", "sideScroll", "inspection", "shake"]) {
|
|
7
|
+
if (override.config[key] !== undefined)
|
|
8
|
+
Object.assign(merged, { [key]: { ...config?.[key], ...override.config[key] } });
|
|
9
|
+
}
|
|
10
|
+
return merged;
|
|
11
|
+
}
|
|
1
12
|
/**
|
|
2
13
|
* Resolves which rig mounts from a `GameCameraConfig`. Precedence, most to least
|
|
3
14
|
* specific: an explicit `rig` field always wins; then `perspective: "first"`
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type ReactElement } from "react";
|
|
2
|
+
import * as THREE from "three";
|
|
3
|
+
import type { LiveProjectile } from "@jgengine/core/combat/projectiles";
|
|
4
|
+
import type { ModelConfig } from "@jgengine/core/game/playableGame";
|
|
5
|
+
/** Explicit opt-in shaft/head geometry when a game has no projectile model. */
|
|
6
|
+
export interface ProjectileShaft {
|
|
7
|
+
length?: number;
|
|
8
|
+
radius?: number;
|
|
9
|
+
shaftColor?: THREE.ColorRepresentation;
|
|
10
|
+
headColor?: THREE.ColorRepresentation;
|
|
11
|
+
}
|
|
12
|
+
/** Render simulated flights without owning their travel or settlement. */
|
|
13
|
+
export interface ProjectileModelsProps {
|
|
14
|
+
model?: ModelConfig | ((projectile: LiveProjectile) => ModelConfig | undefined);
|
|
15
|
+
/** Defaults to no fallback geometry. */
|
|
16
|
+
fallback?: ProjectileShaft;
|
|
17
|
+
/** The model's nose axis. Default +Y, matching the shaft fallback. */
|
|
18
|
+
forward?: readonly [number, number, number];
|
|
19
|
+
maxInstances?: number;
|
|
20
|
+
/** An alternate authoritative source; otherwise uses the context's live flights. */
|
|
21
|
+
source?: () => readonly LiveProjectile[];
|
|
22
|
+
}
|
|
23
|
+
/** @internal Apply live position and full velocity orientation without frame allocations. */
|
|
24
|
+
export declare function applyProjectilePose(group: THREE.Group, projectile: Pick<LiveProjectile, "position" | "velocity">, forward: THREE.Vector3, direction: THREE.Vector3): void;
|
|
25
|
+
/**
|
|
26
|
+
* Mount caller-owned models at authoritative projectile positions. Flight ids
|
|
27
|
+
* determine lifetime; each frame only updates transforms, and settlement removes
|
|
28
|
+
* the model. Optional shaft/head geometry is enabled only by the caller.
|
|
29
|
+
* @capability projectile-models render authoritative projectile flights with caller models or opt-in shaft/head geometry, velocity orientation, bounded instances, and settlement cleanup
|
|
30
|
+
*/
|
|
31
|
+
export declare function ProjectileModels({ model, fallback, forward, maxInstances, source }: ProjectileModelsProps): ReactElement;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useFrame } from "@react-three/fiber";
|
|
3
|
+
import { useRef, useState } from "react";
|
|
4
|
+
import * as THREE from "three";
|
|
5
|
+
import { useGameContext } from "@jgengine/react/provider";
|
|
6
|
+
import { IsolatedEntityModel } from "../render/SceneModels.js";
|
|
7
|
+
/** @internal Apply live position and full velocity orientation without frame allocations. */
|
|
8
|
+
export function applyProjectilePose(group, projectile, forward, direction) {
|
|
9
|
+
group.position.set(...projectile.position);
|
|
10
|
+
direction.set(...projectile.velocity);
|
|
11
|
+
if (direction.lengthSq() > 1e-12)
|
|
12
|
+
group.quaternion.setFromUnitVectors(forward, direction.normalize());
|
|
13
|
+
}
|
|
14
|
+
function Shaft({ length = 0.85, radius = 0.012, shaftColor = "#947044", headColor = "#b9c4d0" }) {
|
|
15
|
+
const headLength = length * 0.18;
|
|
16
|
+
return _jsxs(_Fragment, { children: [_jsxs("mesh", { position: [0, -(length + headLength) / 2, 0], children: [_jsx("cylinderGeometry", { args: [radius, radius, length - headLength, 6] }), _jsx("meshStandardMaterial", { color: shaftColor, roughness: 0.78 })] }), _jsxs("mesh", { position: [0, -headLength / 2, 0], children: [_jsx("coneGeometry", { args: [radius * 3.5, headLength, 6] }), _jsx("meshStandardMaterial", { color: headColor, metalness: 0.5, roughness: 0.38 })] })] });
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Mount caller-owned models at authoritative projectile positions. Flight ids
|
|
20
|
+
* determine lifetime; each frame only updates transforms, and settlement removes
|
|
21
|
+
* the model. Optional shaft/head geometry is enabled only by the caller.
|
|
22
|
+
* @capability projectile-models render authoritative projectile flights with caller models or opt-in shaft/head geometry, velocity orientation, bounded instances, and settlement cleanup
|
|
23
|
+
*/
|
|
24
|
+
export function ProjectileModels({ model, fallback, forward = [0, 1, 0], maxInstances = 256, source }) {
|
|
25
|
+
const ctx = useGameContext();
|
|
26
|
+
const entries = useRef(new Map());
|
|
27
|
+
const seen = useRef(new Set());
|
|
28
|
+
const direction = useRef(new THREE.Vector3());
|
|
29
|
+
const nose = useRef(new THREE.Vector3());
|
|
30
|
+
const [, setGeneration] = useState(0);
|
|
31
|
+
nose.current.set(...forward).normalize();
|
|
32
|
+
if (nose.current.lengthSq() === 0)
|
|
33
|
+
nose.current.set(0, 1, 0);
|
|
34
|
+
const limit = Number.isFinite(maxInstances) ? Math.max(0, Math.min(1024, Math.floor(maxInstances))) : 256;
|
|
35
|
+
useFrame(() => {
|
|
36
|
+
let changed = false;
|
|
37
|
+
const live = source?.() ?? ctx.scene.entity.activeProjectiles();
|
|
38
|
+
seen.current.clear();
|
|
39
|
+
for (let i = 0, n = Math.min(limit, live.length); i < n; i++) {
|
|
40
|
+
const flight = live[i];
|
|
41
|
+
const key = `${flight.shotId}:${flight.pellet}`;
|
|
42
|
+
seen.current.add(key);
|
|
43
|
+
const current = entries.current.get(key);
|
|
44
|
+
if (current === undefined) {
|
|
45
|
+
entries.current.set(key, { flight, group: null });
|
|
46
|
+
changed = true;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
current.flight = flight;
|
|
50
|
+
if (current.group !== null)
|
|
51
|
+
applyProjectilePose(current.group, flight, nose.current, direction.current);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const key of entries.current.keys())
|
|
55
|
+
if (!seen.current.has(key)) {
|
|
56
|
+
entries.current.delete(key);
|
|
57
|
+
changed = true;
|
|
58
|
+
}
|
|
59
|
+
if (changed)
|
|
60
|
+
setGeneration((n) => n + 1);
|
|
61
|
+
});
|
|
62
|
+
return _jsx(_Fragment, { children: Array.from(entries.current, ([key, entry]) => {
|
|
63
|
+
const selected = typeof model === "function" ? model(entry.flight) : model;
|
|
64
|
+
return _jsx("group", { ref: (group) => {
|
|
65
|
+
entry.group = group;
|
|
66
|
+
if (group !== null)
|
|
67
|
+
applyProjectilePose(group, entry.flight, nose.current, direction.current);
|
|
68
|
+
}, children: selected === undefined ? fallback === undefined ? null : _jsx(Shaft, { ...fallback }) : _jsx(IsolatedEntityModel, { model: selected, instanceId: key }) }, key);
|
|
69
|
+
}) });
|
|
70
|
+
}
|
package/dist/commandSink.d.ts
CHANGED
|
@@ -14,8 +14,7 @@ export declare function localCommandSink(ctx: GameContext): CommandSink;
|
|
|
14
14
|
export declare function remoteCommandSink(backend: Pick<LiveGameBackend, "transport">, serverId: string): CommandSink;
|
|
15
15
|
/**
|
|
16
16
|
* The sink a server-authoritative shell should dispatch gameplay commands through: remote when the game opts into
|
|
17
|
-
* `authority: "server"` and a server is joined
|
|
18
|
-
* scroll) keep calling `ctx.game.commands.run` directly — only authoritative gameplay verbs route to the host.
|
|
17
|
+
* `authority: "server"` and a server is joined. Before join it discards commands rather than mutating a replica.
|
|
19
18
|
* @internal
|
|
20
19
|
*/
|
|
21
20
|
export declare function resolveCommandSink(ctx: GameContext, opts: {
|
package/dist/commandSink.js
CHANGED
|
@@ -20,13 +20,14 @@ export function remoteCommandSink(backend, serverId) {
|
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* The sink a server-authoritative shell should dispatch gameplay commands through: remote when the game opts into
|
|
23
|
-
* `authority: "server"` and a server is joined
|
|
24
|
-
* scroll) keep calling `ctx.game.commands.run` directly — only authoritative gameplay verbs route to the host.
|
|
23
|
+
* `authority: "server"` and a server is joined. Before join it discards commands rather than mutating a replica.
|
|
25
24
|
* @internal
|
|
26
25
|
*/
|
|
27
26
|
export function resolveCommandSink(ctx, opts) {
|
|
28
27
|
if (opts.serverAuthoritative && opts.backend !== null && opts.serverId !== null) {
|
|
29
28
|
return remoteCommandSink(opts.backend, opts.serverId);
|
|
30
29
|
}
|
|
30
|
+
if (opts.serverAuthoritative)
|
|
31
|
+
return { run() { } };
|
|
31
32
|
return localCommandSink(ctx);
|
|
32
33
|
}
|
package/dist/defineGame.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ import type { BackdropConfig } from "@jgengine/core/game/playableGame";
|
|
|
5
5
|
import type { GameContext, GameContextContent } from "@jgengine/core/runtime/gameContext";
|
|
6
6
|
import type { ModelAssetRef } from "@jgengine/core/scene/assetCatalog";
|
|
7
7
|
import type { ModelConfig } from "@jgengine/core/game/playableGame";
|
|
8
|
+
import type { SkyEnvironmentConfig } from "@jgengine/core/world/features";
|
|
9
|
+
import type { ResolveAuthoredObjectsOptions } from "@jgengine/core/world/authoredObjects";
|
|
8
10
|
import type { PlayableGame } from "./registry.js";
|
|
9
11
|
type EngineFields<TAssetRef extends ModelAssetRef> = Omit<GameDefinitionConfig<TAssetRef>, "loop" | "ui" | "multiplayer"> & {
|
|
10
12
|
multiplayer?: GameDefinitionConfig<TAssetRef>["multiplayer"];
|
|
@@ -13,21 +15,26 @@ type PresentationFields = Omit<PlayableGame, "game" | "content" | "loop" | "Game
|
|
|
13
15
|
content?: GameContextContent;
|
|
14
16
|
loop?: Partial<GameLoop<GameContext>>;
|
|
15
17
|
GameUI?: ComponentType;
|
|
16
|
-
/** Tunes how the auto-mounted `AuthoredScene` places
|
|
17
|
-
scenePlacement?: boolean | {
|
|
18
|
+
/** Tunes how the auto-mounted `AuthoredScene` places catalog-id markers into the object store. Default `true`; `excludeKinds` replaces the resolver's mob/boss exclusions, so include those when adding game-owned spawn kinds. Pass `false` when the game places all content itself. */
|
|
19
|
+
scenePlacement?: boolean | ({
|
|
18
20
|
verticalOffset?: number;
|
|
19
|
-
};
|
|
21
|
+
} & ResolveAuthoredObjectsOptions);
|
|
20
22
|
/** GLB models for the auto-mounted scene's scatter palette items, keyed by palette item id; string ids resolve through the game's asset catalog. Unmatched items keep the built-in proxy meshes. */
|
|
21
23
|
sceneScatterModels?: Record<string, string | ModelConfig>;
|
|
24
|
+
/** Animated species models for cosmetic authored habitats; gameplay actors remain game-owned. */
|
|
25
|
+
sceneFlockModels?: Record<string, string | ModelConfig>;
|
|
26
|
+
/** Visible ground path kinds; omit to draw every non-scatter path, or restrict to keep flight/patrol guides out of world roads. */
|
|
27
|
+
scenePathKinds?: readonly string[];
|
|
22
28
|
};
|
|
23
29
|
export type GameConfig<TAssetRef extends ModelAssetRef = ModelAssetRef> = EngineFields<TAssetRef> & PresentationFields;
|
|
24
30
|
/**
|
|
25
31
|
* 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.
|
|
32
|
+
* lighting-workspace edits render in `place()` worlds too. Defined document fields overlay the
|
|
33
|
+
* legacy world's sky, preserving settings the editor cannot represent. An environment carrying only
|
|
27
34
|
* point lights yields no sky fields and leaves the world's sky alone.
|
|
28
35
|
* @internal
|
|
29
36
|
*/
|
|
30
|
-
export declare function withDocumentSky(backdrop: BackdropConfig | undefined, doc: EditorDocument | undefined): BackdropConfig | undefined;
|
|
37
|
+
export declare function withDocumentSky(backdrop: BackdropConfig | undefined, doc: EditorDocument | undefined, worldSky?: SkyEnvironmentConfig): BackdropConfig | undefined;
|
|
31
38
|
/**
|
|
32
39
|
* The one public authoring entry point: compose engine fields (systems, world, physics, input) and
|
|
33
40
|
* presentation fields (camera, HUD, audio, authored scene) into a `PlayableGame` ready for `GameHost`.
|
package/dist/defineGame.js
CHANGED
|
@@ -16,26 +16,34 @@ function worldBackdrop(feature) {
|
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
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.
|
|
19
|
+
* lighting-workspace edits render in `place()` worlds too. Defined document fields overlay the
|
|
20
|
+
* legacy world's sky, preserving settings the editor cannot represent. An environment carrying only
|
|
20
21
|
* point lights yields no sky fields and leaves the world's sky alone.
|
|
21
22
|
* @internal
|
|
22
23
|
*/
|
|
23
|
-
export function withDocumentSky(backdrop, doc) {
|
|
24
|
+
export function withDocumentSky(backdrop, doc, worldSky) {
|
|
24
25
|
if (backdrop?.sky !== undefined || doc === undefined)
|
|
25
26
|
return backdrop;
|
|
26
27
|
const sky = skyFromDocument(doc);
|
|
27
28
|
if (sky === undefined || Object.keys(sky).length === 0)
|
|
28
29
|
return backdrop;
|
|
29
|
-
return { ...backdrop, sky
|
|
30
|
+
return { ...backdrop, sky: {
|
|
31
|
+
...worldSky,
|
|
32
|
+
...sky,
|
|
33
|
+
...(sky.sun === undefined ? {} : { sun: { ...worldSky?.sun,
|
|
34
|
+
...Object.fromEntries(Object.entries(sky.sun).filter(([, value]) => value !== undefined)) } }),
|
|
35
|
+
...(sky.fog === undefined ? {} : { fog: { ...worldSky?.fog,
|
|
36
|
+
...Object.fromEntries(Object.entries(sky.fog).filter(([, value]) => value !== undefined)) } }),
|
|
37
|
+
} };
|
|
30
38
|
}
|
|
31
39
|
function isEnvironmentSource(value) {
|
|
32
40
|
return typeof value === "object" && value !== null && "kind" in value && ["gradient", "hdri", "cube"].includes(value.kind);
|
|
33
41
|
}
|
|
34
|
-
function authoredSceneOverlay(document, placement, scatterModels, assets, world, Vfx) {
|
|
42
|
+
function authoredSceneOverlay(document, diagnostics, placement, scatterModels, flockModels, pathKinds, assets, world, Vfx) {
|
|
35
43
|
const terrain = world !== undefined && world.kind === "environment" ? world.terrain : undefined;
|
|
36
44
|
return function AuthoredSceneOverlay(props) {
|
|
37
|
-
const groundColorAt = useMemo(() => terrainGroundColorSampler(terrain, props.ctx.world.ground), [props.ctx.world.ground]);
|
|
38
|
-
return (_jsxs(_Fragment, { children: [_jsx(AuthoredScene, { document: document, field: props.ctx.world.ground, placeObjects: placement, ...(scatterModels === undefined ? {} : { scatterModels,
|
|
45
|
+
const groundColorAt = useMemo(() => terrainGroundColorSampler(terrain, props.ctx.world.ground, document.terrain), [props.ctx.world.ground]);
|
|
46
|
+
return (_jsxs(_Fragment, { children: [_jsx(AuthoredScene, { document: document, diagnostics: diagnostics, field: props.ctx.world.ground, placeObjects: placement, assets: assets, ...(scatterModels === undefined ? {} : { scatterModels }), ...(flockModels === undefined ? {} : { flockModels }), ...(pathKinds === undefined ? {} : { pathKinds }), ...(groundColorAt === undefined ? {} : { groundColorAt }) }), Vfx === undefined ? null : _jsx(Vfx, { ...props })] }));
|
|
39
47
|
};
|
|
40
48
|
}
|
|
41
49
|
const emptyUi = () => null;
|
|
@@ -47,16 +55,22 @@ const emptyUi = () => null;
|
|
|
47
55
|
* @capability define-game single public game-authoring path — compose systems, world, presentation, and authored scene in one definition
|
|
48
56
|
*/
|
|
49
57
|
export function defineGame(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;
|
|
58
|
+
const { content, loop, GameUI, environment, camera, multiplayer, editorLayers, editorCatalogs, scenePlacement, sceneScatterModels, sceneFlockModels, scenePathKinds, 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, settings, objectStyles, devtools, capture, platforms, hudFit, ...engineFields } = config;
|
|
51
59
|
const game = defineEngineGame({
|
|
52
60
|
...engineFields,
|
|
61
|
+
...(editorLayers === undefined ? {} : { authoredDocument: editorLayers }),
|
|
53
62
|
multiplayer: multiplayer ?? offline(),
|
|
54
|
-
loop,
|
|
63
|
+
loop: { ...loop, onDispose(ctx) { try {
|
|
64
|
+
loop?.onDispose?.(ctx);
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
ctx.environment.dispose();
|
|
68
|
+
} } },
|
|
55
69
|
});
|
|
56
70
|
const resolvedLook = resolveGameLook({
|
|
57
71
|
look,
|
|
58
72
|
lighting: authoredLighting,
|
|
59
|
-
backdrop: withDocumentSky(backdrop, editorLayers),
|
|
73
|
+
backdrop: withDocumentSky(backdrop, editorLayers, engineFields.world?.kind === "environment" ? engineFields.world.sky : undefined),
|
|
60
74
|
postProcessing,
|
|
61
75
|
hasWorldSky: editorLayers?.environment?.preset !== undefined,
|
|
62
76
|
});
|
|
@@ -87,7 +101,7 @@ export function defineGame(config) {
|
|
|
87
101
|
editorCatalogs,
|
|
88
102
|
WorldOverlay: editorLayers === undefined
|
|
89
103
|
? WorldOverlay
|
|
90
|
-
: authoredSceneOverlay(editorLayers, scenePlacement ?? true, sceneScatterModels, game.assets, game.world, WorldOverlay),
|
|
104
|
+
: authoredSceneOverlay(editorLayers, devtools !== false, scenePlacement ?? true, sceneScatterModels, sceneFlockModels, scenePathKinds, game.assets, game.world, WorldOverlay),
|
|
91
105
|
viewmodel,
|
|
92
106
|
renderEntity,
|
|
93
107
|
renderObject,
|
|
@@ -118,6 +132,7 @@ export function defineGame(config) {
|
|
|
118
132
|
shadows,
|
|
119
133
|
graphics,
|
|
120
134
|
presentation,
|
|
135
|
+
settings,
|
|
121
136
|
objectStyles,
|
|
122
137
|
devtools,
|
|
123
138
|
capture,
|
|
@@ -1,78 +1,11 @@
|
|
|
1
|
-
import { type DevtoolsSnapshot } from "@jgengine/core/devtools/devtools";
|
|
2
1
|
import type { GameContext } from "@jgengine/core/runtime/gameContext";
|
|
3
2
|
import type { ShellMultiplayer } from "../multiplayer.js";
|
|
4
3
|
import type { PlayableGame } from "../registry.js";
|
|
5
4
|
export { applyStoredDevtoolsOverrides, persistDevtoolsOverrides } from "./devtoolsOverrides.js";
|
|
6
|
-
|
|
7
|
-
export declare function withDevtoolsLatency(multiplayer: ShellMultiplayer): ShellMultiplayer;
|
|
5
|
+
export { withDevtoolsLatency } from "./latencyInstrumentation.js";
|
|
8
6
|
/** @internal */
|
|
9
7
|
export declare function DevtoolsRendererProbe(): null;
|
|
10
|
-
|
|
11
|
-
export declare function buildLeanReport(playable: PlayableGame): {
|
|
12
|
-
game: string;
|
|
13
|
-
at: number;
|
|
14
|
-
why: string | null;
|
|
15
|
-
frame: {
|
|
16
|
-
fps: number;
|
|
17
|
-
avgFrameMs: number;
|
|
18
|
-
p95FrameMs: number;
|
|
19
|
-
maxFrameMs: number;
|
|
20
|
-
avgSimMs: number;
|
|
21
|
-
maxSimMs: number;
|
|
22
|
-
avgOutsideMs: number;
|
|
23
|
-
maxOutsideMs: number;
|
|
24
|
-
longFrames: number;
|
|
25
|
-
samples: number;
|
|
26
|
-
phases: {
|
|
27
|
-
name: string;
|
|
28
|
-
avgMs: number;
|
|
29
|
-
maxMs: number;
|
|
30
|
-
pctOfSim: number;
|
|
31
|
-
}[];
|
|
32
|
-
} | null;
|
|
33
|
-
render: import("@jgengine/core/devtools/types").RenderSample | null;
|
|
34
|
-
latency: import("@jgengine/core/devtools/types").LatencyStats | null;
|
|
35
|
-
longFrameSummary: {
|
|
36
|
-
count: number;
|
|
37
|
-
culprits: {
|
|
38
|
-
culprit: string;
|
|
39
|
-
count: number;
|
|
40
|
-
}[];
|
|
41
|
-
maxFrameMs: number;
|
|
42
|
-
maxDrawCalls: number | undefined;
|
|
43
|
-
maxGeometries: number | undefined;
|
|
44
|
-
} | null;
|
|
45
|
-
longFrames: {
|
|
46
|
-
at: number;
|
|
47
|
-
frameMs: number;
|
|
48
|
-
simMs: number;
|
|
49
|
-
outsideMs: number;
|
|
50
|
-
culprit: string;
|
|
51
|
-
reason: string;
|
|
52
|
-
phases: {
|
|
53
|
-
name: string;
|
|
54
|
-
ms: number;
|
|
55
|
-
}[];
|
|
56
|
-
probes: Record<string, unknown>;
|
|
57
|
-
render: import("@jgengine/core/devtools/types").RenderSample | null;
|
|
58
|
-
}[];
|
|
59
|
-
probes: Record<string, unknown>;
|
|
60
|
-
logs: import("@jgengine/core/devtools/types").DevtoolsLogEntry[] | undefined;
|
|
61
|
-
controls: {
|
|
62
|
-
name: string;
|
|
63
|
-
value: unknown;
|
|
64
|
-
initial: unknown;
|
|
65
|
-
}[] | undefined;
|
|
66
|
-
discovered: {
|
|
67
|
-
id: string;
|
|
68
|
-
value: unknown;
|
|
69
|
-
}[] | undefined;
|
|
70
|
-
discoveredCount: number;
|
|
71
|
-
};
|
|
72
|
-
/** @internal */
|
|
73
|
-
export declare function buildFullReport(playable: PlayableGame): DevtoolsSnapshot & {
|
|
74
|
-
game: string;
|
|
75
|
-
};
|
|
8
|
+
export { buildLeanReport, buildFullReport } from "./devtoolsReports.js";
|
|
76
9
|
/** @internal */
|
|
77
10
|
export declare function DevtoolsOverlay({ open, ctx, playable, multiplayer, }: {
|
|
78
11
|
open: boolean;
|