@volter/editor-game 0.5.66 → 0.5.67
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/contributions/audio-unlock.service.ts +2 -2
- package/contributions/autoplay.service.ts +1 -1
- package/contributions/bridge.command.ts +3 -3
- package/contributions/canvas/component-board.service.ts +16 -0
- package/contributions/canvas/design-time-mount.service.ts +45 -0
- package/contributions/canvas-story-capture.service.ts +12 -0
- package/contributions/edit-mode-audio.service.ts +2 -2
- package/contributions/edit-mode-networking.service.ts +1 -1
- package/contributions/gameplay.command.ts +4 -4
- package/contributions/generation.service.ts +1 -1
- package/contributions/godot.style.ts +26 -6
- package/contributions/godot.view.ts +6 -0
- package/contributions/ingest.service.ts +2 -2
- package/contributions/instances.command.ts +1 -1
- package/contributions/navmesh.menu.ts +1 -1
- package/contributions/network-observer.service.ts +14 -0
- package/contributions/play.command.ts +10 -4
- package/contributions/react/component-board.service.ts +1 -1
- package/contributions/react/design-time-mount.service.ts +1 -1
- package/contributions/scene-document.service.ts +3 -3
- package/contributions/state-watch.menu.ts +1 -1
- package/contributions/state-watch.utility.tsx +1 -1
- package/contributions/team-playtest.service.ts +4 -4
- package/contributions/three/component-board.service.ts +1 -1
- package/contributions/three/component-verbs.command.ts +6 -6
- package/contributions/three/three-authoring.service.ts +8 -5
- package/contributions/three-story-capture.service.ts +12 -0
- package/contributions/unity.style.ts +17 -7
- package/contributions/unity.view.ts +6 -0
- package/contributions/unreal.style.ts +10 -2
- package/contributions/unreal.view.ts +5 -0
- package/package.json +20 -10
- package/src/asset-budget/AssetBudgetPanel.tsx +1 -1
- package/src/asset-budget/asset-budget-model.ts +1 -1
- package/src/audio/AudioDebuggerPanel.tsx +1 -1
- package/src/bridge/dispatch.ts +14 -14
- package/src/bridge/live-frames.ts +1 -1
- package/src/bridge/screenshot.ts +3 -3
- package/src/build/BuildProfilesPanel.tsx +3 -3
- package/src/canvas/canvas-board/CanvasBoardDocument.tsx +676 -0
- package/src/canvas/canvas-board/canvas-board-model.ts +407 -0
- package/src/canvas/canvas-board/canvas-component-board.ts +56 -0
- package/src/canvas/canvas-design-mount.ts +524 -0
- package/src/canvas/design-time-canvas-mount.ts +79 -0
- package/src/coverage/live-authoring-surface.ts +5 -5
- package/src/coverage/live-project-verbs.ts +1 -1
- package/src/coverage/native-system-coverage.ts +5 -5
- package/src/coverage/root-coverage.ts +1 -1
- package/src/coverage/session-coverage.ts +3 -3
- package/src/design-system-stories/ApplicationChrome.stories.tsx +5 -5
- package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +7 -7
- package/src/edit-mode/edit-mode-audio.ts +4 -4
- package/src/edit-mode/edit-mode-networking.ts +2 -2
- package/src/game-document/GameCaptureFrameButton.tsx +1 -1
- package/src/game-document/GameDocument.tsx +2 -2
- package/src/game-document/GamePanel.tsx +4 -4
- package/src/game-document/InstanceInspectorPicker.tsx +2 -2
- package/src/game-document/crowd-debug.ts +1 -1
- package/src/game-document/device-preview.ts +10 -11
- package/src/game-document/physics-debug.ts +1 -1
- package/src/generation/GenerationActivity.tsx +3 -3
- package/src/generation/generation-documents.tsx +5 -5
- package/src/generation/generation-jobs.ts +1 -1
- package/src/host/adapter-runtime-bindings.ts +96 -5
- package/src/host/authoring/babylon-authoring-adapter.ts +6 -6
- package/src/host/authoring/gesture-persist.ts +1 -1
- package/src/host/authoring/ingest-data-writer.ts +1 -1
- package/src/host/authoring/ingest-source-persistence.ts +4 -4
- package/src/host/authoring/mounted-authoring.ts +4 -4
- package/src/host/authoring/phaser-live-authoring-adapter.ts +4 -4
- package/src/host/authoring/pixi-authoring-adapter.ts +40 -15
- package/src/host/authoring/pixi-creation-site-write-target.ts +2 -2
- package/src/host/authoring/pixi-live-write-target.ts +4 -4
- package/src/host/authoring/pixi-source-identity.ts +3 -3
- package/src/host/authoring/pixi-source-write-target.ts +1614 -0
- package/src/host/authoring/pixi-still-presentation.ts +1 -1
- package/src/host/authoring/pixi-structure-history.ts +2 -2
- package/src/host/authoring/pixi-transform-channels.ts +16 -14
- package/src/host/authoring/source-persistence-backend.ts +3 -3
- package/src/host/authoring/struct-write-pipe.ts +1 -1
- package/src/host/binding-resolver.ts +8 -9
- package/src/host/browser-transpile.ts +1 -1
- package/src/host/canvas-entry-runtime.ts +58 -47
- package/src/host/canvas-preview-frames.ts +482 -0
- package/src/host/components/CameraAuthoringOverlay.tsx +1 -1
- package/src/host/components/HeaderTelemetry.tsx +4 -4
- package/src/host/components/PixiIsolationSceneContent.tsx +10 -10
- package/src/host/components/ThreeIsolationSceneContent.tsx +3 -3
- package/src/host/components/frame-debugger-model.ts +2 -2
- package/src/host/components/header-telemetry-model.ts +2 -2
- package/src/host/components/scene-document.tsx +14 -14
- package/src/host/components/utility-view-state.ts +1 -1
- package/src/host/components/world-root-stage-binding.tsx +12 -12
- package/src/host/components/world-root-stage.ts +70 -49
- package/src/host/coverage/system-adapter-coverage.ts +3 -4
- package/src/host/design-system-stories/fixtures/editor-runtime.tsx +9 -11
- package/src/host/document-preview-three.ts +1 -1
- package/src/host/entry-adjudication.ts +6 -6
- package/src/host/game-css-scope-transform.ts +4 -0
- package/src/host/game-realm-page.ts +1 -1
- package/src/host/gameplay-export.ts +25 -14
- package/src/host/gameplay-recording.ts +5 -5
- package/src/host/gated-globals.ts +2 -2
- package/src/host/history/json-history-resource.ts +1 -1
- package/src/host/projection/pixi.ts +24 -2
- package/src/host/r3f-entry-runtime.ts +65 -34
- package/src/host/react-mount-runtime.ts +7 -48
- package/src/host/realm-services.ts +1 -1
- package/src/host/roots/canvas-root.tsx +361 -0
- package/src/host/roots/r3f-root.tsx +473 -0
- package/src/host/roots/react-root.ts +9 -43
- package/src/host/served-bundle-runtime-modules.ts +1 -19
- package/src/host/server-log-bridge.ts +2 -2
- package/src/host/stories/mounted-story-viewport-source.ts +1 -1
- package/src/host/stories/pixi-story-model.ts +30 -0
- package/src/host/stories/story-media-captures.ts +46 -0
- package/src/host/stories/story-media-presence.ts +3 -3
- package/src/host/stories/story-pixi-preview.ts +408 -0
- package/src/host/stories/story-three-preview.ts +806 -0
- package/src/host/stories/three-story-captures.ts +35 -0
- package/src/host/story-three-preview-runtime.ts +56 -0
- package/src/host/use-active-performance-source.ts +2 -2
- package/src/host/viewport-pose-memory.ts +1 -1
- package/src/host/viewport-root-presentation.ts +6 -5
- package/src/ingest/active-ingest.ts +1 -1
- package/src/ingest/authoring/ingest-dom-surface-authoring.ts +4 -4
- package/src/ingest/authoring/ingest-root-adapter.ts +8 -8
- package/src/ingest/deferred-ingest-play.ts +6 -6
- package/src/ingest/discovery-public-ingest.ts +2 -2
- package/src/ingest/ingest-boot-viewport.ts +2 -2
- package/src/ingest/ingest-canvas-scene-document.tsx +8 -8
- package/src/ingest/ingest-canvas-scene.ts +3 -3
- package/src/ingest/ingest-evidence-hook.ts +1 -1
- package/src/ingest/ingest-frame-snapshot.ts +1 -1
- package/src/ingest/ingest-play-control.ts +1 -1
- package/src/ingest/ingest-render-debug.ts +10 -10
- package/src/ingest/ingest-siblings.ts +13 -25
- package/src/ingest/module-mode.ts +14 -14
- package/src/ingest/mount-canvas-ingest-root.ts +21 -21
- package/src/ingest/mount-coverage.ts +2 -2
- package/src/ingest/mount-dom-ingest-root.ts +11 -11
- package/src/ingest/mount-ingest-root.ts +8 -8
- package/src/ingest/mount-three-ingest-root.ts +8 -8
- package/src/ingest/resolve-canvas.ts +1 -1
- package/src/ingest/served-html-boot.ts +1 -1
- package/src/ingest/surface-canvas.ts +1 -1
- package/src/ingest/unmount-ingest-root.ts +5 -5
- package/src/navmesh/navmesh-handler.ts +24 -16
- package/src/network/NetworkInspectorPanel.tsx +449 -14
- package/src/network/network-inspector-model.ts +14 -2
- package/src/play/play-log-events.ts +1 -1
- package/src/play/play-mode.ts +76 -133
- package/src/play/play-recording.ts +1 -1
- package/src/play/react-play-live-authoring.ts +3 -3
- package/src/play/run-selection.ts +93 -0
- package/src/play-bar/PlayBar.tsx +20 -39
- package/src/profiler/FrameDebuggerPanel.tsx +1 -1
- package/src/profiler/PerformancePanel.tsx +3 -3
- package/src/react/design-time-react-mount.ts +23 -65
- package/src/react/dom-authoring-adapter.ts +9 -9
- package/src/react/react-inspector-section.tsx +5 -5
- package/src/react/react-world-authoring-adapter.ts +11 -11
- package/src/react/story-documents/story-documents.tsx +9 -9
- package/src/react/ui-board-document.tsx +7 -7
- package/src/react/ui-component-board.ts +2 -2
- package/src/runtime/adapter/audio-meter.ts +21 -0
- package/src/runtime/adapter/first-party-audio-system.ts +230 -0
- package/src/runtime/adapter/ingest/contract-debug-adapter.ts +114 -0
- package/src/runtime/adapter/ingest/contract-system-adapters.ts +256 -0
- package/src/runtime/adapter/ingest/merge-debug-adapters.ts +197 -0
- package/src/runtime/adapter/ingest/observation-debug-adapter.ts +162 -0
- package/src/runtime/adapter/ingest/upstream-pin.ts +51 -0
- package/src/runtime/adapter/native-debug-module.ts +498 -0
- package/src/runtime/audio/bus-mixer.ts +161 -0
- package/src/runtime/audio/pose-guard.ts +80 -0
- package/src/runtime/core/frame-pacing.ts +126 -0
- package/src/runtime/core/game-loop.ts +225 -0
- package/src/runtime/core/game-scoped-slot.ts +28 -0
- package/src/runtime/core/seeded-random.ts +162 -0
- package/src/runtime/core/sim-clock.ts +391 -0
- package/src/runtime/core/system-runner.ts +269 -0
- package/src/runtime/core/types.ts +104 -0
- package/src/runtime/create-runtime.ts +1128 -0
- package/src/runtime/debug-bridge.ts +570 -0
- package/src/runtime/debug-registry.ts +899 -0
- package/src/runtime/dev/chrome-trace.ts +153 -0
- package/src/runtime/dev/instruments.ts +403 -0
- package/src/runtime/dev/logger.ts +119 -0
- package/src/runtime/dev/performance-profiler.ts +367 -0
- package/src/runtime/dev/register-render-vitals.ts +276 -0
- package/src/runtime/dev/render-census.ts +354 -0
- package/src/runtime/dev/render-debug-adapter.ts +218 -0
- package/src/runtime/dev/render-memory.ts +226 -0
- package/src/runtime/dev/render-vitals.ts +338 -0
- package/src/runtime/dev/static-batch-advisor.ts +188 -0
- package/src/runtime/dev/webgl-frame-capture.ts +366 -0
- package/src/runtime/dev/webgl-gpu-timer.ts +53 -0
- package/src/runtime/dev-build.ts +47 -0
- package/src/runtime/game.ts +1636 -0
- package/src/runtime/gameplay-rng-trap.ts +135 -0
- package/src/runtime/host-context.ts +64 -0
- package/src/runtime/input-router.ts +169 -0
- package/src/runtime/mount-manifest.ts +480 -0
- package/src/runtime/pixi/authoring.ts +706 -0
- package/src/runtime/pixi/ingest.ts +116 -0
- package/src/runtime/pixi/physics-registry.ts +49 -0
- package/src/runtime/pixi/render-pass-bracket.ts +117 -0
- package/src/runtime/pixi/scene-capture.ts +179 -0
- package/src/runtime/pixi/system-adapters.ts +69 -0
- package/src/runtime/playtest.ts +22 -0
- package/src/runtime/presentation.ts +141 -0
- package/src/runtime/render-control.ts +642 -0
- package/src/runtime/render-seed.ts +77 -0
- package/src/runtime/run-ticks-settled.ts +73 -0
- package/src/runtime/setup/setup-audio.ts +72 -0
- package/src/services/audio-pose-guard.ts +2 -2
- package/src/services/game-audio.ts +152 -0
- package/src/services/game-network.ts +657 -0
- package/src/services/game-physics.ts +334 -0
- package/src/state-watch/StateWatchPanel.tsx +1 -1
- package/src/three/authoring/camera-runtime-inspector-section.tsx +3 -2
- package/src/three/authoring/constraint-inspector-section.tsx +6 -5
- package/src/three/authoring/model-asset-inspector-section.tsx +7 -6
- package/src/three/authoring/oid-source-persistence.ts +7 -7
- package/src/three/authoring/r3f-design-session.ts +58 -54
- package/src/three/authoring/r3f-source-authoring-adapter.ts +95 -91
- package/src/three/authoring/reflection-probe-inspector-section.tsx +3 -2
- package/src/three/authoring/three-authoring-adapter.ts +29 -29
- package/src/three/component-verbs/extract-menu.ts +7 -6
- package/src/three/component-verbs/fork-menu.ts +7 -6
- package/src/three/component-verbs/internals-menu.ts +2 -2
- package/src/three/story-documents/three-story-documents.tsx +13 -13
- package/src/three/three-board/ThreeBoardDocument.tsx +13 -12
- package/src/three/three-board/board-scene.ts +6 -6
- package/src/three/three-board/three-component-board.ts +2 -2
- package/src/services/game-audio-unlock.ts +0 -48
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
* Degradation ladder rendered honestly (W3a contract, first system-adapter
|
|
15
15
|
* consumer): no adapter → the register-an-adapter notice; an adapter missing
|
|
16
16
|
* an optional capability → that section says "not provided by this adapter";
|
|
17
|
-
* nothing is ever fabricated.
|
|
18
|
-
* the
|
|
17
|
+
* nothing is ever fabricated. The panel reads only the `NetworkingAdapter`
|
|
18
|
+
* interface: the game's own, or the editor's observer of its Colyseus rooms
|
|
19
|
+
* (`services/game-network.ts`).
|
|
19
20
|
*
|
|
20
21
|
* Mounted as the `network` workspace utility (drawer tab beside
|
|
21
22
|
* Console/Profiler, `core-utilities.tsx`), available only while a
|
|
@@ -37,6 +38,8 @@ import type {
|
|
|
37
38
|
ConnectionState,
|
|
38
39
|
NetConditioning,
|
|
39
40
|
NetMessageEvent,
|
|
41
|
+
NetServerInspection,
|
|
42
|
+
NetTypeTraffic,
|
|
40
43
|
NetworkingAdapter,
|
|
41
44
|
} from '@volter/editor-project/adapter';
|
|
42
45
|
import { memo, useEffect, useReducer, useRef, useState } from 'react';
|
|
@@ -100,8 +103,43 @@ function formatLeaf(value: unknown): string {
|
|
|
100
103
|
* are NOT mounted: a fresh snapshot every poll re-renders only the visible
|
|
101
104
|
* rows, never a collapsed subtree.
|
|
102
105
|
*/
|
|
103
|
-
|
|
106
|
+
type StateEdit = (path: readonly (string | number)[], value: unknown) => Promise<void>;
|
|
107
|
+
type StateDelete = (path: readonly (string | number)[]) => Promise<void>;
|
|
108
|
+
type StateType = (path: readonly (string | number)[]) => string | null;
|
|
109
|
+
|
|
110
|
+
/** A field's replicated type beside its name, dim (Godot's Replication panel's column). */
|
|
111
|
+
function TypeTag({ type }: { type: string | null | undefined }) {
|
|
112
|
+
if (!type) return null;
|
|
113
|
+
return (
|
|
114
|
+
<span data-testid="net-tree-type" style={{ color: themeVars.content.dim, marginLeft: 6 }}>
|
|
115
|
+
{type}
|
|
116
|
+
</span>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function StateTreeNode({
|
|
121
|
+
name,
|
|
122
|
+
value,
|
|
123
|
+
depth,
|
|
124
|
+
path = [],
|
|
125
|
+
edit,
|
|
126
|
+
remove,
|
|
127
|
+
typeOf,
|
|
128
|
+
}: {
|
|
129
|
+
name: string;
|
|
130
|
+
value: unknown;
|
|
131
|
+
depth: number;
|
|
132
|
+
path?: readonly string[];
|
|
133
|
+
/** The server's state edit (Monitor's), when the adapter offers one: leaves become editable. */
|
|
134
|
+
edit?: StateEdit | undefined;
|
|
135
|
+
/** The server's state delete (Monitor's), when offered: each key gets a remove control. */
|
|
136
|
+
remove?: StateDelete | undefined;
|
|
137
|
+
/** The schema's declared type for a path, when the adapter reads one. */
|
|
138
|
+
typeOf?: StateType | undefined;
|
|
139
|
+
}) {
|
|
104
140
|
const [expanded, setExpanded] = useState(depth === 0);
|
|
141
|
+
const [draft, setDraft] = useState<string | null>(null);
|
|
142
|
+
const [failed, setFailed] = useState<string | null>(null);
|
|
105
143
|
const rowStyle: React.CSSProperties = {
|
|
106
144
|
...MONO,
|
|
107
145
|
paddingLeft: 8 + depth * 14,
|
|
@@ -110,11 +148,71 @@ function StateTreeNode({ name, value, depth }: { name: string; value: unknown; d
|
|
|
110
148
|
};
|
|
111
149
|
|
|
112
150
|
if (!isBranch(value)) {
|
|
151
|
+
const editable = edit && (typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean');
|
|
152
|
+
const commit = (next: unknown) => {
|
|
153
|
+
setDraft(null);
|
|
154
|
+
edit?.(path, next).then(
|
|
155
|
+
() => setFailed(null),
|
|
156
|
+
(caught: unknown) => setFailed(caught instanceof Error ? caught.message : String(caught)),
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
const parse = (text: string): unknown => {
|
|
160
|
+
if (typeof value === 'number') {
|
|
161
|
+
const number = Number(text);
|
|
162
|
+
return Number.isFinite(number) ? number : value;
|
|
163
|
+
}
|
|
164
|
+
return text;
|
|
165
|
+
};
|
|
113
166
|
return (
|
|
114
|
-
<div style={rowStyle} data-testid="net-tree-leaf">
|
|
167
|
+
<div style={rowStyle} data-testid="net-tree-leaf" data-path={path.join('.')}>
|
|
115
168
|
<span style={{ color: themeVars.content.primary }}>{name}</span>
|
|
116
169
|
<span style={{ color: themeVars.content.muted }}>: </span>
|
|
117
|
-
|
|
170
|
+
{draft !== null ? (
|
|
171
|
+
<TextInput
|
|
172
|
+
data-testid="net-tree-edit"
|
|
173
|
+
autoFocus
|
|
174
|
+
value={draft}
|
|
175
|
+
onChange={(event) => setDraft(event.target.value)}
|
|
176
|
+
onKeyDown={(event) => {
|
|
177
|
+
if (event.key === 'Enter') commit(parse(draft));
|
|
178
|
+
if (event.key === 'Escape') setDraft(null);
|
|
179
|
+
}}
|
|
180
|
+
onBlur={() => setDraft(null)}
|
|
181
|
+
style={{ width: 120 }}
|
|
182
|
+
/>
|
|
183
|
+
) : (
|
|
184
|
+
<span
|
|
185
|
+
data-testid={editable ? 'net-tree-value' : undefined}
|
|
186
|
+
title={editable ? (typeof value === 'boolean' ? 'Click to toggle on the server' : 'Click to edit on the server') : undefined}
|
|
187
|
+
style={{ color: themeVars.content.primary, cursor: editable ? 'text' : undefined }}
|
|
188
|
+
onClick={
|
|
189
|
+
editable
|
|
190
|
+
? () => (typeof value === 'boolean' ? commit(!value) : setDraft(String(value)))
|
|
191
|
+
: undefined
|
|
192
|
+
}
|
|
193
|
+
>
|
|
194
|
+
{formatLeaf(value)}
|
|
195
|
+
</span>
|
|
196
|
+
)}
|
|
197
|
+
<TypeTag type={path.length > 0 ? typeOf?.(path) : null} />
|
|
198
|
+
{remove && path.length > 0 ? (
|
|
199
|
+
<button
|
|
200
|
+
type="button"
|
|
201
|
+
data-testid="net-tree-delete"
|
|
202
|
+
title="Delete this key on the server"
|
|
203
|
+
aria-label={`Delete ${path.join('.')} on the server`}
|
|
204
|
+
onClick={() =>
|
|
205
|
+
remove(path).then(
|
|
206
|
+
() => setFailed(null),
|
|
207
|
+
(caught: unknown) => setFailed(caught instanceof Error ? caught.message : String(caught)),
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
style={{ marginLeft: 8, border: 0, background: 'transparent', color: themeVars.content.muted, cursor: 'pointer' }}
|
|
211
|
+
>
|
|
212
|
+
×
|
|
213
|
+
</button>
|
|
214
|
+
) : null}
|
|
215
|
+
{failed ? <span style={{ color: themeVars.semantic.danger }}> {failed}</span> : null}
|
|
118
216
|
</div>
|
|
119
217
|
);
|
|
120
218
|
}
|
|
@@ -136,10 +234,39 @@ function StateTreeNode({ name, value, depth }: { name: string; value: unknown; d
|
|
|
136
234
|
{' '}
|
|
137
235
|
{Array.isArray(value) ? `[${entries.length}]` : `{${entries.length}}`}
|
|
138
236
|
</span>
|
|
237
|
+
<TypeTag type={path.length > 0 ? typeOf?.(path) : null} />
|
|
238
|
+
{failed ? <span style={{ color: themeVars.semantic.danger }}> {failed}</span> : null}
|
|
239
|
+
{remove && path.length > 0 ? (
|
|
240
|
+
<button
|
|
241
|
+
type="button"
|
|
242
|
+
data-testid="net-tree-delete"
|
|
243
|
+
title="Delete this key on the server"
|
|
244
|
+
aria-label={`Delete ${path.join('.')} on the server`}
|
|
245
|
+
onClick={(event) => {
|
|
246
|
+
event.stopPropagation();
|
|
247
|
+
remove(path).then(
|
|
248
|
+
() => setFailed(null),
|
|
249
|
+
(caught: unknown) => setFailed(caught instanceof Error ? caught.message : String(caught)),
|
|
250
|
+
);
|
|
251
|
+
}}
|
|
252
|
+
style={{ marginLeft: 8, border: 0, background: 'transparent', color: themeVars.content.muted, cursor: 'pointer' }}
|
|
253
|
+
>
|
|
254
|
+
×
|
|
255
|
+
</button>
|
|
256
|
+
) : null}
|
|
139
257
|
</div>
|
|
140
258
|
{expanded &&
|
|
141
259
|
entries.map(([key, child]) => (
|
|
142
|
-
<StateTreeNode
|
|
260
|
+
<StateTreeNode
|
|
261
|
+
key={key}
|
|
262
|
+
name={key}
|
|
263
|
+
value={child}
|
|
264
|
+
depth={depth + 1}
|
|
265
|
+
path={depth === 0 ? [key] : [...path, key]}
|
|
266
|
+
edit={edit}
|
|
267
|
+
remove={remove}
|
|
268
|
+
typeOf={typeOf}
|
|
269
|
+
/>
|
|
143
270
|
))}
|
|
144
271
|
</div>
|
|
145
272
|
);
|
|
@@ -252,6 +379,7 @@ function ConditionerControls({ adapter }: { adapter: NetworkingAdapter }) {
|
|
|
252
379
|
const [cond, setCond] = useState<NetConditioning>(
|
|
253
380
|
() => adapter.getConditioning?.() ?? { latencyMs: 0, jitterMs: 0, packetLoss: 0 },
|
|
254
381
|
);
|
|
382
|
+
const limits = adapter.getConditioningLimits?.() ?? {};
|
|
255
383
|
const apply = (next: NetConditioning) => {
|
|
256
384
|
setCond(next);
|
|
257
385
|
adapter.setConditioning?.(next);
|
|
@@ -281,13 +409,23 @@ function ConditionerControls({ adapter }: { adapter: NetworkingAdapter }) {
|
|
|
281
409
|
testId="net-cond-jitter"
|
|
282
410
|
onChange={(v) => apply({ ...cond, jitterMs: Math.max(0, v) })}
|
|
283
411
|
/>
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
412
|
+
{limits.packetLoss ? (
|
|
413
|
+
<span
|
|
414
|
+
data-testid="net-cond-loss-absent"
|
|
415
|
+
title={limits.packetLoss}
|
|
416
|
+
style={{ fontSize: fontSizeVar.sm, color: themeVars.content.muted }}
|
|
417
|
+
>
|
|
418
|
+
loss: not simulated
|
|
419
|
+
</span>
|
|
420
|
+
) : (
|
|
421
|
+
<NumberInput
|
|
422
|
+
label="loss %"
|
|
423
|
+
value={cond.packetLoss * 100}
|
|
424
|
+
step={5}
|
|
425
|
+
testId="net-cond-loss"
|
|
426
|
+
onChange={(v) => apply({ ...cond, packetLoss: Math.min(1, Math.max(0, v / 100)) })}
|
|
427
|
+
/>
|
|
428
|
+
)}
|
|
291
429
|
</div>
|
|
292
430
|
);
|
|
293
431
|
}
|
|
@@ -445,6 +583,8 @@ export function NetworkInspectorPanel() {
|
|
|
445
583
|
const adapterRef = useRef<NetworkingAdapter | null>(null);
|
|
446
584
|
const [paused, setPaused] = useState(false);
|
|
447
585
|
const [filter, setFilter] = useState('');
|
|
586
|
+
// The one message draft: sent as this client, or from the server to one client or all.
|
|
587
|
+
const [draft, setDraft] = useState<MessageDraft>({ type: '', payload: '' });
|
|
448
588
|
|
|
449
589
|
useEffect(() => {
|
|
450
590
|
const syncAdapter = () => {
|
|
@@ -550,6 +690,17 @@ export function NetworkInspectorPanel() {
|
|
|
550
690
|
</AbsentNote>
|
|
551
691
|
)}
|
|
552
692
|
|
|
693
|
+
{caps.server ? (
|
|
694
|
+
<ServerView
|
|
695
|
+
adapter={adapter}
|
|
696
|
+
ownSession={view.roomInfo?.sessionId ?? null}
|
|
697
|
+
ownRoom={view.roomInfo?.roomId ?? null}
|
|
698
|
+
draft={draft}
|
|
699
|
+
/>
|
|
700
|
+
) : null}
|
|
701
|
+
{caps.traffic ? <TrafficTable rows={adapter.getTrafficByType?.() ?? []} /> : null}
|
|
702
|
+
{caps.send ? <SendControls adapter={adapter} ping={caps.ping} draft={draft} setDraft={setDraft} /> : null}
|
|
703
|
+
|
|
553
704
|
{/* Body: state tree | message log */}
|
|
554
705
|
<div style={{ flex: 1, minHeight: 0, display: 'flex' }}>
|
|
555
706
|
<div
|
|
@@ -564,7 +715,14 @@ export function NetworkInspectorPanel() {
|
|
|
564
715
|
>
|
|
565
716
|
{caps.stateTree ? (
|
|
566
717
|
snapshot != null ? (
|
|
567
|
-
<StateTreeNode
|
|
718
|
+
<StateTreeNode
|
|
719
|
+
name="state"
|
|
720
|
+
value={snapshot}
|
|
721
|
+
depth={0}
|
|
722
|
+
edit={adapter.editServerState ? (path, next) => adapter.editServerState!(path, next) : undefined}
|
|
723
|
+
remove={adapter.deleteServerState ? (path) => adapter.deleteServerState!(path) : undefined}
|
|
724
|
+
typeOf={adapter.stateFieldType ? (path) => adapter.stateFieldType!(path) : undefined}
|
|
725
|
+
/>
|
|
568
726
|
) : (
|
|
569
727
|
<AbsentNote>Not connected — no replicated state to show.</AbsentNote>
|
|
570
728
|
)
|
|
@@ -606,7 +764,9 @@ export function NetworkInspectorPanel() {
|
|
|
606
764
|
variant="ghost"
|
|
607
765
|
data-testid="net-log-clear"
|
|
608
766
|
onClick={() => {
|
|
767
|
+
// Godot's Clear resets its tables too: the log and the traffic totals.
|
|
609
768
|
logRef.current.clear();
|
|
769
|
+
adapter.clearTraffic?.();
|
|
610
770
|
force();
|
|
611
771
|
}}
|
|
612
772
|
>
|
|
@@ -642,3 +802,278 @@ export function NetworkInspectorPanel() {
|
|
|
642
802
|
</div>
|
|
643
803
|
);
|
|
644
804
|
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Traffic per message type, both directions — Godot's network profiler reads a game's RPCs and
|
|
808
|
+
* its synchronizers as two tables of counts and sizes; a Colyseus room's are its messages and its
|
|
809
|
+
* state sync, which arrive here as rows of one table (`state`, `patch`, and each message type).
|
|
810
|
+
*/
|
|
811
|
+
function TrafficTable({ rows }: { rows: readonly NetTypeTraffic[] }) {
|
|
812
|
+
const cell = { padding: `0 ${spaceVar[3]}`, textAlign: 'right' as const, whiteSpace: 'nowrap' as const };
|
|
813
|
+
const bytes = (value: number) => (value >= 1024 ? `${(value / 1024).toFixed(1)} KiB` : `${value} B`);
|
|
814
|
+
return (
|
|
815
|
+
<div
|
|
816
|
+
data-testid="net-traffic"
|
|
817
|
+
style={{ maxHeight: 132, overflow: 'auto', borderBottom: `1px solid ${themeVars.boundary.default}` }}
|
|
818
|
+
>
|
|
819
|
+
{rows.length === 0 ? (
|
|
820
|
+
<AbsentNote>No traffic yet.</AbsentNote>
|
|
821
|
+
) : (
|
|
822
|
+
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: fontSizeVar.sm, ...MONO }}>
|
|
823
|
+
<thead>
|
|
824
|
+
<tr style={{ color: themeVars.content.muted }}>
|
|
825
|
+
<th style={{ ...cell, textAlign: 'left' }}>Type</th>
|
|
826
|
+
<th style={cell}>In</th>
|
|
827
|
+
<th style={cell}>Bytes in</th>
|
|
828
|
+
<th style={cell}>Out</th>
|
|
829
|
+
<th style={cell}>Bytes out</th>
|
|
830
|
+
</tr>
|
|
831
|
+
</thead>
|
|
832
|
+
<tbody>
|
|
833
|
+
{rows.map((row) => (
|
|
834
|
+
<tr key={row.type} data-testid="net-traffic-row">
|
|
835
|
+
<td style={{ ...cell, textAlign: 'left' }}>{row.type}</td>
|
|
836
|
+
<td style={cell}>{row.countIn || '-'}</td>
|
|
837
|
+
<td style={cell}>{row.countIn ? bytes(row.bytesIn) : '-'}</td>
|
|
838
|
+
<td style={cell}>{row.countOut || '-'}</td>
|
|
839
|
+
<td style={cell}>{row.countOut ? bytes(row.bytesOut) : '-'}</td>
|
|
840
|
+
</tr>
|
|
841
|
+
))}
|
|
842
|
+
</tbody>
|
|
843
|
+
</table>
|
|
844
|
+
)}
|
|
845
|
+
</div>
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
interface MessageDraft {
|
|
850
|
+
readonly type: string;
|
|
851
|
+
readonly payload: string;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/** The draft as a message: its type and its parsed JSON payload (none when empty). */
|
|
855
|
+
function draftMessage(draft: MessageDraft): { type: string; payload: unknown } {
|
|
856
|
+
if (draft.type.trim() === '') throw new Error('Name the message type first.');
|
|
857
|
+
return { type: draft.type, payload: draft.payload.trim() === '' ? undefined : (JSON.parse(draft.payload) as unknown) };
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* A message type and a JSON payload (Colyseus Monitor's Send dialog), sent into the room as this
|
|
862
|
+
* client so a server handler can be exercised without writing game code for it; the Server section
|
|
863
|
+
* sends the same draft from the server.
|
|
864
|
+
*/
|
|
865
|
+
function SendControls({
|
|
866
|
+
adapter,
|
|
867
|
+
ping,
|
|
868
|
+
draft,
|
|
869
|
+
setDraft,
|
|
870
|
+
}: {
|
|
871
|
+
adapter: NetworkingAdapter;
|
|
872
|
+
ping: boolean;
|
|
873
|
+
draft: MessageDraft;
|
|
874
|
+
setDraft: (next: MessageDraft) => void;
|
|
875
|
+
}) {
|
|
876
|
+
const [rtt, setRtt] = useState<string | null>(null);
|
|
877
|
+
const measure = () => {
|
|
878
|
+
setRtt('…');
|
|
879
|
+
adapter.ping?.().then(
|
|
880
|
+
(ms) => setRtt(`${ms} ms`),
|
|
881
|
+
(caught: unknown) => setRtt(caught instanceof Error ? caught.message : String(caught)),
|
|
882
|
+
);
|
|
883
|
+
};
|
|
884
|
+
const [error, setError] = useState<string | null>(null);
|
|
885
|
+
const send = () => {
|
|
886
|
+
try {
|
|
887
|
+
const message = draftMessage(draft);
|
|
888
|
+
adapter.sendMessage?.(message.type, message.payload);
|
|
889
|
+
setError(null);
|
|
890
|
+
} catch (caught) {
|
|
891
|
+
setError(caught instanceof Error ? caught.message : String(caught));
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
return (
|
|
895
|
+
<div
|
|
896
|
+
data-testid="net-send"
|
|
897
|
+
style={{ display: 'flex', gap: spaceVar[2], alignItems: 'center', padding: spaceVar[2] }}
|
|
898
|
+
>
|
|
899
|
+
<TextInput
|
|
900
|
+
data-testid="net-send-type"
|
|
901
|
+
placeholder="Message type"
|
|
902
|
+
value={draft.type}
|
|
903
|
+
onChange={(event) => setDraft({ ...draft, type: event.target.value })}
|
|
904
|
+
style={{ width: 140 }}
|
|
905
|
+
/>
|
|
906
|
+
<TextInput
|
|
907
|
+
data-testid="net-send-payload"
|
|
908
|
+
placeholder='Payload (JSON), e.g. {"x": 1}'
|
|
909
|
+
value={draft.payload}
|
|
910
|
+
onChange={(event) => setDraft({ ...draft, payload: event.target.value })}
|
|
911
|
+
style={{ flex: 1, minWidth: 80 }}
|
|
912
|
+
/>
|
|
913
|
+
<Button type="button" variant="ghost" data-testid="net-send-button" disabled={draft.type.trim() === ''} onClick={send}>
|
|
914
|
+
Send as this client
|
|
915
|
+
</Button>
|
|
916
|
+
{ping ? (
|
|
917
|
+
<>
|
|
918
|
+
<Button type="button" variant="ghost" data-testid="net-ping" onClick={measure}>
|
|
919
|
+
Ping
|
|
920
|
+
</Button>
|
|
921
|
+
{rtt ? (
|
|
922
|
+
<span data-testid="net-rtt" style={{ fontSize: fontSizeVar.sm, ...MONO }}>
|
|
923
|
+
{rtt}
|
|
924
|
+
</span>
|
|
925
|
+
) : null}
|
|
926
|
+
</>
|
|
927
|
+
) : null}
|
|
928
|
+
{error ? (
|
|
929
|
+
<span data-testid="net-send-error" style={{ color: themeVars.semantic.danger, fontSize: fontSizeVar.sm }}>
|
|
930
|
+
{error}
|
|
931
|
+
</span>
|
|
932
|
+
) : null}
|
|
933
|
+
</div>
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* The room server's side — Colyseus Monitor's room list and room view, read from Monitor's own API
|
|
939
|
+
* on the server: every room (Inspect one), the machine's CPU and memory, and the inspected room's
|
|
940
|
+
* clients. Its acts are Monitor's: Broadcast and Dispose for the room, Send and Disconnect for a
|
|
941
|
+
* client, the message being the Send row's draft.
|
|
942
|
+
*/
|
|
943
|
+
function ServerView({
|
|
944
|
+
adapter,
|
|
945
|
+
ownSession,
|
|
946
|
+
ownRoom,
|
|
947
|
+
draft,
|
|
948
|
+
}: {
|
|
949
|
+
adapter: NetworkingAdapter;
|
|
950
|
+
ownSession: string | null;
|
|
951
|
+
ownRoom: string | null;
|
|
952
|
+
draft: MessageDraft;
|
|
953
|
+
}) {
|
|
954
|
+
const [inspection, setInspection] = useState<NetServerInspection | null | undefined>(undefined);
|
|
955
|
+
const [inspected, setInspected] = useState<string | undefined>(undefined);
|
|
956
|
+
const [error, setError] = useState<string | null>(null);
|
|
957
|
+
useEffect(() => {
|
|
958
|
+
let live = true;
|
|
959
|
+
const read = () => {
|
|
960
|
+
void adapter.inspectServer?.(inspected).then(
|
|
961
|
+
(next) => {
|
|
962
|
+
if (live) setInspection(next);
|
|
963
|
+
},
|
|
964
|
+
(caught: unknown) => {
|
|
965
|
+
if (live) setError(caught instanceof Error ? caught.message : String(caught));
|
|
966
|
+
},
|
|
967
|
+
);
|
|
968
|
+
};
|
|
969
|
+
read();
|
|
970
|
+
// Monitor refreshes its room view every 5 s; this reads the server every 2.
|
|
971
|
+
const id = setInterval(read, 2000);
|
|
972
|
+
return () => {
|
|
973
|
+
live = false;
|
|
974
|
+
clearInterval(id);
|
|
975
|
+
};
|
|
976
|
+
}, [adapter, inspected]);
|
|
977
|
+
const act = (run: () => Promise<void> | undefined) => {
|
|
978
|
+
try {
|
|
979
|
+
run()?.then(
|
|
980
|
+
() => setError(null),
|
|
981
|
+
(caught: unknown) => setError(caught instanceof Error ? caught.message : String(caught)),
|
|
982
|
+
);
|
|
983
|
+
} catch (caught) {
|
|
984
|
+
setError(caught instanceof Error ? caught.message : String(caught));
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
const seconds = (ms: number) => `${Math.round(ms / 1000)} s`;
|
|
988
|
+
const cell = { padding: `0 ${spaceVar[3]}`, whiteSpace: 'nowrap' as const };
|
|
989
|
+
if (inspection === undefined) return null;
|
|
990
|
+
if (inspection === null) {
|
|
991
|
+
return (
|
|
992
|
+
<AbsentNote testId="net-server-absent">
|
|
993
|
+
The room server serves no Monitor view (its `server` configuration sets VGAI_ROOM_MONITOR=1).
|
|
994
|
+
</AbsentNote>
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
const roomId = inspection.room?.roomId;
|
|
998
|
+
const actButton = (testId: string, label: string, run: () => Promise<void> | undefined) => (
|
|
999
|
+
<Button type="button" variant="ghost" data-testid={testId} onClick={() => act(run)}>
|
|
1000
|
+
{label}
|
|
1001
|
+
</Button>
|
|
1002
|
+
);
|
|
1003
|
+
return (
|
|
1004
|
+
<div
|
|
1005
|
+
data-testid="net-server"
|
|
1006
|
+
style={{ padding: `${spaceVar[2]} 0`, borderBottom: `1px solid ${themeVars.boundary.default}`, fontSize: fontSizeVar.sm }}
|
|
1007
|
+
>
|
|
1008
|
+
<div data-testid="net-server-summary" style={{ padding: `0 ${spaceVar[3]}`, color: themeVars.content.muted }}>
|
|
1009
|
+
Server · {inspection.rooms.length} room{inspection.rooms.length === 1 ? '' : 's'} · {inspection.connections}{' '}
|
|
1010
|
+
connection{inspection.connections === 1 ? '' : 's'}
|
|
1011
|
+
{inspection.cpuPercent !== null ? ` · CPU ${inspection.cpuPercent.toFixed(0)}%` : ''}
|
|
1012
|
+
{inspection.memory ? ` · memory ${Math.round(inspection.memory.usedMb)} / ${Math.round(inspection.memory.totalMb)} MB` : ''}
|
|
1013
|
+
{inspection.room ? ` · state ${inspection.room.stateBytes} B` : ''}
|
|
1014
|
+
</div>
|
|
1015
|
+
<table style={{ borderCollapse: 'collapse', ...MONO }}>
|
|
1016
|
+
<tbody>
|
|
1017
|
+
{inspection.rooms.map((room) => (
|
|
1018
|
+
<tr key={room.roomId} data-testid="net-server-room" data-inspected={room.roomId === roomId || undefined}>
|
|
1019
|
+
<td style={cell}>{room.name}</td>
|
|
1020
|
+
<td style={cell}>{room.roomId}</td>
|
|
1021
|
+
<td style={cell}>
|
|
1022
|
+
{room.clients}
|
|
1023
|
+
{room.maxClients !== null ? ` / ${room.maxClients}` : ''} clients
|
|
1024
|
+
</td>
|
|
1025
|
+
<td style={cell}>{room.locked ? 'locked' : 'open'}</td>
|
|
1026
|
+
<td style={cell}>{seconds(room.elapsedMs)}</td>
|
|
1027
|
+
<td style={cell}>
|
|
1028
|
+
{room.roomId === roomId ? (
|
|
1029
|
+
<>
|
|
1030
|
+
{actButton('net-server-broadcast', 'Broadcast', () => {
|
|
1031
|
+
const message = draftMessage(draft);
|
|
1032
|
+
return adapter.broadcast?.(message.type, message.payload, room.roomId);
|
|
1033
|
+
})}
|
|
1034
|
+
{actButton('net-server-dispose', 'Dispose', () => adapter.disposeRoom?.(room.roomId))}
|
|
1035
|
+
</>
|
|
1036
|
+
) : (
|
|
1037
|
+
<Button type="button" variant="ghost" data-testid="net-server-inspect" onClick={() => setInspected(room.roomId)}>
|
|
1038
|
+
Inspect
|
|
1039
|
+
</Button>
|
|
1040
|
+
)}
|
|
1041
|
+
</td>
|
|
1042
|
+
</tr>
|
|
1043
|
+
))}
|
|
1044
|
+
{inspection.room?.clients.map((client) => (
|
|
1045
|
+
<tr key={client.sessionId} data-testid="net-server-client">
|
|
1046
|
+
<td style={cell}>client</td>
|
|
1047
|
+
<td style={cell}>
|
|
1048
|
+
{client.sessionId}
|
|
1049
|
+
{client.sessionId === ownSession ? ' (this client)' : ''}
|
|
1050
|
+
</td>
|
|
1051
|
+
<td style={cell}>{seconds(client.elapsedMs)}</td>
|
|
1052
|
+
<td style={cell} colSpan={3}>
|
|
1053
|
+
{actButton('net-server-send', 'Send', () => {
|
|
1054
|
+
const message = draftMessage(draft);
|
|
1055
|
+
return adapter.sendToClient?.(client.sessionId, message.type, message.payload, roomId);
|
|
1056
|
+
})}
|
|
1057
|
+
{actButton('net-server-disconnect', 'Disconnect', () => adapter.disconnectClient?.(client.sessionId, roomId))}
|
|
1058
|
+
</td>
|
|
1059
|
+
</tr>
|
|
1060
|
+
))}
|
|
1061
|
+
</tbody>
|
|
1062
|
+
</table>
|
|
1063
|
+
{inspection.room && inspection.room.roomId !== ownRoom && inspection.room.state !== undefined ? (
|
|
1064
|
+
// ANOTHER room's state, as Monitor's Inspect opens it: read from the server, and edited
|
|
1065
|
+
// and deleted in THAT room. The tree below the section stays this client's own room.
|
|
1066
|
+
<div data-testid="net-server-room-state" style={{ padding: `${spaceVar[2]} 0` }}>
|
|
1067
|
+
<StateTreeNode
|
|
1068
|
+
name={`room ${inspection.room.roomId}`}
|
|
1069
|
+
value={inspection.room.state}
|
|
1070
|
+
depth={0}
|
|
1071
|
+
edit={adapter.editServerState ? (path, next) => adapter.editServerState!(path, next, roomId) : undefined}
|
|
1072
|
+
remove={adapter.deleteServerState ? (path) => adapter.deleteServerState!(path, roomId) : undefined}
|
|
1073
|
+
/>
|
|
1074
|
+
</div>
|
|
1075
|
+
) : null}
|
|
1076
|
+
{error ? <AbsentNote>{error}</AbsentNote> : null}
|
|
1077
|
+
</div>
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* pause/filter, the sparkline sample history, and the polyline geometry —
|
|
10
10
|
* lives here.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
12
|
+
* The inspector reads only the `NetworkingAdapter` interface; decoding a
|
|
13
|
+
* game's Colyseus rooms is the observer's (`services/game-network.ts`).
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import type {
|
|
@@ -82,6 +82,14 @@ export interface NetworkCapabilityMap {
|
|
|
82
82
|
/** The adapter can SET the local identity (`setPlayerIdentity`) — the
|
|
83
83
|
* inspector shows an EDITABLE name field. Implies `identity`. */
|
|
84
84
|
identitySettable: boolean;
|
|
85
|
+
/** Traffic per message type (`getTrafficByType`). */
|
|
86
|
+
traffic: boolean;
|
|
87
|
+
/** Sending a message into the room as this client (`sendMessage`). */
|
|
88
|
+
send: boolean;
|
|
89
|
+
/** Measuring a round trip (`ping`). */
|
|
90
|
+
ping: boolean;
|
|
91
|
+
/** The room server's own view (`inspectServer`). */
|
|
92
|
+
server: boolean;
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
export function deriveNetworkCapabilities(
|
|
@@ -103,6 +111,10 @@ export function deriveNetworkCapabilities(
|
|
|
103
111
|
stats: typeof adapter.getReplicationStats === 'function',
|
|
104
112
|
identity: typeof adapter.getPlayerIdentity === 'function',
|
|
105
113
|
identitySettable: typeof adapter.setPlayerIdentity === 'function',
|
|
114
|
+
traffic: typeof adapter.getTrafficByType === 'function',
|
|
115
|
+
send: typeof adapter.sendMessage === 'function',
|
|
116
|
+
ping: typeof adapter.ping === 'function',
|
|
117
|
+
server: typeof adapter.inspectServer === 'function',
|
|
106
118
|
};
|
|
107
119
|
}
|
|
108
120
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LogEntry } from '@volter/editor-
|
|
1
|
+
import type { LogEntry } from '@volter/editor-sdk/kit/editor-api';
|
|
2
2
|
import type { TickStampedEvent } from '@volter/editor-project/adapter/system-adapter';
|
|
3
3
|
|
|
4
4
|
/** Convert newly observed game-authored debug events into persistent JSONL entries. */
|