@jgengine/shell 0.10.0 → 0.12.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 +39 -2
- package/LICENSE +203 -661
- package/README.md +1 -1
- package/dist/CombatPresentation.d.ts +5 -0
- package/dist/CombatPresentation.js +8 -0
- package/dist/GameHost.d.ts +25 -1
- package/dist/GameHost.js +60 -4
- package/dist/GamePhaseStamp.d.ts +1 -0
- package/dist/GamePhaseStamp.js +9 -0
- package/dist/GamePlayer.d.ts +5 -0
- package/dist/GamePlayer.js +5 -0
- package/dist/GamePlayerShell.d.ts +5 -12
- package/dist/GamePlayerShell.js +67 -1332
- package/dist/GameUiPreview.js +3 -2
- package/dist/Shell3dPresentation.d.ts +63 -0
- package/dist/Shell3dPresentation.js +302 -0
- package/dist/ShellChrome.d.ts +51 -0
- package/dist/ShellChrome.js +51 -0
- package/dist/ShellHudPresentation.d.ts +41 -0
- package/dist/ShellHudPresentation.js +22 -0
- package/dist/audio/audioEngine.d.ts +4 -0
- package/dist/audio/audioEngine.js +128 -21
- package/dist/audio/audioWire.d.ts +40 -0
- package/dist/audio/audioWire.js +62 -0
- package/dist/audio/loopParams.d.ts +20 -0
- package/dist/audio/loopParams.js +28 -0
- package/dist/behaviour.d.ts +2 -1
- package/dist/behaviour.js +2 -1
- package/dist/behaviourAttach.d.ts +1 -0
- package/dist/behaviourAttach.js +1 -0
- package/dist/behaviourDriver.d.ts +1 -0
- package/dist/behaviourDriver.js +1 -0
- package/dist/boundActionDispatch.d.ts +13 -0
- package/dist/boundActionDispatch.js +37 -0
- package/dist/camera/GameCameraRig.d.ts +5 -2
- package/dist/camera/GameCameraRig.js +8 -3
- package/dist/camera/GameFirstPersonCamera.d.ts +9 -2
- package/dist/camera/GameFirstPersonCamera.js +8 -3
- package/dist/camera/GameInspectionCamera.d.ts +1 -1
- package/dist/camera/GameInspectionCamera.js +2 -2
- package/dist/camera/GameOrbitCamera.js +24 -9
- package/dist/camera/cameraBlendMath.d.ts +3 -0
- package/dist/camera/cameraBlendMath.js +3 -0
- package/dist/camera/cameraRigs.d.ts +3 -1
- package/dist/camera/cameraRigs.js +21 -3
- package/dist/camera/fovPreference.d.ts +6 -1
- package/dist/camera/fovPreference.js +6 -1
- package/dist/camera/index.d.ts +4 -7
- package/dist/camera/index.js +4 -7
- package/dist/camera/inspectionCameraMath.d.ts +7 -2
- package/dist/camera/inspectionCameraMath.js +7 -2
- package/dist/camera/orbitCameraMath.d.ts +36 -6
- package/dist/camera/orbitCameraMath.js +56 -6
- package/dist/camera/rigMath.d.ts +114 -24
- package/dist/camera/rigMath.js +129 -24
- package/dist/camera/rigResolve.d.ts +4 -2
- package/dist/camera/rigResolve.js +4 -2
- package/dist/camera/shakeChannel.d.ts +6 -2
- package/dist/camera/shakeChannel.js +6 -2
- package/dist/camera/shakeChannelMath.d.ts +1 -0
- package/dist/camera/shakeChannelMath.js +1 -0
- package/dist/commandSink.d.ts +8 -3
- package/dist/commandSink.js +8 -3
- package/dist/defineGame.d.ts +15 -1
- package/dist/defineGame.js +50 -9
- package/dist/devtools/ColPanel.d.ts +1 -0
- package/dist/devtools/ColPanel.js +14 -0
- package/dist/devtools/DevtoolsOverlay.d.ts +11 -7
- package/dist/devtools/DevtoolsOverlay.js +47 -462
- package/dist/devtools/KeysPanel.d.ts +4 -0
- package/dist/devtools/KeysPanel.js +31 -0
- package/dist/devtools/LogsPanel.d.ts +1 -0
- package/dist/devtools/LogsPanel.js +12 -0
- package/dist/devtools/NetPanel.d.ts +4 -0
- package/dist/devtools/NetPanel.js +10 -0
- package/dist/devtools/PerfPanel.d.ts +4 -0
- package/dist/devtools/PerfPanel.js +53 -0
- package/dist/devtools/TunePanel.d.ts +3 -0
- package/dist/devtools/TunePanel.js +309 -0
- package/dist/devtools/agentBridge.d.ts +41 -0
- package/dist/devtools/agentBridge.js +266 -0
- package/dist/devtools/collisionDebug.d.ts +6 -0
- package/dist/devtools/collisionDebug.js +6 -0
- package/dist/devtools/collisionDebugMath.d.ts +8 -2
- package/dist/devtools/collisionDebugMath.js +24 -5
- package/dist/devtools/devtoolsOverrides.d.ts +6 -0
- package/dist/devtools/devtoolsOverrides.js +48 -0
- package/dist/devtools/panelAtoms.d.ts +9 -0
- package/dist/devtools/panelAtoms.js +10 -0
- package/dist/devtools/perfDiagnose.d.ts +2 -0
- package/dist/devtools/perfDiagnose.js +43 -0
- package/dist/diagnostics/RuntimeDiagnostics.d.ts +36 -0
- package/dist/diagnostics/RuntimeDiagnostics.js +80 -0
- package/dist/drivers/FrameDriver.d.ts +37 -0
- package/dist/drivers/FrameDriver.js +232 -0
- package/dist/drivers/HudOnlyDriver.d.ts +16 -0
- package/dist/drivers/HudOnlyDriver.js +60 -0
- package/dist/environment/Daylight.d.ts +23 -7
- package/dist/environment/Daylight.js +106 -13
- package/dist/environment/EnvironmentScene.js +44 -9
- package/dist/environment/VolumetricClouds.d.ts +17 -0
- package/dist/environment/VolumetricClouds.js +202 -0
- package/dist/environment/daylightCycle.d.ts +3 -1
- package/dist/environment/daylightCycle.js +3 -1
- package/dist/environment/groundPadMath.d.ts +3 -0
- package/dist/environment/groundPadMath.js +3 -0
- package/dist/environment/index.d.ts +1 -0
- package/dist/environment/index.js +1 -0
- package/dist/environment/skyLightingPolicy.d.ts +2 -0
- package/dist/environment/skyLightingPolicy.js +2 -0
- package/dist/environment/terrainGroundColor.d.ts +9 -0
- package/dist/environment/terrainGroundColor.js +22 -0
- package/dist/gameKit.d.ts +27 -0
- package/dist/gameKit.js +29 -0
- package/dist/hotbarActions.d.ts +26 -0
- package/dist/hotbarActions.js +27 -0
- package/dist/inputSink.d.ts +16 -4
- package/dist/inputSink.js +57 -7
- package/dist/map/MapMarkerBeacons.d.ts +3 -2
- package/dist/materialOverride.d.ts +10 -0
- package/dist/materialOverride.js +15 -3
- package/dist/multiplayer.js +8 -2
- package/dist/postfx/PostProcessing.d.ts +9 -1
- package/dist/postfx/PostProcessing.js +56 -11
- package/dist/postfx/gradeShader.js +9 -1
- package/dist/presentationResolve.d.ts +35 -0
- package/dist/presentationResolve.js +51 -0
- package/dist/registry.d.ts +4 -2
- package/dist/render/EnvironmentLighting.d.ts +10 -0
- package/dist/render/EnvironmentLighting.js +34 -0
- package/dist/render/SceneLighting.d.ts +7 -0
- package/dist/render/SceneLighting.js +18 -0
- package/dist/render/SceneModels.d.ts +14 -0
- package/dist/render/SceneModels.js +337 -0
- package/dist/render/assetBase.d.ts +17 -0
- package/dist/render/assetBase.js +35 -0
- package/dist/render/defaultSurface.d.ts +29 -0
- package/dist/render/defaultSurface.js +101 -0
- package/dist/render/modelLoad.d.ts +24 -0
- package/dist/render/modelLoad.js +76 -0
- package/dist/render/modelRender.d.ts +11 -0
- package/dist/render/modelRender.js +15 -0
- package/dist/render/resolveModel.d.ts +54 -3
- package/dist/render/resolveModel.js +116 -4
- package/dist/render/useEntityRenderCues.d.ts +13 -0
- package/dist/render/useEntityRenderCues.js +62 -0
- package/dist/scatter/InstancedScatter.d.ts +24 -0
- package/dist/scatter/InstancedScatter.js +135 -0
- package/dist/scatter/scatterModels.d.ts +25 -0
- package/dist/scatter/scatterModels.js +61 -0
- package/dist/scatter/scatterProxies.d.ts +6 -0
- package/dist/scatter/scatterProxies.js +118 -0
- package/dist/scatter.d.ts +1 -0
- package/dist/scatter.js +1 -0
- package/dist/scene/AuthoredScene.d.ts +84 -0
- package/dist/scene/AuthoredScene.js +178 -0
- package/dist/scene/CityRenderer.d.ts +2 -0
- package/dist/scene/CityRenderer.js +1031 -0
- package/dist/scene/GeneratedAssetRenderer.d.ts +14 -0
- package/dist/scene/GeneratedAssetRenderer.js +59 -0
- package/dist/scene/GrassFieldRenderer.d.ts +2 -0
- package/dist/scene/GrassFieldRenderer.js +73 -0
- package/dist/scene/PoleLineRenderer.d.ts +2 -0
- package/dist/scene/PoleLineRenderer.js +287 -0
- package/dist/scene/SoilPatchRenderer.d.ts +2 -0
- package/dist/scene/SoilPatchRenderer.js +84 -0
- package/dist/scene/StudioStage.d.ts +37 -0
- package/dist/scene/StudioStage.js +42 -0
- package/dist/scene/WaterRenderer.d.ts +2 -0
- package/dist/scene/WaterRenderer.js +61 -0
- package/dist/scene/builtinSceneKindRenderers.d.ts +2 -0
- package/dist/scene/builtinSceneKindRenderers.js +26 -0
- package/dist/scene/cityWindowMaterial.d.ts +16 -0
- package/dist/scene/cityWindowMaterial.js +81 -0
- package/dist/scene/sceneKindRenderers.d.ts +30 -0
- package/dist/scene/sceneKindRenderers.js +17 -0
- package/dist/scene.d.ts +1 -0
- package/dist/scene.js +1 -0
- package/dist/settings/appliedSettings.d.ts +7 -1
- package/dist/settings/appliedSettings.js +13 -2
- package/dist/settings/settingsController.d.ts +2 -0
- package/dist/settings/settingsController.js +22 -1
- package/dist/shellConstants.d.ts +4 -0
- package/dist/shellConstants.js +4 -0
- package/dist/shellMovement.d.ts +8 -0
- package/dist/shellMovement.js +11 -0
- package/dist/shellPointer.d.ts +20 -0
- package/dist/shellPointer.js +28 -0
- package/dist/structures/PlacementGhost.d.ts +4 -0
- package/dist/structures/PlacementGhost.js +4 -0
- package/dist/structures/TransformGizmo.d.ts +53 -0
- package/dist/structures/TransformGizmo.js +59 -0
- package/dist/structures/index.d.ts +1 -0
- package/dist/structures/index.js +1 -0
- package/dist/terrain/GrassField.d.ts +11 -3
- package/dist/terrain/GrassField.js +16 -6
- package/dist/terrain/TerraformBrushCursor.js +3 -0
- package/dist/terrain/grassBudget.d.ts +11 -2
- package/dist/terrain/grassBudget.js +20 -4
- package/dist/terrain/grassGeometry.d.ts +35 -0
- package/dist/terrain/grassGeometry.js +85 -20
- package/dist/terrain/grassMaterial.d.ts +20 -0
- package/dist/terrain/grassMaterial.js +68 -13
- package/dist/terrain/heightfieldGeometry.d.ts +34 -0
- package/dist/terrain/heightfieldGeometry.js +109 -0
- package/dist/terrain/index.d.ts +1 -0
- package/dist/terrain/index.js +1 -0
- package/dist/terrain/random.d.ts +3 -0
- package/dist/terrain/random.js +3 -0
- package/dist/terrain/soilPatchMaterial.d.ts +23 -0
- package/dist/terrain/soilPatchMaterial.js +147 -0
- package/dist/terrain/terrainDetailMaterial.d.ts +19 -2
- package/dist/terrain/terrainDetailMaterial.js +87 -7
- package/dist/terrain/terrainMath.d.ts +8 -1
- package/dist/terrain/terrainMath.js +8 -1
- package/dist/touch/TouchControlsOverlay.d.ts +9 -5
- package/dist/touch/TouchControlsOverlay.js +201 -22
- package/dist/useShellMultiplayerSync.d.ts +9 -0
- package/dist/useShellMultiplayerSync.js +98 -0
- package/dist/visibility/CullingProvider.js +1 -1
- package/dist/water/Ocean.d.ts +7 -1
- package/dist/water/Ocean.js +16 -3
- package/dist/water/OceanConfig.d.ts +11 -0
- package/dist/water/OceanConfig.js +8 -0
- package/dist/water/OceanMaterial.d.ts +12 -0
- package/dist/water/OceanMaterial.js +9 -0
- package/dist/water/OceanShader.d.ts +2 -2
- package/dist/water/OceanShader.js +78 -10
- package/dist/weather/fireSpreadPose.d.ts +1 -0
- package/dist/weather/fireSpreadPose.js +1 -0
- package/dist/weather/weatherGeometry.d.ts +1 -0
- package/dist/weather/weatherGeometry.js +1 -0
- package/dist/weather/weatherMath.d.ts +3 -0
- package/dist/weather/weatherMath.js +3 -0
- package/dist/weather/weatherUniforms.d.ts +3 -0
- package/dist/weather/weatherUniforms.js +3 -0
- package/dist/world/PlaceScene.d.ts +11 -0
- package/dist/world/PlaceScene.js +27 -0
- package/dist/world/RetainedVfx.d.ts +10 -0
- package/dist/world/RetainedVfx.js +136 -0
- package/dist/world/WorldHud.d.ts +49 -2
- package/dist/world/WorldHud.js +87 -2
- package/dist/world/WorldScene.d.ts +21 -0
- package/dist/world/WorldScene.js +186 -0
- package/dist/world/WorldVfx.d.ts +9 -0
- package/dist/world/WorldVfx.js +184 -0
- package/dist/world/entityPose.d.ts +2 -0
- package/dist/world/entityPose.js +2 -0
- package/dist/world/floatTextStyle.d.ts +1 -0
- package/dist/world/floatTextStyle.js +1 -0
- package/dist/world/telegraphPulse.d.ts +1 -0
- package/dist/world/telegraphPulse.js +1 -0
- package/dist/world/worldBarSamples.d.ts +25 -0
- package/dist/world/worldBarSamples.js +38 -0
- package/dist/worldSky.d.ts +11 -0
- package/dist/worldSky.js +21 -0
- package/dist/worldSync.d.ts +2 -1
- package/dist/worldSync.js +2 -1
- package/package.json +6 -6
- package/skills/game-design/SKILL.md +28 -0
- package/skills/game-design/agents/openai.yaml +4 -0
- package/skills/game-design/references/application-playbook.md +170 -0
- package/skills/game-design/references/field-guide.md +288 -0
- package/skills/jgengine/SKILL.md +54 -0
- package/skills/jgengine/api.md +527 -0
- package/skills/jgengine/capabilities.md +131 -0
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-ui/SKILL.md +41 -0
- package/skills/jgengine-ui/api.md +2007 -0
- package/skills/jgengine-ui/capabilities.md +211 -0
- package/skills/jgengine-ui/recipes/portable-minimap.md +150 -0
- package/skills/jgengine-ui/reference.md +513 -0
- package/skills/jgengine-ui/references/visual-scorecard.md +73 -0
- package/skills/jgengine-world/SKILL.md +51 -0
- package/skills/jgengine-world/api.md +3056 -0
- package/skills/jgengine-world/capabilities.md +476 -0
- package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
- package/skills/jgengine-world/reference.md +261 -0
- package/skills/level-design/SKILL.md +54 -0
- package/skills/level-design/agents/openai.yaml +4 -0
- package/skills/level-design/references/application-playbook.md +192 -0
- package/skills/level-design/references/field-guide.md +337 -0
- package/skills/level-design/references/live-editor-loop.md +179 -0
- package/dist/cartridge.d.ts +0 -145
- package/dist/cartridge.js +0 -246
- package/llms.txt +0 -2761
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { devtools } from "@jgengine/core/devtools/devtools";
|
|
2
|
+
import { getSaveEndpoint } from "@jgengine/core/devtools/saveEndpoint";
|
|
3
|
+
import { isHudAnchor, listActiveHudLayouts, } from "@jgengine/core/ui/hudLayout";
|
|
4
|
+
import { listHudPanelTypes, resolvePanelResize, resizePanelSize, } from "@jgengine/core/ui/hudDocument";
|
|
5
|
+
import { buildFullReport, buildLeanReport } from "./DevtoolsOverlay.js";
|
|
6
|
+
async function saveScene(editorHost) {
|
|
7
|
+
const endpoint = getSaveEndpoint();
|
|
8
|
+
if (endpoint === null)
|
|
9
|
+
return { ok: false, error: "no save endpoint — save_scene only works in the dev runner" };
|
|
10
|
+
const exported = editorHost.handle({ method: "export_document" });
|
|
11
|
+
const json = exported?.result?.json;
|
|
12
|
+
if (exported?.ok !== true || typeof json !== "string") {
|
|
13
|
+
return { ok: false, error: "export_document failed" };
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const response = await fetch(endpoint.url, {
|
|
17
|
+
method: "POST",
|
|
18
|
+
headers: { "content-type": "application/json" },
|
|
19
|
+
body: JSON.stringify({ kind: "editor-document", gameId: editorHost.gameId, json }),
|
|
20
|
+
});
|
|
21
|
+
const saved = (await response.json());
|
|
22
|
+
return saved.ok ? { ok: true, path: saved.path } : { ok: false, error: saved.error ?? "save failed" };
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function canvasPanels() {
|
|
29
|
+
return listActiveHudLayouts().map((layout, index) => {
|
|
30
|
+
const state = layout.getState();
|
|
31
|
+
return {
|
|
32
|
+
layout: index,
|
|
33
|
+
editing: state.editing,
|
|
34
|
+
locked: state.locked,
|
|
35
|
+
panels: Object.values(state.panels).map((panel) => ({
|
|
36
|
+
id: panel.id,
|
|
37
|
+
anchor: panel.placement.anchor,
|
|
38
|
+
dx: panel.placement.dx,
|
|
39
|
+
dy: panel.placement.dy,
|
|
40
|
+
width: panel.width,
|
|
41
|
+
height: panel.height,
|
|
42
|
+
visible: panel.visible,
|
|
43
|
+
type: panel.type,
|
|
44
|
+
moved: panel.moved,
|
|
45
|
+
})),
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function writeUiPanelDocument(id, panel) {
|
|
50
|
+
const host = window.__jgengineEditorHost;
|
|
51
|
+
if (host === undefined)
|
|
52
|
+
return false;
|
|
53
|
+
host.handle({
|
|
54
|
+
method: "dispatch",
|
|
55
|
+
command: { type: "setUiPanel", id, patch: panel },
|
|
56
|
+
});
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
function panelLayoutSnapshot(placement, extras) {
|
|
60
|
+
return {
|
|
61
|
+
anchor: placement.anchor,
|
|
62
|
+
dx: placement.dx,
|
|
63
|
+
dy: placement.dy,
|
|
64
|
+
...(extras?.width === undefined ? {} : { width: extras.width }),
|
|
65
|
+
...(extras?.height === undefined ? {} : { height: extras.height }),
|
|
66
|
+
...(extras?.visible === undefined ? {} : { visible: extras.visible }),
|
|
67
|
+
...(extras?.type === undefined ? {} : { type: extras.type }),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* @internal
|
|
72
|
+
* Headless control plane spanning the F2 chord family: debug mode (F2+D),
|
|
73
|
+
* canvas mode (F2+C), and editor mode (F2+E). Installed on every
|
|
74
|
+
* GamePlayerShell mount as `window.__jgengineAgent`; unknown verbs delegate
|
|
75
|
+
* to the editor host when one is live, so a single RPC surface covers all
|
|
76
|
+
* three modes for `bun run drive --rpc`.
|
|
77
|
+
*/
|
|
78
|
+
export function installAgentBridge(options) {
|
|
79
|
+
const { playable, devtoolsEnabled, isDevtoolsOpen, setDevtoolsOpen } = options;
|
|
80
|
+
const handle = (request) => {
|
|
81
|
+
const method = typeof request?.method === "string" ? request.method : "";
|
|
82
|
+
switch (method) {
|
|
83
|
+
case "agent_status":
|
|
84
|
+
return {
|
|
85
|
+
ok: true,
|
|
86
|
+
game: playable.game.name,
|
|
87
|
+
modes: {
|
|
88
|
+
debug: { available: devtoolsEnabled, open: isDevtoolsOpen(), chord: "F2+D" },
|
|
89
|
+
canvas: { available: listActiveHudLayouts().length > 0, chord: "F2+C" },
|
|
90
|
+
editor: {
|
|
91
|
+
available: window.__jgengineEditorHost !== undefined || window.__jgengineSummonEditor !== undefined,
|
|
92
|
+
live: window.__jgengineEditorHost !== undefined,
|
|
93
|
+
chord: "F2+E",
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
case "debug_open": {
|
|
98
|
+
if (!devtoolsEnabled)
|
|
99
|
+
return { ok: false, error: "devtools disabled for this game" };
|
|
100
|
+
const open = typeof request.open === "boolean" ? request.open : !isDevtoolsOpen();
|
|
101
|
+
setDevtoolsOpen(open);
|
|
102
|
+
return { ok: true, open };
|
|
103
|
+
}
|
|
104
|
+
case "debug_snapshot":
|
|
105
|
+
return { ok: true, snapshot: buildLeanReport(playable) };
|
|
106
|
+
case "debug_report":
|
|
107
|
+
return { ok: true, report: buildFullReport(playable) };
|
|
108
|
+
case "debug_perf_reset":
|
|
109
|
+
devtools.frame.reset();
|
|
110
|
+
return { ok: true };
|
|
111
|
+
case "canvas_state":
|
|
112
|
+
return { ok: true, layouts: canvasPanels() };
|
|
113
|
+
case "canvas_set_editing": {
|
|
114
|
+
const editing = request.editing === true;
|
|
115
|
+
const layouts = listActiveHudLayouts();
|
|
116
|
+
if (layouts.length === 0)
|
|
117
|
+
return { ok: false, error: "no HudCanvas mounted" };
|
|
118
|
+
for (const layout of layouts)
|
|
119
|
+
layout.setEditing(editing);
|
|
120
|
+
return { ok: true, editing };
|
|
121
|
+
}
|
|
122
|
+
case "canvas_move_panel": {
|
|
123
|
+
const id = typeof request.id === "string" ? request.id : "";
|
|
124
|
+
const anchor = request.anchor;
|
|
125
|
+
const dx = typeof request.dx === "number" ? request.dx : 0;
|
|
126
|
+
const dy = typeof request.dy === "number" ? request.dy : 0;
|
|
127
|
+
if (id === "" || !isHudAnchor(anchor)) {
|
|
128
|
+
return { ok: false, error: "canvas_move_panel needs { id, anchor } (dx/dy optional px offsets)" };
|
|
129
|
+
}
|
|
130
|
+
const placement = { anchor, dx, dy };
|
|
131
|
+
for (const layout of listActiveHudLayouts()) {
|
|
132
|
+
const existing = layout.getState().panels[id];
|
|
133
|
+
if (existing === undefined)
|
|
134
|
+
continue;
|
|
135
|
+
layout.hydrate(JSON.stringify({
|
|
136
|
+
v: 2,
|
|
137
|
+
panels: {
|
|
138
|
+
[id]: {
|
|
139
|
+
...placement,
|
|
140
|
+
...(existing.width === undefined ? {} : { width: existing.width }),
|
|
141
|
+
...(existing.height === undefined ? {} : { height: existing.height }),
|
|
142
|
+
...(existing.visible === false ? { visible: false } : {}),
|
|
143
|
+
...(existing.type === undefined ? {} : { type: existing.type }),
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
}));
|
|
147
|
+
const panel = layout.getState().panels[id];
|
|
148
|
+
const uiPanel = panelLayoutSnapshot(panel.placement, {
|
|
149
|
+
width: panel.width,
|
|
150
|
+
height: panel.height,
|
|
151
|
+
visible: panel.visible,
|
|
152
|
+
type: panel.type,
|
|
153
|
+
});
|
|
154
|
+
const document = writeUiPanelDocument(id, uiPanel);
|
|
155
|
+
return { ok: true, id, placement: panel.placement, panel: uiPanel, document };
|
|
156
|
+
}
|
|
157
|
+
return { ok: false, error: `no HUD panel "${id}" — canvas_state lists ids` };
|
|
158
|
+
}
|
|
159
|
+
case "canvas_resize_panel": {
|
|
160
|
+
const id = typeof request.id === "string" ? request.id : "";
|
|
161
|
+
if (id === "") {
|
|
162
|
+
return { ok: false, error: "canvas_resize_panel needs { id, width?/height? or dw?/dh? }" };
|
|
163
|
+
}
|
|
164
|
+
for (const layout of listActiveHudLayouts()) {
|
|
165
|
+
const existing = layout.getState().panels[id];
|
|
166
|
+
if (existing === undefined)
|
|
167
|
+
continue;
|
|
168
|
+
const resize = resolvePanelResize(existing.type);
|
|
169
|
+
const current = {
|
|
170
|
+
width: existing.width ?? (typeof request.width === "number" ? request.width : 100),
|
|
171
|
+
height: existing.height ?? (typeof request.height === "number" ? request.height : 40),
|
|
172
|
+
};
|
|
173
|
+
let next = current;
|
|
174
|
+
if (typeof request.width === "number" || typeof request.height === "number") {
|
|
175
|
+
const absolute = {
|
|
176
|
+
width: typeof request.width === "number" ? request.width : current.width,
|
|
177
|
+
height: typeof request.height === "number" ? request.height : current.height,
|
|
178
|
+
};
|
|
179
|
+
next = resizePanelSize(current, { dw: absolute.width - current.width, dh: absolute.height - current.height }, "both", resize);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
const dw = typeof request.dw === "number" ? request.dw : 0;
|
|
183
|
+
const dh = typeof request.dh === "number" ? request.dh : 0;
|
|
184
|
+
next = resizePanelSize(current, { dw, dh }, resize.axes, resize);
|
|
185
|
+
}
|
|
186
|
+
layout.hydrate(JSON.stringify({
|
|
187
|
+
v: 2,
|
|
188
|
+
panels: {
|
|
189
|
+
[id]: {
|
|
190
|
+
anchor: existing.placement.anchor,
|
|
191
|
+
dx: existing.placement.dx,
|
|
192
|
+
dy: existing.placement.dy,
|
|
193
|
+
width: next.width,
|
|
194
|
+
height: next.height,
|
|
195
|
+
...(existing.visible === false ? { visible: false } : {}),
|
|
196
|
+
...(existing.type === undefined ? {} : { type: existing.type }),
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
}));
|
|
200
|
+
const panel = layout.getState().panels[id];
|
|
201
|
+
const uiPanel = panelLayoutSnapshot(panel.placement, {
|
|
202
|
+
width: panel.width,
|
|
203
|
+
height: panel.height,
|
|
204
|
+
visible: panel.visible,
|
|
205
|
+
type: panel.type,
|
|
206
|
+
});
|
|
207
|
+
const document = writeUiPanelDocument(id, uiPanel);
|
|
208
|
+
return { ok: true, id, size: next, panel: uiPanel, document };
|
|
209
|
+
}
|
|
210
|
+
return { ok: false, error: `no HUD panel "${id}" — canvas_state lists ids` };
|
|
211
|
+
}
|
|
212
|
+
case "canvas_list_panel_types":
|
|
213
|
+
return {
|
|
214
|
+
ok: true,
|
|
215
|
+
types: listHudPanelTypes().map((def) => ({
|
|
216
|
+
id: def.id,
|
|
217
|
+
label: def.label,
|
|
218
|
+
resize: def.resize,
|
|
219
|
+
minWidth: def.minWidth,
|
|
220
|
+
maxWidth: def.maxWidth,
|
|
221
|
+
minHeight: def.minHeight,
|
|
222
|
+
maxHeight: def.maxHeight,
|
|
223
|
+
hasSchema: def.schema !== undefined,
|
|
224
|
+
})),
|
|
225
|
+
};
|
|
226
|
+
case "canvas_reset": {
|
|
227
|
+
const id = typeof request.id === "string" ? request.id : undefined;
|
|
228
|
+
const layouts = listActiveHudLayouts();
|
|
229
|
+
if (layouts.length === 0)
|
|
230
|
+
return { ok: false, error: "no HudCanvas mounted" };
|
|
231
|
+
for (const layout of layouts)
|
|
232
|
+
layout.reset(id);
|
|
233
|
+
return { ok: true };
|
|
234
|
+
}
|
|
235
|
+
case "editor_summon": {
|
|
236
|
+
if (window.__jgengineEditorHost !== undefined)
|
|
237
|
+
return { ok: true, live: true };
|
|
238
|
+
if (window.__jgengineSummonEditor === undefined) {
|
|
239
|
+
return { ok: false, error: "no editor summoner on this page — open with ?mode=editor" };
|
|
240
|
+
}
|
|
241
|
+
window.__jgengineSummonEditor();
|
|
242
|
+
return { ok: true, live: false };
|
|
243
|
+
}
|
|
244
|
+
case "save_scene": {
|
|
245
|
+
const editorHost = window.__jgengineEditorHost;
|
|
246
|
+
if (editorHost === undefined)
|
|
247
|
+
return { ok: false, error: "no live editor — editor_summon or mode=editor first" };
|
|
248
|
+
return saveScene(editorHost);
|
|
249
|
+
}
|
|
250
|
+
default: {
|
|
251
|
+
const editorHost = window.__jgengineEditorHost;
|
|
252
|
+
if (editorHost !== undefined)
|
|
253
|
+
return editorHost.handle(request);
|
|
254
|
+
return {
|
|
255
|
+
ok: false,
|
|
256
|
+
error: `unknown method "${method}" and no live editor — editor verbs need mode=editor or editor_summon first`,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
window.__jgengineAgent = { handle };
|
|
262
|
+
return () => {
|
|
263
|
+
if (window.__jgengineAgent?.handle === handle)
|
|
264
|
+
delete window.__jgengineAgent;
|
|
265
|
+
};
|
|
266
|
+
}
|
|
@@ -27,10 +27,15 @@ export interface CollisionDebugState {
|
|
|
27
27
|
projectileTraceLifeMs: number;
|
|
28
28
|
}
|
|
29
29
|
export type CollisionDebugListener = () => void;
|
|
30
|
+
/** @internal */
|
|
30
31
|
export declare function createDefaultCollisionDebugState(): CollisionDebugState;
|
|
32
|
+
/** @internal */
|
|
31
33
|
export declare function anyCollisionLayerOn(layers: CollisionDebugLayers): boolean;
|
|
34
|
+
/** @internal */
|
|
32
35
|
export declare function colliderScanNeeded(layers: CollisionDebugLayers): boolean;
|
|
36
|
+
/** @internal */
|
|
33
37
|
export declare function projectileListenNeeded(layers: CollisionDebugLayers): boolean;
|
|
38
|
+
/** @internal */
|
|
34
39
|
export declare function aimProbeNeeded(layers: CollisionDebugLayers): boolean;
|
|
35
40
|
export interface CollisionDebugController {
|
|
36
41
|
getState(): CollisionDebugState;
|
|
@@ -53,5 +58,6 @@ export interface CollisionDebugController {
|
|
|
53
58
|
/** True when any overlay work is scheduled. */
|
|
54
59
|
isActive(): boolean;
|
|
55
60
|
}
|
|
61
|
+
/** @internal */
|
|
56
62
|
export declare function createCollisionDebugController(initial?: CollisionDebugState): CollisionDebugController;
|
|
57
63
|
export declare const collisionDebug: CollisionDebugController;
|
|
@@ -12,6 +12,7 @@ const DEFAULT_LAYERS = {
|
|
|
12
12
|
muzzles: false,
|
|
13
13
|
aimLaser: false,
|
|
14
14
|
};
|
|
15
|
+
/** @internal */
|
|
15
16
|
export function createDefaultCollisionDebugState() {
|
|
16
17
|
return {
|
|
17
18
|
layers: { ...DEFAULT_LAYERS },
|
|
@@ -21,6 +22,7 @@ export function createDefaultCollisionDebugState() {
|
|
|
21
22
|
projectileTraceLifeMs: 2500,
|
|
22
23
|
};
|
|
23
24
|
}
|
|
25
|
+
/** @internal */
|
|
24
26
|
export function anyCollisionLayerOn(layers) {
|
|
25
27
|
return (layers.hitboxes ||
|
|
26
28
|
layers.bodies ||
|
|
@@ -28,15 +30,19 @@ export function anyCollisionLayerOn(layers) {
|
|
|
28
30
|
layers.muzzles ||
|
|
29
31
|
layers.aimLaser);
|
|
30
32
|
}
|
|
33
|
+
/** @internal */
|
|
31
34
|
export function colliderScanNeeded(layers) {
|
|
32
35
|
return layers.hitboxes || layers.bodies;
|
|
33
36
|
}
|
|
37
|
+
/** @internal */
|
|
34
38
|
export function projectileListenNeeded(layers) {
|
|
35
39
|
return layers.projectiles || layers.muzzles;
|
|
36
40
|
}
|
|
41
|
+
/** @internal */
|
|
37
42
|
export function aimProbeNeeded(layers) {
|
|
38
43
|
return layers.aimLaser;
|
|
39
44
|
}
|
|
45
|
+
/** @internal */
|
|
40
46
|
export function createCollisionDebugController(initial = createDefaultCollisionDebugState()) {
|
|
41
47
|
let state = {
|
|
42
48
|
...initial,
|
|
@@ -62,13 +62,17 @@ export interface CollectDebugShapesInput {
|
|
|
62
62
|
shapes: number;
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
+
/** @internal */
|
|
65
66
|
export declare function classifyAimEndpoint(hit: Pick<SceneRaycastHit, "damageEligible"> | null | undefined): AimEndpointKind;
|
|
67
|
+
/** @internal */
|
|
66
68
|
export declare function pointAlongRay(origin: EntityPosition, direction: EntityPosition, distance: number): EntityPosition;
|
|
69
|
+
/** @internal */
|
|
67
70
|
export declare function shapeWorldCenter(collider: ResolvedCollider, position: EntityPosition, rotationY: number): EntityPosition;
|
|
68
71
|
/**
|
|
69
72
|
* Collects world-space collider wireframe entries for enabled layers.
|
|
70
73
|
* Returns [] and performs no entity/object iteration when both hitbox/body layers are off.
|
|
71
|
-
|
|
74
|
+
* @internal
|
|
75
|
+
*/
|
|
72
76
|
export declare function collectDebugShapes(input: CollectDebugShapesInput): DebugShapeEntry[];
|
|
73
77
|
export interface ComputeAimLaserInput {
|
|
74
78
|
layers: CollisionDebugLayers;
|
|
@@ -87,8 +91,10 @@ export interface ComputeAimLaserInput {
|
|
|
87
91
|
/**
|
|
88
92
|
* Builds the authoritative aim-laser segment using resolveShot + scene raycast
|
|
89
93
|
* (same seam as projectile prediction/settlement). Zero queries when aimLaser is off.
|
|
90
|
-
|
|
94
|
+
* @internal
|
|
95
|
+
*/
|
|
91
96
|
export declare function computeAimLaser(input: ComputeAimLaserInput): AimLaserDebug | null;
|
|
97
|
+
/** @internal */
|
|
92
98
|
export declare function muzzleMarkerFromOrigin(origin: EntityPosition): {
|
|
93
99
|
center: EntityPosition;
|
|
94
100
|
radius: number;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { colliderWorldCenter, defaultEntityColliders, defaultObjectColliders, resolveColliders, } from "@jgengine/core/scene/colliders";
|
|
2
2
|
import { firstImpact, hitsUntilBlocked, } from "@jgengine/core/scene/sceneRaycast";
|
|
3
|
-
import { resolveShot } from "@jgengine/core/combat/shotOrigin";
|
|
3
|
+
import { convergeShot, resolveShot } from "@jgengine/core/combat/shotOrigin";
|
|
4
4
|
import { aimProbeNeeded, colliderScanNeeded } from "./collisionDebug.js";
|
|
5
|
+
/** @internal */
|
|
5
6
|
export function classifyAimEndpoint(hit) {
|
|
6
7
|
if (hit === null || hit === undefined)
|
|
7
8
|
return "miss";
|
|
8
9
|
return hit.damageEligible ? "damage" : "solid";
|
|
9
10
|
}
|
|
11
|
+
/** @internal */
|
|
10
12
|
export function pointAlongRay(origin, direction, distance) {
|
|
11
13
|
return [
|
|
12
14
|
origin[0] + direction[0] * distance,
|
|
@@ -14,6 +16,7 @@ export function pointAlongRay(origin, direction, distance) {
|
|
|
14
16
|
origin[2] + direction[2] * distance,
|
|
15
17
|
];
|
|
16
18
|
}
|
|
19
|
+
/** @internal */
|
|
17
20
|
export function shapeWorldCenter(collider, position, rotationY) {
|
|
18
21
|
return colliderWorldCenter(collider, position, rotationY);
|
|
19
22
|
}
|
|
@@ -58,7 +61,8 @@ function pushResolved(out, targetKind, instanceId, catalogId, position, rotation
|
|
|
58
61
|
/**
|
|
59
62
|
* Collects world-space collider wireframe entries for enabled layers.
|
|
60
63
|
* Returns [] and performs no entity/object iteration when both hitbox/body layers are off.
|
|
61
|
-
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
62
66
|
export function collectDebugShapes(input) {
|
|
63
67
|
if (!colliderScanNeeded(input.layers))
|
|
64
68
|
return [];
|
|
@@ -83,16 +87,30 @@ export function collectDebugShapes(input) {
|
|
|
83
87
|
/**
|
|
84
88
|
* Builds the authoritative aim-laser segment using resolveShot + scene raycast
|
|
85
89
|
* (same seam as projectile prediction/settlement). Zero queries when aimLaser is off.
|
|
86
|
-
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
87
92
|
export function computeAimLaser(input) {
|
|
88
93
|
if (!aimProbeNeeded(input.layers)) {
|
|
89
94
|
return null;
|
|
90
95
|
}
|
|
91
|
-
const
|
|
96
|
+
const shotDeps = {
|
|
92
97
|
positionOf: input.positionOf,
|
|
93
98
|
rotationYOf: input.rotationYOf,
|
|
94
99
|
collidersOf: input.collidersOf,
|
|
95
|
-
}
|
|
100
|
+
};
|
|
101
|
+
const policy = input.originPolicy ?? { kind: "converge" };
|
|
102
|
+
const resolved = policy.kind === "converge"
|
|
103
|
+
? convergeShot(shotDeps, input.from, input.aim, input.maxDistance, (origin, direction) => {
|
|
104
|
+
input.counters !== undefined && (input.counters.queries += 1);
|
|
105
|
+
const sightHits = input.sceneRaycast.raycastAll({
|
|
106
|
+
origin,
|
|
107
|
+
direction,
|
|
108
|
+
maxDistance: input.maxDistance,
|
|
109
|
+
excludeInstanceIds: [input.from],
|
|
110
|
+
});
|
|
111
|
+
return hitsUntilBlocked(sightHits)[0]?.point ?? null;
|
|
112
|
+
}, policy.muzzle)
|
|
113
|
+
: resolveShot(shotDeps, input.from, input.aim, policy);
|
|
96
114
|
if (resolved === null)
|
|
97
115
|
return null;
|
|
98
116
|
input.counters !== undefined && (input.counters.queries += 1);
|
|
@@ -117,6 +135,7 @@ export function computeAimLaser(input) {
|
|
|
117
135
|
queryCount: 1,
|
|
118
136
|
};
|
|
119
137
|
}
|
|
138
|
+
/** @internal */
|
|
120
139
|
export function muzzleMarkerFromOrigin(origin) {
|
|
121
140
|
return { center: origin, radius: 0.08, color: "#ef4444" };
|
|
122
141
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type DevtoolsOverrides } from "@jgengine/core/devtools/devtools";
|
|
2
|
+
export declare function readStoredOverrides(gameName: string): DevtoolsOverrides | null;
|
|
3
|
+
/** @internal */
|
|
4
|
+
export declare function persistDevtoolsOverrides(gameName: string): DevtoolsOverrides;
|
|
5
|
+
/** @internal */
|
|
6
|
+
export declare function applyStoredDevtoolsOverrides(gameName: string): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { devtools, parseOverridesPayload } from "@jgengine/core/devtools/devtools";
|
|
2
|
+
function overridesStorageKey(gameName) {
|
|
3
|
+
return `jg-devtools:${gameName}`;
|
|
4
|
+
}
|
|
5
|
+
export function readStoredOverrides(gameName) {
|
|
6
|
+
try {
|
|
7
|
+
const raw = localStorage.getItem(overridesStorageKey(gameName));
|
|
8
|
+
if (raw === null)
|
|
9
|
+
return null;
|
|
10
|
+
const parsed = parseOverridesPayload(JSON.parse(raw));
|
|
11
|
+
if (parsed.overrides === null) {
|
|
12
|
+
for (const message of parsed.diagnostics)
|
|
13
|
+
console.warn(`[jgengine:devtools] ${message}`);
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
for (const message of parsed.diagnostics)
|
|
17
|
+
console.info(`[jgengine:devtools] ${message}`);
|
|
18
|
+
return parsed.overrides;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** @internal */
|
|
25
|
+
export function persistDevtoolsOverrides(gameName) {
|
|
26
|
+
const overrides = devtools.overrides.export();
|
|
27
|
+
try {
|
|
28
|
+
localStorage.setItem(overridesStorageKey(gameName), JSON.stringify(overrides));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return overrides;
|
|
32
|
+
}
|
|
33
|
+
return overrides;
|
|
34
|
+
}
|
|
35
|
+
/** @internal */
|
|
36
|
+
export function applyStoredDevtoolsOverrides(gameName) {
|
|
37
|
+
const stored = readStoredOverrides(gameName);
|
|
38
|
+
if (stored === null)
|
|
39
|
+
return;
|
|
40
|
+
const result = devtools.overrides.apply(stored);
|
|
41
|
+
if (result.applied === 0 && result.skipped.length === 0)
|
|
42
|
+
return;
|
|
43
|
+
console.info(`[jgengine:devtools] applied ${result.applied} stored override(s) for ${gameName}` +
|
|
44
|
+
(result.skipped.length > 0 ? ` · skipped ${result.skipped.length}` : ""));
|
|
45
|
+
for (const entry of result.skipped) {
|
|
46
|
+
console.warn(`[jgengine:devtools] skipped override ${entry.id}: ${entry.reason}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function ms(value: number): string;
|
|
2
|
+
export declare function StatRow({ name, value, alert }: {
|
|
3
|
+
name: string;
|
|
4
|
+
value: string;
|
|
5
|
+
alert?: boolean;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
export declare function SectionLabel({ children }: {
|
|
8
|
+
children: string;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export function ms(value) {
|
|
3
|
+
return `${value.toFixed(1)}ms`;
|
|
4
|
+
}
|
|
5
|
+
export function StatRow({ name, value, alert }) {
|
|
6
|
+
return (_jsxs("div", { className: "flex items-baseline justify-between gap-3", children: [_jsx("span", { className: "text-neutral-400", children: name }), _jsx("span", { className: `font-mono tabular-nums ${alert === true ? "text-red-400" : "text-neutral-100"}`, children: value })] }));
|
|
7
|
+
}
|
|
8
|
+
export function SectionLabel({ children }) {
|
|
9
|
+
return _jsx("div", { className: "text-[9px] font-semibold uppercase tracking-[0.14em] text-neutral-500", children: children });
|
|
10
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { devtools, LONG_FRAME_MS } from "@jgengine/core/devtools/devtools";
|
|
2
|
+
import { ms } from "./panelAtoms.js";
|
|
3
|
+
const CULPRIT_HINTS = {
|
|
4
|
+
"outside-sim": "Cost is outside the sim driver — three.js render, React commit, GPU, GC, or tab throttle. Check the render sample: high draw calls/triangles mean scene cost (instancing, culling, terrain density); moderate counts with slow frames mean fill cost (dpr, AO/DOF passes, bloom) — try Quality low/medium.",
|
|
5
|
+
sim: "Sim is slow but no named phase stands out — wrap hot onTick work with measure(\"name\", fn).",
|
|
6
|
+
onTick: "Game loop.onTick is the hotspot — profile inside it with measure(\"physics\"|\"ai\"|…, fn).",
|
|
7
|
+
pose: "Shell movement / collision / voxel step is expensive.",
|
|
8
|
+
actions: "Input → command dispatch / hotbar / interact is expensive.",
|
|
9
|
+
presence: "Multiplayer pose sync is expensive.",
|
|
10
|
+
pickup: "Auto-pickup nearest scan is expensive.",
|
|
11
|
+
"time+input": "Clock advance or input publish is unexpectedly heavy.",
|
|
12
|
+
};
|
|
13
|
+
export function diagnose(frame, longs) {
|
|
14
|
+
if (frame.fps >= 55 && frame.longFrames === 0 && longs.length === 0)
|
|
15
|
+
return null;
|
|
16
|
+
const recent = longs.slice(-5);
|
|
17
|
+
const culpritCounts = new Map();
|
|
18
|
+
for (const event of recent) {
|
|
19
|
+
culpritCounts.set(event.culprit, (culpritCounts.get(event.culprit) ?? 0) + 1);
|
|
20
|
+
}
|
|
21
|
+
let topCulprit = null;
|
|
22
|
+
let topCount = 0;
|
|
23
|
+
for (const [name, count] of culpritCounts) {
|
|
24
|
+
if (count > topCount) {
|
|
25
|
+
topCulprit = name;
|
|
26
|
+
topCount = count;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (topCulprit === null && frame.phases[0] !== undefined && frame.avgSimMs > 8) {
|
|
30
|
+
topCulprit = frame.phases[0].name;
|
|
31
|
+
}
|
|
32
|
+
if (topCulprit === null && frame.avgOutsideMs > frame.avgSimMs && frame.avgOutsideMs > 4) {
|
|
33
|
+
topCulprit = "outside-sim";
|
|
34
|
+
}
|
|
35
|
+
if (topCulprit === null) {
|
|
36
|
+
if (frame.p95FrameMs > LONG_FRAME_MS)
|
|
37
|
+
return `p95 ${ms(frame.p95FrameMs)} — hitching without a clear phase; check long-frame log as it fills.`;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const hint = CULPRIT_HINTS[topCulprit] ?? `Hotspot “${topCulprit}” — dig into that phase with measure() or reduce its work.`;
|
|
41
|
+
const countText = topCount > 0 ? ` (${topCount}/${recent.length || topCount} recent long frames)` : "";
|
|
42
|
+
return `${topCulprit}${countText}: ${hint}`;
|
|
43
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Component, type ErrorInfo, type ReactNode } from "react";
|
|
2
|
+
export interface RuntimeDiagnostic {
|
|
3
|
+
id: number;
|
|
4
|
+
phase: string;
|
|
5
|
+
message: string;
|
|
6
|
+
stack?: string;
|
|
7
|
+
componentStack?: string;
|
|
8
|
+
capturedAt: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function logRuntimeError(error: unknown, phase: string, componentStack?: string): Omit<RuntimeDiagnostic, "id">;
|
|
11
|
+
export declare class GameUiErrorBoundary extends Component<{
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
onRuntimeError: (error: unknown, phase: string, componentStack?: string) => void;
|
|
14
|
+
}, {
|
|
15
|
+
failed: boolean;
|
|
16
|
+
}> {
|
|
17
|
+
state: {
|
|
18
|
+
failed: boolean;
|
|
19
|
+
};
|
|
20
|
+
static getDerivedStateFromError(): {
|
|
21
|
+
failed: boolean;
|
|
22
|
+
};
|
|
23
|
+
componentDidCatch(error: unknown, info: ErrorInfo): void;
|
|
24
|
+
render(): ReactNode;
|
|
25
|
+
}
|
|
26
|
+
/** Standalone error footer text (page, browser, engine) shared by every error surface. */
|
|
27
|
+
export declare function errorReportContext(): string;
|
|
28
|
+
/** "Copy error" + "File issue" buttons for any error surface. `report` is the full text to copy / prefill. */
|
|
29
|
+
export declare function ErrorReportActions({ report, issueTitle }: {
|
|
30
|
+
report: string;
|
|
31
|
+
issueTitle: string;
|
|
32
|
+
}): import("react").JSX.Element;
|
|
33
|
+
export declare function DiagnosticOverlay({ diagnostics, gameName }: {
|
|
34
|
+
diagnostics: RuntimeDiagnostic[];
|
|
35
|
+
gameName: string;
|
|
36
|
+
}): import("react").JSX.Element | null;
|