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