@volter/project 0.0.0-stage → 0.5.204
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/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +18 -3
- package/package.json +64 -4
- package/schemas/engine-api.json +57 -0
- package/schemas/engine-api.md +22 -0
- package/schemas/volter-project.schema.json +468 -0
- package/schemas/volter-settings.schema.json +149 -0
- package/src/adapter/adapter-module.ts +997 -0
- package/src/adapter/adapter-surface.ts +34 -0
- package/src/adapter/authoring-seam-contract.ts +224 -0
- package/src/adapter/authoring.ts +1336 -0
- package/src/adapter/binding.ts +445 -0
- package/src/adapter/debug-registry-ref.ts +19 -0
- package/src/adapter/editor-looks.ts +519 -0
- package/src/adapter/finders/finder-result.ts +29 -0
- package/src/adapter/finders/registry.ts +116 -0
- package/src/adapter/frame-capture.ts +80 -0
- package/src/adapter/host-context.ts +114 -0
- package/src/adapter/index.ts +209 -0
- package/src/adapter/ingest/contract-system-slots.ts +28 -0
- package/src/adapter/ingest/game-contract-seams.ts +86 -0
- package/src/adapter/ingest/game-contract.ts +506 -0
- package/src/adapter/ingest/mount-readiness.ts +140 -0
- package/src/adapter/loop-gate-report.ts +53 -0
- package/src/adapter/manifest-interpreter.ts +64 -0
- package/src/adapter/native-entry-surface.ts +100 -0
- package/src/adapter/physics-adapter-2d.ts +30 -0
- package/src/adapter/render-audio.ts +20 -0
- package/src/adapter/render-memory.ts +34 -0
- package/src/adapter/renderer-config.ts +84 -0
- package/src/adapter/root-adapter.ts +243 -0
- package/src/adapter/root-seam-contract.ts +61 -0
- package/src/adapter/root-stacking.ts +67 -0
- package/src/adapter/seam-evidence.ts +336 -0
- package/src/adapter/system-adapter.ts +865 -0
- package/src/adapter/system-seam-contract.ts +138 -0
- package/src/adapter/system-slot.ts +301 -0
- package/src/adapter/transform.ts +35 -0
- package/src/adapter/workspace-arrangement.ts +92 -0
- package/src/core/system-phase.ts +52 -0
- package/src/inspection.ts +281 -0
- package/src/manifest/configuration-kinds.ts +241 -0
- package/src/manifest/editor-port.ts +69 -0
- package/src/manifest/filename.ts +45 -0
- package/src/manifest/kind-modules.ts +58 -0
- package/src/manifest/load-file.ts +33 -0
- package/src/manifest/load.ts +380 -0
- package/src/manifest/locate.ts +55 -0
- package/src/manifest/runtime-environment.ts +48 -0
- package/src/manifest/schema.ts +690 -0
- package/src/settings/keys.ts +152 -0
- package/src/settings/schema.ts +151 -0
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System adapters — optional subsystem capability providers hung off a mounted
|
|
3
|
+
* game (`MountedThreeRoot.systems`). Each is a thin capability boundary the editor can
|
|
4
|
+
* coordinate with WITHOUT owning the implementation. The first-party
|
|
5
|
+
* implementers wrap Rapier / Colyseus / recast; an external game supplies its
|
|
6
|
+
* own or none, and the editor degrades per `capabilities`.
|
|
7
|
+
*
|
|
8
|
+
* These are introspection/coordination boundaries, NOT re-implementations: e.g.
|
|
9
|
+
* `NetworkingAdapter` is not a transport, and `PhysicsAdapter` does not replace
|
|
10
|
+
* Rapier — it lets the editor ask "who owns this object" and "freeze it while I
|
|
11
|
+
* edit it".
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { FrameCapture } from './frame-capture';
|
|
15
|
+
import type { PhysicsAdapter2D } from './physics-adapter-2d';
|
|
16
|
+
import type { OfflineAudioRenderer } from './render-audio';
|
|
17
|
+
import type { RenderMemorySnapshot } from './render-memory';
|
|
18
|
+
import type { Transform, TransformOwner } from './transform';
|
|
19
|
+
|
|
20
|
+
export type PhysicsColliderShape =
|
|
21
|
+
| { readonly type: 'cuboid'; readonly halfExtents: readonly [number, number, number] }
|
|
22
|
+
| { readonly type: 'ball'; readonly radius: number }
|
|
23
|
+
| { readonly type: 'capsule'; readonly halfHeight: number; readonly radius: number }
|
|
24
|
+
/**
|
|
25
|
+
* A collider this seam has no plain-data projection for — a trimesh,
|
|
26
|
+
* heightfield, convex hull, cylinder, cone, …
|
|
27
|
+
*
|
|
28
|
+
* IT IS STILL A COLLIDER, and the body still has it. Implementations used to
|
|
29
|
+
* drop these from `colliders()` entirely, so a body whose only collider was a
|
|
30
|
+
* trimesh inspected as a body with NO physics at all. `kind` is the native
|
|
31
|
+
* shape's own name, so the inspector can say WHICH shape it cannot draw
|
|
32
|
+
* rather than implying nothing is there. Nothing may synthesize dimensions
|
|
33
|
+
* for one, and `previewCollider` refuses it.
|
|
34
|
+
*/
|
|
35
|
+
| { readonly type: 'unsupported'; readonly kind: string };
|
|
36
|
+
|
|
37
|
+
/** Plain-data projection of one native 3D physics collider. The owning
|
|
38
|
+
* physics implementation keeps the actual collider; editor authoring only
|
|
39
|
+
* receives the geometry needed to inspect and draw it. */
|
|
40
|
+
export interface PhysicsColliderSnapshot {
|
|
41
|
+
readonly id: string;
|
|
42
|
+
readonly shape: PhysicsColliderShape;
|
|
43
|
+
readonly position: readonly [number, number, number];
|
|
44
|
+
readonly rotation: readonly [number, number, number, number];
|
|
45
|
+
/** Absolute world scale used when the native shape was created. Source
|
|
46
|
+
* dimensions divide by this to recover @react-three/rapier's local args. */
|
|
47
|
+
readonly scale: readonly [number, number, number];
|
|
48
|
+
readonly sensor: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type PhysicsJointType =
|
|
52
|
+
| 'fixed'
|
|
53
|
+
| 'spherical'
|
|
54
|
+
| 'revolute'
|
|
55
|
+
| 'prismatic'
|
|
56
|
+
| 'rope'
|
|
57
|
+
| 'spring'
|
|
58
|
+
| 'generic';
|
|
59
|
+
|
|
60
|
+
/** Plain-data projection of one native 3D impulse joint. Anchors are exposed
|
|
61
|
+
* in both native body-local space (for persistence) and world space (for
|
|
62
|
+
* viewport instruments); the actual joint remains owned by the physics lib. */
|
|
63
|
+
export interface PhysicsJointSnapshot {
|
|
64
|
+
readonly id: string;
|
|
65
|
+
readonly type: PhysicsJointType;
|
|
66
|
+
readonly body1: string;
|
|
67
|
+
readonly body2: string;
|
|
68
|
+
readonly anchor1: readonly [number, number, number];
|
|
69
|
+
readonly anchor2: readonly [number, number, number];
|
|
70
|
+
readonly worldAnchor1: readonly [number, number, number];
|
|
71
|
+
readonly worldAnchor2: readonly [number, number, number];
|
|
72
|
+
readonly body1Position: readonly [number, number, number];
|
|
73
|
+
readonly body2Position: readonly [number, number, number];
|
|
74
|
+
readonly body1Rotation: readonly [number, number, number, number];
|
|
75
|
+
readonly body2Rotation: readonly [number, number, number, number];
|
|
76
|
+
/** The free axis in body-1 local/world space for revolute/prismatic joints. */
|
|
77
|
+
readonly axis?: readonly [number, number, number];
|
|
78
|
+
readonly worldAxis?: readonly [number, number, number];
|
|
79
|
+
readonly limits?: { readonly min: number; readonly max: number };
|
|
80
|
+
readonly contactsEnabled: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Physics coordination so the editor can stably edit an object a simulation
|
|
85
|
+
* would otherwise overwrite every frame: `freeze → apply → unfreeze`.
|
|
86
|
+
*
|
|
87
|
+
* Keyed by NODE ID (the `EditorNode` id), not `THREE.Object3D` (P-4). The
|
|
88
|
+
* Object3D keying made this seam structurally unreachable for any non-three
|
|
89
|
+
* world: a Pixi or React world could never expose a physics inspector, because
|
|
90
|
+
* the seam spoke a vocabulary it has no values in — which is why the Pixi
|
|
91
|
+
* surface grew a parallel {@link PhysicsAdapter2D} (`pixi/system-adapters.ts`)
|
|
92
|
+
* instead of implementing this. Substrate-specific resolution (id → the native
|
|
93
|
+
* object) is the IMPLEMENTER's job, done once at its own boundary.
|
|
94
|
+
*
|
|
95
|
+
* ## `keyedBy`, and why the physics slot's union is TAGGED
|
|
96
|
+
*
|
|
97
|
+
* `SystemAdapters['physics']` holds EITHER shape ({@link PhysicsCarrier}),
|
|
98
|
+
* because a carrier addresses the thing being edited in its surface's own
|
|
99
|
+
* vocabulary and the canvas lane's is the display object itself. But the two
|
|
100
|
+
* shapes carry the SAME four member names and are otherwise indistinguishable
|
|
101
|
+
* at runtime, while the registry that holds them
|
|
102
|
+
* (`editor/src/authoring/active-systems.ts`) is GAME-scoped and surface-blind —
|
|
103
|
+
* one object every editor panel reads. An untagged union would therefore be one
|
|
104
|
+
* nothing could narrow, and every three-lane consumer
|
|
105
|
+
* (`getActivePhysics()?.freeze(nodeId)`, `play-mode.ts`'s `physics.commit(id,
|
|
106
|
+
* transform)`) would have to reach it through a cast asserting a fact nobody
|
|
107
|
+
* checked.
|
|
108
|
+
*
|
|
109
|
+
* So `keyedBy` is a real discriminant, and it is REQUIRED on
|
|
110
|
+
* {@link PhysicsAdapter2D} while OPTIONAL here on purpose: node-id keying is
|
|
111
|
+
* this seam's original and — until the canvas lane — only vocabulary, so every
|
|
112
|
+
* existing implementer stays valid untouched, and the shape that needs telling
|
|
113
|
+
* apart is the one obliged to say so. {@link nodeKeyedPhysics} /
|
|
114
|
+
* {@link displayKeyedPhysics} are the two narrowings; nothing else should test
|
|
115
|
+
* the tag by hand.
|
|
116
|
+
*/
|
|
117
|
+
export interface PhysicsAdapter {
|
|
118
|
+
/** Optional tag; see the interface comment. `'node-id'` is the only value,
|
|
119
|
+
* and omitting it means the same thing. */
|
|
120
|
+
readonly keyedBy?: 'node-id';
|
|
121
|
+
/** Returns `'physics'` when a body drives this node, else another owner. */
|
|
122
|
+
ownerOf(nodeId: string): TransformOwner;
|
|
123
|
+
/** Pause the body driving `nodeId` (kinematic / sleep / detach) for editing. */
|
|
124
|
+
freeze(nodeId: string): void;
|
|
125
|
+
/** Teleport the body to the edited pose (so the sim continues from there). */
|
|
126
|
+
commit(nodeId: string, t: Transform): void;
|
|
127
|
+
/** Resume simulation of `nodeId`. */
|
|
128
|
+
unfreeze(nodeId: string): void;
|
|
129
|
+
/** Optional debug-draw object (collider wireframes), in the surface's own
|
|
130
|
+
* medium; opaque to the neutral contract. */
|
|
131
|
+
debugDraw?(): unknown;
|
|
132
|
+
/**
|
|
133
|
+
* Optional: turn the engine's per-frame physics debug rendering on/off —
|
|
134
|
+
* the first-party implementation drives Rapier's `debugRender()` into the
|
|
135
|
+
* `debugDraw()` object inside the game's own render system, so an editor
|
|
136
|
+
* overlay toggle costs the editor zero per-frame work (W2a §2.3c).
|
|
137
|
+
*/
|
|
138
|
+
setDebugDrawEnabled?(enabled: boolean): void;
|
|
139
|
+
/**
|
|
140
|
+
* Optional: live world-space contact points this frame, as xyz triples.
|
|
141
|
+
* Returns a view over a REUSED backing buffer — valid only until the next
|
|
142
|
+
* call (no per-frame allocation churn; callers copy into their own
|
|
143
|
+
* geometry).
|
|
144
|
+
*/
|
|
145
|
+
contactPoints?(): Float32Array;
|
|
146
|
+
/** Optional native collider inspection for one authored node. Implementers
|
|
147
|
+
* return only shapes they can describe exactly; absence means the editor
|
|
148
|
+
* offers no collider component UI. */
|
|
149
|
+
colliders?(nodeId: string): readonly PhysicsColliderSnapshot[];
|
|
150
|
+
/** Optional gesture preview against the implementation's real collider.
|
|
151
|
+
* Source persistence remains the AuthoringAdapter's responsibility. */
|
|
152
|
+
previewCollider?(colliderId: string, shape: PhysicsColliderShape): void;
|
|
153
|
+
/** Optional native impulse-joint inspection for joints attached to one node. */
|
|
154
|
+
joints?(nodeId: string): readonly PhysicsJointSnapshot[];
|
|
155
|
+
/** Optional gesture preview against the implementation's real local anchor. */
|
|
156
|
+
previewJointAnchor?(
|
|
157
|
+
jointId: string,
|
|
158
|
+
endpoint: 0 | 1,
|
|
159
|
+
anchor: readonly [number, number, number],
|
|
160
|
+
): void;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface NetPeer {
|
|
164
|
+
id: string;
|
|
165
|
+
label?: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** The project's declared multiplayer server, readable with the game NOT
|
|
169
|
+
* running. `null` from `getServerConfig` means no server is declared. */
|
|
170
|
+
export interface NetServerConfig {
|
|
171
|
+
endpoint: string;
|
|
172
|
+
/** Known once the game's own networking adapter reports it; in edit mode
|
|
173
|
+
* the declared `process` gives only the endpoint. */
|
|
174
|
+
roomName?: string;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** The signed-in player's authored identity, used as the connect default. */
|
|
178
|
+
export interface NetPlayerIdentity {
|
|
179
|
+
name?: string;
|
|
180
|
+
id?: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** The live connection lifecycle, for the editor's network inspector. */
|
|
184
|
+
export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'error';
|
|
185
|
+
|
|
186
|
+
/** Identity of the room the local peer is currently in, or `null` when not connected. */
|
|
187
|
+
export interface RoomInfo {
|
|
188
|
+
roomId: string;
|
|
189
|
+
sessionId: string;
|
|
190
|
+
roomName: string;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Lightweight replication counters for the editor's network inspector. */
|
|
194
|
+
export interface ReplicationStats {
|
|
195
|
+
/** Number of replicated entities currently known (e.g. players + NPCs). */
|
|
196
|
+
entities: number;
|
|
197
|
+
/** Approximate inbound replication activity (state changes observed per second). */
|
|
198
|
+
msgsInPerSec: number;
|
|
199
|
+
/** Approximate outbound message rate (client → server sends per second). */
|
|
200
|
+
msgsOutPerSec: number;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export type Unsubscribe = () => void;
|
|
204
|
+
|
|
205
|
+
/** One captured network message, for the inspector's message log (W3b).
|
|
206
|
+
* `seq` is an adapter-lifetime MONOTONIC counter — the ring-buffer fence a
|
|
207
|
+
* consumer polls with (`messageEvents(sinceSeq)`), mirroring
|
|
208
|
+
* `DebugAdapter.events`' seq discipline. `time` is a `performance.now()`-
|
|
209
|
+
* domain ms timestamp at capture. */
|
|
210
|
+
export interface NetMessageEvent {
|
|
211
|
+
seq: number;
|
|
212
|
+
time: number;
|
|
213
|
+
direction: 'in' | 'out';
|
|
214
|
+
/** Message type/channel (e.g. `'input'`, `'players.add'`, `'state.phase'`). */
|
|
215
|
+
type: string;
|
|
216
|
+
/** Approximate payload size in bytes, when measurable. OMITTED — never
|
|
217
|
+
* fabricated — when the transport doesn't expose it (e.g. Colyseus schema
|
|
218
|
+
* patches observed via the Callbacks API carry no byte count). */
|
|
219
|
+
size?: number;
|
|
220
|
+
/** `true` when the link conditioner (`setConditioning`) dropped this
|
|
221
|
+
* outbound message instead of sending it — logged so the inspector can
|
|
222
|
+
* show the drop honestly rather than silently losing it. */
|
|
223
|
+
dropped?: boolean;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Send/receive rates for the inspector's sparklines (W3b). The byte fields
|
|
227
|
+
* are OPTIONAL capabilities-within-the-capability: an implementer omits a
|
|
228
|
+
* direction it cannot measure (never reports a fabricated 0). */
|
|
229
|
+
/** One message type's traffic, both directions. */
|
|
230
|
+
export interface NetTypeTraffic {
|
|
231
|
+
type: string;
|
|
232
|
+
countIn: number;
|
|
233
|
+
countOut: number;
|
|
234
|
+
bytesIn: number;
|
|
235
|
+
bytesOut: number;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** One replicated entity's traffic (Godot's synchronizer table): the state patches that changed
|
|
239
|
+
* it, the field changes they carried, and the size range of those patches, which carried other
|
|
240
|
+
* entities too. `path` is the entity's place in the state (`players.abc`, `orbs.orb_1`). */
|
|
241
|
+
export interface NetEntityTraffic {
|
|
242
|
+
path: string;
|
|
243
|
+
syncs: number;
|
|
244
|
+
changes: number;
|
|
245
|
+
patchBytesMin: number;
|
|
246
|
+
patchBytesMax: number;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface NetServerRoom {
|
|
250
|
+
roomId: string;
|
|
251
|
+
name: string;
|
|
252
|
+
clients: number;
|
|
253
|
+
/** `null` when the room admits any number. */
|
|
254
|
+
maxClients: number | null;
|
|
255
|
+
locked: boolean;
|
|
256
|
+
elapsedMs: number;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export interface NetServerClient {
|
|
260
|
+
sessionId: string;
|
|
261
|
+
elapsedMs: number;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface NetServerInspection {
|
|
265
|
+
rooms: NetServerRoom[];
|
|
266
|
+
connections: number;
|
|
267
|
+
/** The server machine's CPU use in percent, when it reports one. */
|
|
268
|
+
cpuPercent: number | null;
|
|
269
|
+
memory: { usedMb: number; totalMb: number } | null;
|
|
270
|
+
/** The current room's clients and its full state's size, when the room is still hosted. */
|
|
271
|
+
room: { roomId: string; clients: NetServerClient[]; stateBytes: number; state?: unknown } | null;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export interface NetConditioningLimits {
|
|
275
|
+
latencyMs?: string;
|
|
276
|
+
jitterMs?: string;
|
|
277
|
+
packetLoss?: string;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export interface NetRates {
|
|
281
|
+
msgsInPerSec: number;
|
|
282
|
+
msgsOutPerSec: number;
|
|
283
|
+
bytesInPerSec?: number;
|
|
284
|
+
bytesOutPerSec?: number;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Client-side link-conditioning parameters, applied AT THE ADAPTER SEAM
|
|
288
|
+
* (W3b M3) — the implementer wraps its own transport calls (e.g. delaying/
|
|
289
|
+
* dropping around `room.send`); nobody patches transport internals. */
|
|
290
|
+
export interface NetConditioning {
|
|
291
|
+
/** Fixed added delay per outbound message, ms. */
|
|
292
|
+
latencyMs: number;
|
|
293
|
+
/** Uniform random extra delay in `[0, jitterMs)`, ms. */
|
|
294
|
+
jitterMs: number;
|
|
295
|
+
/** Probability in `[0, 1]` that an outbound message is dropped. */
|
|
296
|
+
packetLoss: number;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* INTROSPECTION over replication state — NOT a networking transport.
|
|
301
|
+
*
|
|
302
|
+
* The optional members are CAPABILITIES in the `SystemAdapters` sense
|
|
303
|
+
* (absence means "not supported" and the editor's Network inspector marks
|
|
304
|
+
* that section absent — it never fabricates data): `getStateSnapshot` feeds
|
|
305
|
+
* the replicated-state tree, `messageEvents` the message log, `getRates` the
|
|
306
|
+
* rate sparklines, and `getConditioning`/`setConditioning` the latency/loss
|
|
307
|
+
* conditioner. The first-party Colyseus factory
|
|
308
|
+
* (`createColyseusNetworkingAdapter`) only attaches each one when its config
|
|
309
|
+
* supplies the accessor, so partial implementers degrade honestly.
|
|
310
|
+
*/
|
|
311
|
+
export interface NetworkingAdapter {
|
|
312
|
+
peers(): NetPeer[];
|
|
313
|
+
/** Keyed by NODE ID, not `THREE.Object3D` — see {@link PhysicsAdapter} (P-4). */
|
|
314
|
+
networkId(nodeId: string): string | null;
|
|
315
|
+
authority(nodeId: string): 'local' | 'remote' | 'server';
|
|
316
|
+
/** Remote/server-authoritative nodes → inspect-only in the editor. */
|
|
317
|
+
editable(nodeId: string): boolean;
|
|
318
|
+
/**
|
|
319
|
+
* Optional capability: current connection lifecycle state.
|
|
320
|
+
*
|
|
321
|
+
* OPTIONAL for the same reason every other member here is — `'disconnected'`
|
|
322
|
+
* is a POSITIVE CLAIM about the link, and an implementer that never taught
|
|
323
|
+
* this adapter to read connection state has not made it. Absent ⇒ the
|
|
324
|
+
* inspector marks the section unsupported; it does not report a game as
|
|
325
|
+
* disconnected on the strength of nobody having looked.
|
|
326
|
+
*/
|
|
327
|
+
getConnectionState?(): ConnectionState;
|
|
328
|
+
/**
|
|
329
|
+
* Optional capability: the active room's identity, or `null` when connected
|
|
330
|
+
* to no room. `null` is the ANSWER "there is no room", so absence of the
|
|
331
|
+
* accessor — "nothing here can tell you" — has to be a different thing.
|
|
332
|
+
*/
|
|
333
|
+
getRoomInfo?(): RoomInfo | null;
|
|
334
|
+
/**
|
|
335
|
+
* Optional capability: replication activity snapshot, for a live network
|
|
336
|
+
* inspector panel. Zeroes are a measurement, not a default; an adapter with
|
|
337
|
+
* no counters omits this rather than reporting a quiet link.
|
|
338
|
+
*/
|
|
339
|
+
getReplicationStats?(): ReplicationStats;
|
|
340
|
+
/** Subscribe to changes in connection state / room / replication stats. */
|
|
341
|
+
subscribe(cb: () => void): Unsubscribe;
|
|
342
|
+
/** Optional capability: one plain-data (JSON-safe) snapshot of the
|
|
343
|
+
* replicated room state, or `null` when not connected. Colyseus schema is
|
|
344
|
+
* self-describing (`state.toJSON()`), so the first-party implementation
|
|
345
|
+
* is one call — the inspector renders whatever tree comes back. */
|
|
346
|
+
getStateSnapshot?(): unknown;
|
|
347
|
+
/** Optional capability: captured message events with `seq > sinceSeq`
|
|
348
|
+
* (whole ring when omitted). Implementations ring-buffer (drop oldest);
|
|
349
|
+
* the inspector keeps its own consumer-side ring and fences on `seq`. */
|
|
350
|
+
messageEvents?(sinceSeq?: number): NetMessageEvent[];
|
|
351
|
+
/** Optional capability: msg + byte rates for the inspector's sparklines. */
|
|
352
|
+
getRates?(): NetRates;
|
|
353
|
+
/** Optional capability (paired with `setConditioning`): current
|
|
354
|
+
* conditioning parameters. */
|
|
355
|
+
getConditioning?(): NetConditioning;
|
|
356
|
+
/** Optional capability: apply link conditioning at the adapter seam. */
|
|
357
|
+
setConditioning?(c: NetConditioning): void;
|
|
358
|
+
/** Optional edit-time capability: the project's declared server, readable
|
|
359
|
+
* with the game NOT running (`null` = no server declared). Hides on
|
|
360
|
+
* absence, like the other optionals. */
|
|
361
|
+
getServerConfig?(): NetServerConfig | null;
|
|
362
|
+
/** Optional capability: the local player's identity, as the GAME defines it
|
|
363
|
+
* (e.g. read off its own room state). The editor consumes this for display —
|
|
364
|
+
* a seat label — ONLY when a real implementer provides it, falling back to a
|
|
365
|
+
* generic label otherwise. */
|
|
366
|
+
getPlayerIdentity?(): NetPlayerIdentity | undefined;
|
|
367
|
+
/** Traffic per message type since the connection opened, both directions — state and patches
|
|
368
|
+
* included as their own rows (Godot's network profiler tables). */
|
|
369
|
+
getTrafficByType?(): NetTypeTraffic[];
|
|
370
|
+
/** Incoming state traffic per replicated entity since the connection opened (or the last
|
|
371
|
+
* clear), ordered by syncs. */
|
|
372
|
+
getTrafficByEntity?(): NetEntityTraffic[];
|
|
373
|
+
/** Whether the traffic totals and the message log are counting (Godot's profiler Start/Stop);
|
|
374
|
+
* stopped, frames still pass and the state still updates, but nothing is tallied. */
|
|
375
|
+
isRecording?(): boolean;
|
|
376
|
+
setRecording?(on: boolean): void;
|
|
377
|
+
/** Send `payload` to the room as this client, under message type `type` (Colyseus Monitor's
|
|
378
|
+
* Send), so a server handler can be exercised from the inspector. */
|
|
379
|
+
sendMessage?(type: string, payload: unknown): void;
|
|
380
|
+
/** Which conditioning fields this adapter cannot apply, each with the reason (a reliable
|
|
381
|
+
* WebSocket loses nothing, so it cannot simulate loss). Omitted means every field applies. */
|
|
382
|
+
getConditioningLimits?(): NetConditioningLimits;
|
|
383
|
+
/** Measure one round trip to the server now, in milliseconds. */
|
|
384
|
+
ping?(): Promise<number>;
|
|
385
|
+
/** The room server's own view — every room it hosts, and the current room's clients (Colyseus
|
|
386
|
+
* Monitor's). `null` when the server serves no such view. */
|
|
387
|
+
inspectServer?(roomId?: string): Promise<NetServerInspection | null>;
|
|
388
|
+
/** Disconnect one client of the current room from the server's side (Monitor's Disconnect). */
|
|
389
|
+
disconnectClient?(sessionId: string, roomId?: string): Promise<void>;
|
|
390
|
+
/** Set one value of the room's authoritative state on the server (Monitor's State edit);
|
|
391
|
+
* `path` is the keys from the state's root. */
|
|
392
|
+
editServerState?(path: readonly (string | number)[], value: unknown, roomId?: string): Promise<void>;
|
|
393
|
+
/** Remove one key of the room's authoritative state on the server (Monitor's State delete). */
|
|
394
|
+
deleteServerState?(path: readonly (string | number)[], roomId?: string): Promise<void>;
|
|
395
|
+
/** From the server, send `payload` under `type` to one client of the room (Monitor's Send). */
|
|
396
|
+
sendToClient?(sessionId: string, type: string, payload: unknown, roomId?: string): Promise<void>;
|
|
397
|
+
/** From the server, send `payload` under `type` to every client of the room (Monitor's
|
|
398
|
+
* Broadcast). */
|
|
399
|
+
broadcast?(type: string, payload: unknown, roomId?: string): Promise<void>;
|
|
400
|
+
/** Dispose a room on the server (Monitor's Dispose); its clients are disconnected. */
|
|
401
|
+
disposeRoom?(roomId?: string): Promise<void>;
|
|
402
|
+
/** The replicated type of the state field at `path` (`float32`, `map<schema>`), from the
|
|
403
|
+
* schema the server declared — what Godot's Replication panel shows of a synchronizer. */
|
|
404
|
+
stateFieldType?(path: readonly (string | number)[]): string | null;
|
|
405
|
+
/** Start the traffic totals from zero (Godot's profiler Clear resets its tables). */
|
|
406
|
+
clearTraffic?(): void;
|
|
407
|
+
/** Optional capability, PAIRED with {@link getPlayerIdentity}: set the local
|
|
408
|
+
* player's identity through the game's OWN multiplayer mechanism. The editor
|
|
409
|
+
* renders an editable name field ONLY when a real implementer provides this
|
|
410
|
+
* (consume-by-presence, exactly like {@link setConditioning}); its absence
|
|
411
|
+
* means the game does not support an author-settable name and no field shows.
|
|
412
|
+
*
|
|
413
|
+
* This is NOT the editor imposing a name: whether a name is settable, and how
|
|
414
|
+
* it reaches the server, is entirely the game's decision — a game that names
|
|
415
|
+
* players by join order simply never provides this member. When it does, the
|
|
416
|
+
* implementation drives the game's real path (a join option honoured by the
|
|
417
|
+
* server, a rename message, …), so what the editor sets is the same value the
|
|
418
|
+
* multiplayer build actually uses — never a placeholder the server discards. */
|
|
419
|
+
setPlayerIdentity?(identity: NetPlayerIdentity): void | Promise<void>;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export interface NavPoint {
|
|
423
|
+
x: number;
|
|
424
|
+
y: number;
|
|
425
|
+
z: number;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Navmesh bake parameters — the recast `rcConfig` vocabulary the scene schema
|
|
430
|
+
* (`SceneNavigationSchema`) also mirrors. All optional; an implementation
|
|
431
|
+
* applies its own defaults. `walkableHeight`/`walkableClimb`/`walkableRadius`
|
|
432
|
+
* are in VOXEL units (multiples of `cellHeight`/`cellSize`), per recast.
|
|
433
|
+
*/
|
|
434
|
+
export interface NavBakeParams {
|
|
435
|
+
cellSize?: number | undefined;
|
|
436
|
+
cellHeight?: number | undefined;
|
|
437
|
+
walkableSlopeAngle?: number | undefined;
|
|
438
|
+
walkableHeight?: number | undefined;
|
|
439
|
+
walkableClimb?: number | undefined;
|
|
440
|
+
walkableRadius?: number | undefined;
|
|
441
|
+
maxEdgeLen?: number | undefined;
|
|
442
|
+
maxSimplificationError?: number | undefined;
|
|
443
|
+
minRegionArea?: number | undefined;
|
|
444
|
+
mergeRegionArea?: number | undefined;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** One crowd agent's live state — plain data (no engine/WASM handles), for the
|
|
448
|
+
* editor's play-mode crowd debug draw. */
|
|
449
|
+
export interface NavCrowdAgentState {
|
|
450
|
+
position: NavPoint;
|
|
451
|
+
velocity: NavPoint;
|
|
452
|
+
radius: number;
|
|
453
|
+
height: number;
|
|
454
|
+
/** The crowd's own agent state. `invalid` is the expressible FAILURE — the
|
|
455
|
+
* agent is off the navmesh (a bad spawn or teleport) and will not move;
|
|
456
|
+
* `offmesh` is an off-mesh connection traversal. Absent on an adapter that
|
|
457
|
+
* predates this field — the editor draws such agents as walking. */
|
|
458
|
+
state?: 'invalid' | 'walking' | 'offmesh';
|
|
459
|
+
/** The agent's current move target, when the implementation reports one. */
|
|
460
|
+
target?: NavPoint;
|
|
461
|
+
/** The local path-corridor corners the agent is steering along — the
|
|
462
|
+
* REQUESTED PATH the debug draw projects. Empty or absent when idle. */
|
|
463
|
+
corners?: NavPoint[];
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Inspect a game's navigation mesh: whether one is built, query a path, and get a
|
|
468
|
+
* debug overlay. Coordination/introspection only — the editor doesn't own the build.
|
|
469
|
+
*
|
|
470
|
+
* The optional members are CAPABILITIES in the `SystemAdapters` sense (absence
|
|
471
|
+
* means "not supported", the editor degrades): `bake`/`exportData` let the
|
|
472
|
+
* editor's Navigation panel (re)build and persist a navmesh through the seam —
|
|
473
|
+
* the first-party recast implementation (`createNavigationAdapter`) is the
|
|
474
|
+
* blessed implementer — while an external game that owns its build simply
|
|
475
|
+
* omits them. `clear` lets that same owner release its navmesh; the editor never
|
|
476
|
+
* disposes implementation handles itself. `crowdAgents` feeds the play-mode
|
|
477
|
+
* crowd debug draw.
|
|
478
|
+
*
|
|
479
|
+
* `debugMesh`/`bake`/`clear` trade in the surface's own scene, objects and
|
|
480
|
+
* meshes, which this neutral contract leaves opaque; `@volter/editor-threejs`
|
|
481
|
+
* names the Three-typed view (`threeNavigation`).
|
|
482
|
+
*/
|
|
483
|
+
export interface NavigationAdapter {
|
|
484
|
+
hasNavMesh(): boolean;
|
|
485
|
+
/**
|
|
486
|
+
* `[]` means SEARCHED AND FOUND NO ROUTE — a fact about the level. An
|
|
487
|
+
* implementer with no navmesh to search, or whose query FAILED, THROWS
|
|
488
|
+
* instead; those are facts about the implementer, and collapsing all three
|
|
489
|
+
* into `[]` tells a caller the level is impassable when nobody looked.
|
|
490
|
+
* Gate on {@link hasNavMesh} where the first case is reachable.
|
|
491
|
+
*/
|
|
492
|
+
findPath(start: NavPoint, end: NavPoint): NavPoint[];
|
|
493
|
+
debugMesh(scene: unknown): unknown;
|
|
494
|
+
/** (Re)build the navmesh from source meshes. Synchronous in the blessed
|
|
495
|
+
* recast/WASM implementation; returns `false` on a failed bake. */
|
|
496
|
+
bake?(meshes: readonly unknown[], params?: NavBakeParams): boolean;
|
|
497
|
+
/** Serialize the current navmesh to a binary blob (the editor persists it
|
|
498
|
+
* as the scene's `.navmesh` sidecar). Throws when nothing is built. */
|
|
499
|
+
exportData?(): Uint8Array;
|
|
500
|
+
/** Release the current navmesh and any debug object it attached to `scene`.
|
|
501
|
+
* Optional because an external game's navigation may be inspect-only. */
|
|
502
|
+
clear?(scene: unknown): void;
|
|
503
|
+
/** Live crowd-agent snapshots for debug draw. Empty when no crowd. */
|
|
504
|
+
crowdAgents?(): NavCrowdAgentState[];
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** One node in an audio-graph snapshot (W3c, the Audio debugger tab). Edges
|
|
508
|
+
* point DOWNSTREAM: `outputs` holds the ids of nodes this node's output
|
|
509
|
+
* feeds (ultimately reaching the `destination` node). */
|
|
510
|
+
export interface AudioGraphNode {
|
|
511
|
+
id: string;
|
|
512
|
+
/** Node class/constructor name (`'GainNode'`, `'Gain'` for a Tone node,
|
|
513
|
+
* `'AudioDestinationNode'`, …) — whatever the implementation truthfully
|
|
514
|
+
* knows, never a prettified fabrication. */
|
|
515
|
+
type: string;
|
|
516
|
+
/** Human-facing label (`'Master'`, `'Music bus'`) when one exists. */
|
|
517
|
+
label?: string;
|
|
518
|
+
/** ids of downstream nodes this node's output connects to. */
|
|
519
|
+
outputs: string[];
|
|
520
|
+
/** Lifecycle/playback state when truthfully known (`'running'`,
|
|
521
|
+
* `'suspended'`, `'started'`…). OMITTED — never fabricated — otherwise. */
|
|
522
|
+
state?: string;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/** Musical-transport snapshot for the Audio debugger's transport strip
|
|
526
|
+
* (W3c). The first-party implementation reads Tone's real transport. */
|
|
527
|
+
export interface AudioTransportState {
|
|
528
|
+
state: 'started' | 'stopped' | 'paused';
|
|
529
|
+
/** Playback position along the transport's timeline, seconds. */
|
|
530
|
+
seconds: number;
|
|
531
|
+
bpm: number;
|
|
532
|
+
/** Musical position (`Bars:Beats:Sixteenths`) when a musical grid exists. */
|
|
533
|
+
position?: string;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** One bus level sample: `level` is linear RMS over the implementation's most
|
|
537
|
+
* recent analysis window (0 = silence; ~1 = full-scale). */
|
|
538
|
+
export interface AudioMeterFrame {
|
|
539
|
+
id: string;
|
|
540
|
+
label: string;
|
|
541
|
+
level: number;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/** A live metering session. Metering costs real audio nodes (Tone.Meter /
|
|
545
|
+
* AnalyserNode taps), so it is ACQUIRED for exactly as long as a meter UI is
|
|
546
|
+
* visible and MUST be released via `dispose()` — the first-party handle
|
|
547
|
+
* disconnects its analyser taps there (no leaked nodes after Stop). */
|
|
548
|
+
export interface AudioMeterHandle {
|
|
549
|
+
/** Current level per bus. Cheap enough to poll at UI rate (~10 Hz). */
|
|
550
|
+
read(): AudioMeterFrame[];
|
|
551
|
+
/** Release the metering nodes. Idempotent. */
|
|
552
|
+
dispose(): void;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/** One entry in the audio event ring (W3c). `seq` is an adapter-lifetime
|
|
556
|
+
* MONOTONIC counter — the same ring-buffer fence discipline as
|
|
557
|
+
* `NetMessageEvent.seq` / `DebugAdapter.events`. `time` is a
|
|
558
|
+
* `performance.now()`-domain ms timestamp at capture. `'error'` is reserved
|
|
559
|
+
* for implementations that can truthfully report one (the first-party
|
|
560
|
+
* adapter has no honest audio-error source today and never emits it). */
|
|
561
|
+
export interface AudioDebugEvent {
|
|
562
|
+
seq: number;
|
|
563
|
+
time: number;
|
|
564
|
+
kind:
|
|
565
|
+
| 'transport-start'
|
|
566
|
+
| 'transport-stop'
|
|
567
|
+
| 'transport-pause'
|
|
568
|
+
| 'context-statechange'
|
|
569
|
+
| 'mute'
|
|
570
|
+
| 'unmute'
|
|
571
|
+
| 'error';
|
|
572
|
+
detail?: string;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Silence/restore a world's own audio (D10, T7.6) — NOT a mixer/bus API. This
|
|
577
|
+
* is the seam `Game.play.pause()` calls so a paused world's audio goes quiet
|
|
578
|
+
* too ("an audio seam in `SystemAdapters` so pause can silence game audio
|
|
579
|
+
* (howler et al.) or explicitly report it cannot"). Absence on a world's
|
|
580
|
+
* `mounted.systems` means exactly that: `Game.play.pause()` reports it loudly
|
|
581
|
+
* once per world rather than silently leaving that world's audio playing under
|
|
582
|
+
* a "paused" game.
|
|
583
|
+
*
|
|
584
|
+
* The OPTIONAL methods below are the W3c read-only INTROSPECTION capabilities
|
|
585
|
+
* feeding the editor's Audio debugger tab (graph / transport / meters /
|
|
586
|
+
* events). They follow the W3a degradation ladder: an implementer omits what
|
|
587
|
+
* it cannot truthfully provide, and the editor marks that section absent —
|
|
588
|
+
* never fabricated. The full MIXER (sends/effects/gain editing) is SQ-4-gated
|
|
589
|
+
* and deliberately NOT part of this seam. The first-party implementation over
|
|
590
|
+
* the engine's own Tone/Web-Audio stack is
|
|
591
|
+
* `packages/game-runtime/src/audio/audio-introspection.ts`; pre-existing
|
|
592
|
+
* mute-only registrants keep compiling untouched (every addition is
|
|
593
|
+
* optional).
|
|
594
|
+
*/
|
|
595
|
+
export interface AudioAdapter {
|
|
596
|
+
/** Resume/unlock this adapter's real audio context from a user gesture.
|
|
597
|
+
* Optional for external adapters that do not own a resumable Web Audio
|
|
598
|
+
* context; first-party edit and play adapters both provide it. */
|
|
599
|
+
resume?(): void;
|
|
600
|
+
/** `true` silences this world's audio; `false` restores it to whatever
|
|
601
|
+
* level it was at before silencing (an implementer's own concern — the
|
|
602
|
+
* first-party adapter restores its master-gain value, not a hardcoded 1). */
|
|
603
|
+
setMuted(muted: boolean): void;
|
|
604
|
+
/** Current muted state, for inspection (editor mute UI, proofs). */
|
|
605
|
+
isMuted(): boolean;
|
|
606
|
+
/** Optional: current audio-graph snapshot (active nodes + downstream
|
|
607
|
+
* edges). The first-party adapter reports the engine bus hierarchy plus
|
|
608
|
+
* every Tone node routed through `connectToneBusToMasterGain` — it cannot
|
|
609
|
+
* see a game's private Tone-internal wiring and does not pretend to. */
|
|
610
|
+
graphSnapshot?(): AudioGraphNode[];
|
|
611
|
+
/** Optional: musical-transport snapshot. Returns `null` — an honest
|
|
612
|
+
* "this world has no musical transport" — when none is active (e.g. the
|
|
613
|
+
* first-party adapter when the game never bridged Tone onto this world's
|
|
614
|
+
* context). Method ABSENT means the capability itself is unsupported. */
|
|
615
|
+
transportState?(): AudioTransportState | null;
|
|
616
|
+
/** Optional: begin a per-bus metering session. Returns `null` when the
|
|
617
|
+
* environment cannot meter (headless world with no real AudioContext) —
|
|
618
|
+
* the UI says so rather than showing frozen zeros. */
|
|
619
|
+
acquireMeters?(): AudioMeterHandle | null;
|
|
620
|
+
/** Optional: tap the real mixed output for a gameplay recording. The
|
|
621
|
+
* returned stream is the implementation's native browser audio stream; the
|
|
622
|
+
* caller must dispose the tap when recording ends. Absence means video can
|
|
623
|
+
* still be recorded, honestly without an audio track. */
|
|
624
|
+
acquireRecordingStream?(): AudioRecordingHandle | null;
|
|
625
|
+
/**
|
|
626
|
+
* Optional: render this world's audio for the ABSOLUTE/canonical sim range
|
|
627
|
+
* `[start, end)` OFFLINE and deterministically — the fixed-step sibling of
|
|
628
|
+
* `acquireRecordingStream` above. That one taps the speakers in WALL time,
|
|
629
|
+
* which is exactly what the editor's fixed-step video export
|
|
630
|
+
* (`packages/editor-game/src/host/gameplay-export.ts`) may not do: a paused run that
|
|
631
|
+
* is stepped frame by frame emits no real-time audio at all, and anything
|
|
632
|
+
* captured from a live context would vary run to run. This returns PCM for
|
|
633
|
+
* a sim window instead, so identical `(start, end)` on an unchanged score
|
|
634
|
+
* must return identical samples.
|
|
635
|
+
*
|
|
636
|
+
* THE OFFSET TRAP (the gameplay export states it at its call site; quoted
|
|
637
|
+
* here because this is the other place implementers read):
|
|
638
|
+
* an offline render of `[10, 10.72)` schedules its first event at LOCAL
|
|
639
|
+
* time 0, not absolute 10. The caller reasons in canonical time, so the
|
|
640
|
+
* implementation must subtract `start` from every scheduled event time.
|
|
641
|
+
* The export places the returned buffer at local timestamp 0 of the clip.
|
|
642
|
+
*
|
|
643
|
+
* CLAMP THE SUBTRACTION AT ZERO. Measured 2026-09-19 against a working
|
|
644
|
+
* implementation: a sim clock ACCUMULATES its fixed step, so `start` for
|
|
645
|
+
* tick 900 arrives as 15.000000000000036, not 15. An event authored at
|
|
646
|
+
* exactly 15 then schedules at -3.6e-14 and Web Audio throws outright
|
|
647
|
+
* ("Time must be a finite non-negative number"). An event at or
|
|
648
|
+
* infinitesimally before the window start belongs at local 0.
|
|
649
|
+
*
|
|
650
|
+
* Absent — the honest default, and what the first-party master-gain adapter
|
|
651
|
+
* reports — means the export writes a video-only file and says `audio:
|
|
652
|
+
* false` in its result. A live Web Audio graph cannot be re-rendered
|
|
653
|
+
* offline by a bus wrapper; a world that wants export audio owns a
|
|
654
|
+
* deterministic score and binds this through `editor/volter.adapter.ts` (with the
|
|
655
|
+
* `music` capability that is one line over `renderToneOffline`).
|
|
656
|
+
*/
|
|
657
|
+
renderOffline?: OfflineAudioRenderer;
|
|
658
|
+
/** Optional: the audio event ring (bounded, drops oldest). `sinceSeq`
|
|
659
|
+
* filters to `seq > sinceSeq` — the consumer's fence. */
|
|
660
|
+
audioEvents?(sinceSeq?: number): AudioDebugEvent[];
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/** One temporary tap of an audio adapter's mixed output. */
|
|
664
|
+
export interface AudioRecordingHandle {
|
|
665
|
+
readonly stream: MediaStream;
|
|
666
|
+
dispose(): void;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/** One native camera known to a runtime camera controller.
|
|
670
|
+
*
|
|
671
|
+
* `id` is the controller's own stable key (a shot name, virtual-camera id,
|
|
672
|
+
* etc.). `nativeId` is OPTIONAL identity from the underlying scene object —
|
|
673
|
+
* Three.js implementations normally use `Object3D.uuid`; another adapter may
|
|
674
|
+
* use its renderer's equivalent. The editor uses it only to correlate an
|
|
675
|
+
* already-selected native camera with this read-only runtime projection. */
|
|
676
|
+
export interface CameraRuntimeCamera {
|
|
677
|
+
id: string;
|
|
678
|
+
label: string;
|
|
679
|
+
nativeId?: string;
|
|
680
|
+
/** Optional because ownership/cut controllers do not necessarily arbitrate
|
|
681
|
+
* by priority. A camera brain that does may expose the real value it uses. */
|
|
682
|
+
priority?: number;
|
|
683
|
+
enabled?: boolean;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/** A transition the controller is evaluating now. Plain data only: no
|
|
687
|
+
* renderer camera, tween, graph node, or editor-authored state crosses the
|
|
688
|
+
* seam. */
|
|
689
|
+
export interface CameraRuntimeTransition {
|
|
690
|
+
fromCameraId: string | null;
|
|
691
|
+
toCameraId: string;
|
|
692
|
+
durationSeconds: number;
|
|
693
|
+
elapsedSeconds: number;
|
|
694
|
+
progress: number;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/** Read-only runtime state for a selected native camera's Inspector section.
|
|
698
|
+
* Runtime follow/aim/collision stays project behavior; this snapshot merely
|
|
699
|
+
* reports what that behavior already decided. */
|
|
700
|
+
export interface CameraRuntimeSnapshot {
|
|
701
|
+
cameras: readonly CameraRuntimeCamera[];
|
|
702
|
+
activeCameraId: string | null;
|
|
703
|
+
/** The behavior/cinematic currently allowed to drive the rendered camera. */
|
|
704
|
+
activeControllerId: string | null;
|
|
705
|
+
ownershipDepth: number;
|
|
706
|
+
transition: CameraRuntimeTransition | null;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/** Camera-controller INTROSPECTION — not a camera graph or controller API.
|
|
710
|
+
*
|
|
711
|
+
* Games continue to author ordinary native cameras and drive them with their
|
|
712
|
+
* own TS/TSX. A controller registers this adapter only when it can truthfully
|
|
713
|
+
* expose its active camera, priority and blend state. Absence hides the
|
|
714
|
+
* runtime UI; the editor never infers a brain from scene structure. */
|
|
715
|
+
export interface CameraAdapter {
|
|
716
|
+
snapshot(): CameraRuntimeSnapshot;
|
|
717
|
+
subscribe(listener: () => void): Unsubscribe;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/** One tick-stamped debug event (`ctx.debug.emit`, spec §3.3): `tick`/`simT`
|
|
721
|
+
* are the engine's own counters AT EMISSION, so events and state reads
|
|
722
|
+
* correlate frame-exactly across every door (in-page bridge, relay, panels).
|
|
723
|
+
*
|
|
724
|
+
* `seq` (run-4 friction #5 fix) is a registry-lifetime MONOTONIC counter,
|
|
725
|
+
* strictly increasing by one per `emit()` call regardless of `tick` —
|
|
726
|
+
* unlike `tick`, it is never shared by two events, which is what makes it
|
|
727
|
+
* safe to fence on. A debug command handler runs BETWEEN ticks (its
|
|
728
|
+
* emissions carry whatever `tick` is current at that instant, same as every
|
|
729
|
+
* other emission at that instant would), so two DIFFERENT events — or an
|
|
730
|
+
* event and a consumer's fence point — can legitimately share one `tick`;
|
|
731
|
+
* `seq` never collides the same way, so "everything after what I've already
|
|
732
|
+
* observed" is unambiguous. See `events(sinceSeq)` below. */
|
|
733
|
+
export interface TickStampedEvent {
|
|
734
|
+
tick: number;
|
|
735
|
+
simT: number;
|
|
736
|
+
event: string;
|
|
737
|
+
detail?: unknown;
|
|
738
|
+
seq: number;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/** One registered debug command's listing shape — `locus` is always present
|
|
742
|
+
* (defaults to `'client'` when a command didn't declare one) so every
|
|
743
|
+
* listing surface can print it without a fallback of its own. */
|
|
744
|
+
export interface DebugCommandInfo {
|
|
745
|
+
name: string;
|
|
746
|
+
description?: string | undefined;
|
|
747
|
+
/** JSON-Schema projection of the command's Zod args tuple, when declared. */
|
|
748
|
+
argsJsonSchema?: unknown;
|
|
749
|
+
locus: 'client' | 'server';
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* The debug/synthetic-player seam: game-scoped introspection + actuation
|
|
754
|
+
* over whatever a game registers via `ctx.debug`
|
|
755
|
+
* (`registerStateProvider`/`registerCommand`/`emit`). NOT a gameplay API —
|
|
756
|
+
* this is the one seam the debug bridge, the editor's Debug Console/State
|
|
757
|
+
* Watch panels, and `@volter/game-live` all read/drive through.
|
|
758
|
+
*/
|
|
759
|
+
export interface DebugAdapter {
|
|
760
|
+
providers(): { name: string; tier: 'observable' | 'assisted' }[];
|
|
761
|
+
/** One provider's current value. Throws `DebugError` code
|
|
762
|
+
* `STATE_PROVIDER_NOT_FOUND` (`data.registered`) for an unknown name. */
|
|
763
|
+
state(name: string): unknown;
|
|
764
|
+
/** One coherent snapshot of every registered provider — a throwing
|
|
765
|
+
* provider contributes `{ __error: String(err) }` for its own key rather
|
|
766
|
+
* than failing the whole snapshot. */
|
|
767
|
+
stateAll(): Record<string, unknown>;
|
|
768
|
+
commands(): DebugCommandInfo[];
|
|
769
|
+
/** Validates `args` against the command's declared Zod tuple (when
|
|
770
|
+
* present), then awaits the registered fn. Throws `DebugError` code
|
|
771
|
+
* `DEBUG_COMMAND_NOT_REGISTERED` for an unknown name,
|
|
772
|
+
* `DEBUG_COMMAND_ARGS_INVALID` for a validation failure, or
|
|
773
|
+
* `DEBUG_COMMAND_FAILED` wrapping anything the command itself threw. */
|
|
774
|
+
invoke(name: string, args: unknown[]): Promise<unknown>;
|
|
775
|
+
/** The tick-stamped event ring (cap 500, drops oldest).
|
|
776
|
+
*
|
|
777
|
+
* - `sinceSeq` given (run-4 friction #5 fix): filters to `seq > sinceSeq`
|
|
778
|
+
* — the unambiguous fence for "everything emitted after what I've
|
|
779
|
+
* already observed" (see `TickStampedEvent.seq`'s doc comment for why
|
|
780
|
+
* `tick` alone can't do this: a debug-command emission and a consumer's
|
|
781
|
+
* fence point can share one tick).
|
|
782
|
+
* - Omitted: the whole ring.
|
|
783
|
+
*
|
|
784
|
+
* There was a second, KNOWN-DEFECTIVE `sinceTick` parameter (`tick >
|
|
785
|
+
* sinceTick`, which silently dropped same-tick events) kept for
|
|
786
|
+
* back-compat after the seq fix. It is REMOVED — a fence is `seq` or
|
|
787
|
+
* nothing. */
|
|
788
|
+
events(sinceSeq?: number): TickStampedEvent[];
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Read-only render-debugging introspection (W4b, F11 frame debugger +
|
|
793
|
+
* profiler) — NOT a renderer. `captureFrame` arms the first-party WebGL2
|
|
794
|
+
* capture (`dev/webgl-frame-capture.ts`) and resolves with the NEXT rendered
|
|
795
|
+
* frame's draw list, attributed by wrapping each renderable's `onBeforeRender`
|
|
796
|
+
* (three calls it immediately before that object's main-pass draw; shadow/
|
|
797
|
+
* composer-pass draws stay unattributed BY CONSTRUCTION — an honest gap, not a
|
|
798
|
+
* guess). It REJECTS — never fabricates — if no frame renders within a bounded
|
|
799
|
+
* timeout (a paused/stopped world). `memorySnapshot` is the W3a-style OPTIONAL
|
|
800
|
+
* capability: present only when the mount can supply `renderer.info` (a
|
|
801
|
+
* headless mount omits it, and the editor marks the section absent).
|
|
802
|
+
*/
|
|
803
|
+
export interface RenderDebugAdapter {
|
|
804
|
+
captureFrame(): Promise<FrameCapture>;
|
|
805
|
+
memorySnapshot?(): RenderMemorySnapshot;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* A physics carrier in EITHER surface's vocabulary — node-id keyed for a three
|
|
810
|
+
* world, display-object keyed for a canvas one. Tagged by `keyedBy`; see
|
|
811
|
+
* {@link PhysicsAdapter}'s comment for why, and use the two narrowings below
|
|
812
|
+
* rather than reading the tag directly.
|
|
813
|
+
*/
|
|
814
|
+
export type PhysicsCarrier = PhysicsAdapter | PhysicsAdapter2D;
|
|
815
|
+
|
|
816
|
+
/** The carrier a NODE-ID-keyed consumer may call, or `null` — including when
|
|
817
|
+
* the slot holds a display-keyed carrier, which such a consumer must not
|
|
818
|
+
* call and cannot correctly address. */
|
|
819
|
+
export function nodeKeyedPhysics(
|
|
820
|
+
carrier: PhysicsCarrier | null | undefined,
|
|
821
|
+
): PhysicsAdapter | null {
|
|
822
|
+
if (!carrier) return null;
|
|
823
|
+
return isDisplayKeyedPhysics(carrier) ? null : carrier;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Who drives `key`, asked of the game's own physics carrier by an editor surface that renders the
|
|
828
|
+
* answer. A declared system slot with nothing attached yet (a world whose `<Physics>` mounts late)
|
|
829
|
+
* refuses by name, and a refusal read from an outliner row would take the editor's chrome down with
|
|
830
|
+
* it: to the editor a carrier that cannot answer does not know the node, which `'unresolved'` says.
|
|
831
|
+
*/
|
|
832
|
+
export function physicsOwnerOf<K, O extends string>(carrier: { ownerOf(key: K): O }, key: K): O | 'unresolved' {
|
|
833
|
+
try {
|
|
834
|
+
return carrier.ownerOf(key);
|
|
835
|
+
} catch {
|
|
836
|
+
return 'unresolved';
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/** The carrier a DISPLAY-keyed consumer may call, or `null`. Mirror of
|
|
841
|
+
* {@link nodeKeyedPhysics}. */
|
|
842
|
+
export function displayKeyedPhysics(
|
|
843
|
+
carrier: PhysicsCarrier | null | undefined,
|
|
844
|
+
): PhysicsAdapter2D | null {
|
|
845
|
+
if (!carrier) return null;
|
|
846
|
+
return isDisplayKeyedPhysics(carrier) ? carrier : null;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function isDisplayKeyedPhysics(carrier: PhysicsCarrier): carrier is PhysicsAdapter2D {
|
|
850
|
+
return carrier.keyedBy === 'display';
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* The set of optional subsystem providers a mounted game may expose. Absence of
|
|
855
|
+
* a provider means "capability not supported" — the editor degrades gracefully.
|
|
856
|
+
*/
|
|
857
|
+
export interface SystemAdapters {
|
|
858
|
+
physics?: PhysicsCarrier;
|
|
859
|
+
networking?: NetworkingAdapter;
|
|
860
|
+
navigation?: NavigationAdapter;
|
|
861
|
+
audio?: AudioAdapter;
|
|
862
|
+
camera?: CameraAdapter;
|
|
863
|
+
debug?: DebugAdapter;
|
|
864
|
+
renderDebug?: RenderDebugAdapter;
|
|
865
|
+
}
|