@vgai/engine 0.4.1 → 0.5.0-canary.20260719.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.
Files changed (103) hide show
  1. package/README.md +48 -15
  2. package/package.json +11 -25
  3. package/schemas/engine-capabilities.json +10 -10
  4. package/schemas/entity2d.schema.json +468 -0
  5. package/schemas/mat.schema.json +2 -33
  6. package/schemas/prefab.schema.json +16 -172
  7. package/schemas/scn2d.schema.json +42 -23
  8. package/schemas/vgai-game.schema.json +34 -0
  9. package/schemas/vscn.schema.json +16 -172
  10. package/src/adapter/authoring.ts +152 -2
  11. package/src/adapter/colyseus-networking-adapter.ts +35 -1
  12. package/src/adapter/first-party-systems.ts +7 -1
  13. package/src/adapter/game-adapter.ts +13 -0
  14. package/src/adapter/index.ts +25 -0
  15. package/src/adapter/rapier-physics-adapter.ts +55 -2
  16. package/src/adapter/system-adapter.ts +249 -2
  17. package/src/adapter/vgai-scene-game-adapter.ts +149 -25
  18. package/src/ai/navigation.ts +28 -0
  19. package/src/animation/clip-map.ts +1 -8
  20. package/src/animation/theatre-director.ts +50 -0
  21. package/src/animation/xstate-animation-binding.ts +6 -0
  22. package/src/audio/audio-introspection.ts +290 -0
  23. package/src/audio/tone-context.ts +46 -0
  24. package/src/dev/chrome-trace.ts +153 -0
  25. package/src/dev/performance-profiler.ts +93 -6
  26. package/src/dev/render-debug-adapter.ts +199 -0
  27. package/src/dev/render-memory.ts +243 -0
  28. package/src/dev/webgl-frame-capture.ts +424 -0
  29. package/src/ecs/component-manager.ts +43 -10
  30. package/src/ecs/game-component.ts +39 -10
  31. package/src/input/input-manager.ts +24 -19
  32. package/src/input/input-types.ts +1 -1
  33. package/src/loader.ts +7 -0
  34. package/src/manifest/load.ts +14 -0
  35. package/src/manifest/schema.ts +65 -0
  36. package/src/react/game-state.tsx +1 -1
  37. package/src/render/render-batch-system.ts +26 -12
  38. package/src/render/spark-renderer-lifecycle.ts +64 -0
  39. package/src/runtime/create-runtime.ts +33 -5
  40. package/src/runtime/debug-bridge.ts +5 -5
  41. package/src/runtime/game.ts +102 -20
  42. package/src/runtime/mount-manifest.ts +1 -1
  43. package/src/runtime/render-control.ts +121 -0
  44. package/src/runtime/types.ts +1 -1
  45. package/src/scene/asset-loaders.ts +77 -3
  46. package/src/scene/instance-mesh.ts +25 -0
  47. package/src/scene/material-factory.ts +4 -15
  48. package/src/scene/mesh-shadow.ts +18 -0
  49. package/src/scene/particles-factory.ts +59 -0
  50. package/src/scene/scene-loader.ts +41 -16
  51. package/src/scene/schema/instances.ts +1 -2
  52. package/src/scene/schema/material.ts +83 -94
  53. package/src/scene/schema/mesh.ts +76 -90
  54. package/src/scene/schema/scene-file.ts +1 -2
  55. package/src/scene/user-data.ts +30 -14
  56. package/src/setup/setup-renderer.ts +6 -1
  57. package/src/world2d/asset-paths2d.ts +44 -0
  58. package/src/world2d/collision-2d.ts +7 -14
  59. package/src/world2d/entity2d-asset.ts +22 -0
  60. package/src/world2d/index.ts +27 -2
  61. package/src/world2d/physics2d-transform.ts +173 -0
  62. package/src/world2d/physics2d-units.ts +10 -0
  63. package/src/world2d/pixi-game-adapter.ts +148 -36
  64. package/src/world2d/scene2d-identity.ts +49 -0
  65. package/src/world2d/scene2d-loader.ts +243 -119
  66. package/src/world2d/schema/entity2d.ts +51 -33
  67. package/src/world2d/schema/physics2d.ts +14 -3
  68. package/src/world2d/schema/sprite.ts +32 -4
  69. package/src/world2d/schema/tilemap.ts +26 -9
  70. package/src/world2d/transform-writer-2d.ts +29 -11
  71. package/src/world2d/types.ts +21 -8
  72. package/src/world3d-react/behavior.tsx +138 -0
  73. package/src/world3d-react/engine-bridge.ts +48 -0
  74. package/src/world3d-react/index.ts +44 -0
  75. package/src/world3d-react/r3f-adapter.tsx +303 -0
  76. package/src/world3d-react/world-context.ts +294 -0
  77. package/vendor/realism-effects/LICENSE.md +21 -0
  78. package/vendor/realism-effects/UPSTREAM.md +19 -0
  79. package/vendor/realism-effects/dist/index.cjs +3447 -0
  80. package/vendor/realism-effects/dist/index.d.ts +59 -0
  81. package/vendor/realism-effects/dist/index.js +3434 -0
  82. package/vendor/realism-effects/package.json +23 -0
  83. package/src/character/cloth-sim.ts +0 -533
  84. package/src/character/spring-chain.ts +0 -307
  85. package/src/humanoid/body.ts +0 -663
  86. package/src/humanoid/clips.ts +0 -149
  87. package/src/humanoid/compose.ts +0 -209
  88. package/src/humanoid/generate.ts +0 -189
  89. package/src/humanoid/index.ts +0 -36
  90. package/src/humanoid/schema.ts +0 -108
  91. package/src/humanoid/skeleton.ts +0 -345
  92. package/src/react/humanoid-bake.document.tsx +0 -337
  93. package/src/scene/geometries/index.ts +0 -7
  94. package/src/scene/geometries/terrain.ts +0 -42
  95. package/src/scene/geometry-registry.ts +0 -42
  96. package/src/scene/instance-registry.ts +0 -84
  97. package/src/scene/instancers/grid.ts +0 -38
  98. package/src/scene/instancers/index.ts +0 -7
  99. package/src/scene/material-registry.ts +0 -73
  100. package/src/scene/materials/index.ts +0 -7
  101. package/src/scene/materials/water.ts +0 -56
  102. package/src/world2d/components-2d.ts +0 -86
  103. package/tools/humanoid-bake.tool.ts +0 -274
@@ -9,16 +9,61 @@
9
9
  import type RAPIER from '@dimforge/rapier3d-compat';
10
10
  import type * as THREE from 'three';
11
11
  import type { PhysicsRegistry } from '../physics/physics-registry';
12
+ import type { PhysicsContext } from '../setup/setup-physics';
12
13
  import type { PhysicsAdapter } from './system-adapter';
13
14
  import type { Transform, TransformOwner } from './transform';
14
15
 
15
16
  export function createRapierPhysicsAdapter(
16
17
  registry: PhysicsRegistry,
17
- debugMesh?: THREE.Object3D | null,
18
+ physics?: PhysicsContext | null,
18
19
  ): PhysicsAdapter {
19
20
  /** Saved body types while frozen, so unfreeze can restore them. */
20
21
  const frozen = new Map<RAPIER.RigidBody, number>();
21
22
 
23
+ /**
24
+ * Reused backing store for `contactPoints` (grown ×2 on demand, never
25
+ * shrunk) — the seam contract says the returned view is only valid until
26
+ * the next call, exactly so this never allocates per frame once warm.
27
+ */
28
+ let contactBuf = new Float32Array(64 * 3);
29
+ let contactCount = 0;
30
+
31
+ const pushContact = (x: number, y: number, z: number): void => {
32
+ if ((contactCount + 1) * 3 > contactBuf.length) {
33
+ const grown = new Float32Array(contactBuf.length * 2);
34
+ grown.set(contactBuf);
35
+ contactBuf = grown;
36
+ }
37
+ contactBuf[contactCount * 3] = x;
38
+ contactBuf[contactCount * 3 + 1] = y;
39
+ contactBuf[contactCount * 3 + 2] = z;
40
+ contactCount++;
41
+ };
42
+
43
+ const appendManifoldContacts = (manifold: RAPIER.TempContactManifold): void => {
44
+ const count = manifold.numSolverContacts();
45
+ for (let i = 0; i < count; i++) {
46
+ // Solver contacts are WORLD-space (unlike localContactPoint1/2).
47
+ const p = manifold.solverContactPoint(i);
48
+ if (p) pushContact(p.x, p.y, p.z);
49
+ }
50
+ };
51
+
52
+ const collectContactPoints = (): Float32Array => {
53
+ if (!physics) return contactBuf.subarray(0, 0);
54
+ const world = physics.rapierWorld;
55
+ contactCount = 0;
56
+ for (const [, refs] of registry.entries()) {
57
+ world.contactPairsWith(refs.collider, (other) => {
58
+ // Each touching pair is enumerated from BOTH sides — process it only
59
+ // from the lower-handle side (also skips self).
60
+ if (other.handle <= refs.collider.handle) return;
61
+ world.contactPair(refs.collider, other, appendManifoldContacts);
62
+ });
63
+ }
64
+ return contactBuf.subarray(0, contactCount * 3);
65
+ };
66
+
22
67
  return {
23
68
  ownerOf(o: THREE.Object3D): TransformOwner {
24
69
  const body = registry.get(o)?.body;
@@ -51,6 +96,14 @@ export function createRapierPhysicsAdapter(
51
96
  frozen.delete(body);
52
97
  }
53
98
  },
54
- debugDraw: () => debugMesh ?? null,
99
+ debugDraw: () => physics?.debugMesh ?? null,
100
+ setDebugDrawEnabled(enabled: boolean): void {
101
+ if (!physics) return;
102
+ // Same two fields the in-game KeyP toggle flips — the adapter's render
103
+ // system reads `debugEnabled` and feeds `debugRender()` into the mesh.
104
+ physics.debugEnabled = enabled;
105
+ physics.debugMesh.visible = enabled;
106
+ },
107
+ contactPoints: collectContactPoints,
55
108
  };
56
109
  }
@@ -12,6 +12,8 @@
12
12
  */
13
13
 
14
14
  import type * as THREE from 'three';
15
+ import type { RenderMemorySnapshot } from '../dev/render-memory';
16
+ import type { FrameCapture } from '../dev/webgl-frame-capture';
15
17
  import type { ActionValueType, Vector2 } from '../input/input-types';
16
18
  import type { Transform, TransformOwner } from './transform';
17
19
 
@@ -30,6 +32,20 @@ export interface PhysicsAdapter {
30
32
  unfreeze(o: THREE.Object3D): void;
31
33
  /** Optional debug-draw object (collider wireframes). */
32
34
  debugDraw?(): THREE.Object3D | null;
35
+ /**
36
+ * Optional: turn the engine's per-frame physics debug rendering on/off —
37
+ * the first-party implementation drives Rapier's `debugRender()` into the
38
+ * `debugDraw()` object inside the game's own render system, so an editor
39
+ * overlay toggle costs the editor zero per-frame work (W2a §2.3c).
40
+ */
41
+ setDebugDrawEnabled?(enabled: boolean): void;
42
+ /**
43
+ * Optional: live world-space contact points this frame, as xyz triples.
44
+ * Returns a view over a REUSED backing buffer — valid only until the next
45
+ * call (no per-frame allocation churn; callers copy into their own
46
+ * geometry).
47
+ */
48
+ contactPoints?(): Float32Array;
33
49
  }
34
50
 
35
51
  export interface NetPeer {
@@ -59,7 +75,61 @@ export interface ReplicationStats {
59
75
 
60
76
  export type Unsubscribe = () => void;
61
77
 
62
- /** INTROSPECTION over replication state — NOT a networking transport. */
78
+ /** One captured network message, for the inspector's message log (W3b).
79
+ * `seq` is an adapter-lifetime MONOTONIC counter — the ring-buffer fence a
80
+ * consumer polls with (`messageEvents(sinceSeq)`), mirroring
81
+ * `DebugAdapter.events`' seq discipline. `time` is a `performance.now()`-
82
+ * domain ms timestamp at capture. */
83
+ export interface NetMessageEvent {
84
+ seq: number;
85
+ time: number;
86
+ direction: 'in' | 'out';
87
+ /** Message type/channel (e.g. `'input'`, `'players.add'`, `'state.phase'`). */
88
+ type: string;
89
+ /** Approximate payload size in bytes, when measurable. OMITTED — never
90
+ * fabricated — when the transport doesn't expose it (e.g. Colyseus schema
91
+ * patches observed via the Callbacks API carry no byte count). */
92
+ size?: number;
93
+ /** `true` when the link conditioner (`setConditioning`) dropped this
94
+ * outbound message instead of sending it — logged so the inspector can
95
+ * show the drop honestly rather than silently losing it. */
96
+ dropped?: boolean;
97
+ }
98
+
99
+ /** Send/receive rates for the inspector's sparklines (W3b). The byte fields
100
+ * are OPTIONAL capabilities-within-the-capability: an implementer omits a
101
+ * direction it cannot measure (never reports a fabricated 0). */
102
+ export interface NetRates {
103
+ msgsInPerSec: number;
104
+ msgsOutPerSec: number;
105
+ bytesInPerSec?: number;
106
+ bytesOutPerSec?: number;
107
+ }
108
+
109
+ /** Client-side link-conditioning parameters, applied AT THE ADAPTER SEAM
110
+ * (W3b M3) — the implementer wraps its own transport calls (e.g. delaying/
111
+ * dropping around `room.send`); nobody patches transport internals. */
112
+ export interface NetConditioning {
113
+ /** Fixed added delay per outbound message, ms. */
114
+ latencyMs: number;
115
+ /** Uniform random extra delay in `[0, jitterMs)`, ms. */
116
+ jitterMs: number;
117
+ /** Probability in `[0, 1]` that an outbound message is dropped. */
118
+ packetLoss: number;
119
+ }
120
+
121
+ /**
122
+ * INTROSPECTION over replication state — NOT a networking transport.
123
+ *
124
+ * The optional members are CAPABILITIES in the `SystemAdapters` sense
125
+ * (absence means "not supported" and the editor's Network inspector marks
126
+ * that section absent — it never fabricates data): `getStateSnapshot` feeds
127
+ * the replicated-state tree, `messageEvents` the message log, `getRates` the
128
+ * rate sparklines, and `getConditioning`/`setConditioning` the latency/loss
129
+ * conditioner. The first-party Colyseus factory
130
+ * (`createColyseusNetworkingAdapter`) only attaches each one when its config
131
+ * supplies the accessor, so partial implementers degrade honestly.
132
+ */
63
133
  export interface NetworkingAdapter {
64
134
  peers(): NetPeer[];
65
135
  networkId(o: THREE.Object3D): string | null;
@@ -74,6 +144,22 @@ export interface NetworkingAdapter {
74
144
  getReplicationStats(): ReplicationStats;
75
145
  /** Subscribe to changes in connection state / room / replication stats. */
76
146
  subscribe(cb: () => void): Unsubscribe;
147
+ /** Optional capability: one plain-data (JSON-safe) snapshot of the
148
+ * replicated room state, or `null` when not connected. Colyseus schema is
149
+ * self-describing (`state.toJSON()`), so the first-party implementation
150
+ * is one call — the inspector renders whatever tree comes back. */
151
+ getStateSnapshot?(): unknown;
152
+ /** Optional capability: captured message events with `seq > sinceSeq`
153
+ * (whole ring when omitted). Implementations ring-buffer (drop oldest);
154
+ * the inspector keeps its own consumer-side ring and fences on `seq`. */
155
+ messageEvents?(sinceSeq?: number): NetMessageEvent[];
156
+ /** Optional capability: msg + byte rates for the inspector's sparklines. */
157
+ getRates?(): NetRates;
158
+ /** Optional capability (paired with `setConditioning`): current
159
+ * conditioning parameters. */
160
+ getConditioning?(): NetConditioning;
161
+ /** Optional capability: apply link conditioning at the adapter seam. */
162
+ setConditioning?(c: NetConditioning): void;
77
163
  }
78
164
 
79
165
  /** One action's current value, tagged with its declared shape (F1 `ActionValueType`) so a
@@ -105,14 +191,125 @@ export interface NavPoint {
105
191
  z: number;
106
192
  }
107
193
 
194
+ /**
195
+ * Navmesh bake parameters — the recast `rcConfig` vocabulary the scene schema
196
+ * (`SceneNavigationSchema`) also mirrors. All optional; an implementation
197
+ * applies its own defaults. `walkableHeight`/`walkableClimb`/`walkableRadius`
198
+ * are in VOXEL units (multiples of `cellHeight`/`cellSize`), per recast.
199
+ */
200
+ export interface NavBakeParams {
201
+ cellSize?: number | undefined;
202
+ cellHeight?: number | undefined;
203
+ walkableSlopeAngle?: number | undefined;
204
+ walkableHeight?: number | undefined;
205
+ walkableClimb?: number | undefined;
206
+ walkableRadius?: number | undefined;
207
+ maxEdgeLen?: number | undefined;
208
+ maxSimplificationError?: number | undefined;
209
+ minRegionArea?: number | undefined;
210
+ mergeRegionArea?: number | undefined;
211
+ }
212
+
213
+ /** One crowd agent's live state — plain data (no engine/WASM handles), for the
214
+ * editor's play-mode crowd debug draw. */
215
+ export interface NavCrowdAgentState {
216
+ position: NavPoint;
217
+ velocity: NavPoint;
218
+ radius: number;
219
+ height: number;
220
+ }
221
+
108
222
  /**
109
223
  * Inspect a game's navigation mesh: whether one is built, query a path, and get a
110
224
  * debug overlay. Coordination/introspection only — the editor doesn't own the build.
225
+ *
226
+ * The optional members are CAPABILITIES in the `SystemAdapters` sense (absence
227
+ * means "not supported", the editor degrades): `bake`/`exportData` let the
228
+ * editor's Navigation panel (re)build and persist a navmesh through the seam —
229
+ * the first-party recast implementation (`createNavigationAdapter`) is the
230
+ * blessed implementer — while an external game that owns its build simply
231
+ * omits them. `crowdAgents` feeds the play-mode crowd debug draw.
111
232
  */
112
233
  export interface NavigationAdapter {
113
234
  hasNavMesh(): boolean;
114
235
  findPath(start: NavPoint, end: NavPoint): NavPoint[];
115
236
  debugMesh(scene: THREE.Scene): THREE.Object3D | null;
237
+ /** (Re)build the navmesh from source meshes. Synchronous in the blessed
238
+ * recast/WASM implementation; returns `false` on a failed bake. */
239
+ bake?(meshes: THREE.Mesh[], params?: NavBakeParams): boolean;
240
+ /** Serialize the current navmesh to a binary blob (the editor persists it
241
+ * as the scene's `.navmesh` sidecar). Throws when nothing is built. */
242
+ exportData?(): Uint8Array;
243
+ /** Live crowd-agent snapshots for debug draw. Empty when no crowd. */
244
+ crowdAgents?(): NavCrowdAgentState[];
245
+ }
246
+
247
+ /** One node in an audio-graph snapshot (W3c, the Audio debugger tab). Edges
248
+ * point DOWNSTREAM: `outputs` holds the ids of nodes this node's output
249
+ * feeds (ultimately reaching the `destination` node). */
250
+ export interface AudioGraphNode {
251
+ id: string;
252
+ /** Node class/constructor name (`'GainNode'`, `'Gain'` for a Tone node,
253
+ * `'AudioDestinationNode'`, …) — whatever the implementation truthfully
254
+ * knows, never a prettified fabrication. */
255
+ type: string;
256
+ /** Human-facing label (`'Master'`, `'Music bus'`) when one exists. */
257
+ label?: string;
258
+ /** ids of downstream nodes this node's output connects to. */
259
+ outputs: string[];
260
+ /** Lifecycle/playback state when truthfully known (`'running'`,
261
+ * `'suspended'`, `'started'`…). OMITTED — never fabricated — otherwise. */
262
+ state?: string;
263
+ }
264
+
265
+ /** Musical-transport snapshot for the Audio debugger's transport strip
266
+ * (W3c). The first-party implementation reads Tone's real transport. */
267
+ export interface AudioTransportState {
268
+ state: 'started' | 'stopped' | 'paused';
269
+ /** Playback position along the transport's timeline, seconds. */
270
+ seconds: number;
271
+ bpm: number;
272
+ /** Musical position (`Bars:Beats:Sixteenths`) when a musical grid exists. */
273
+ position?: string;
274
+ }
275
+
276
+ /** One bus level sample: `level` is linear RMS over the implementation's most
277
+ * recent analysis window (0 = silence; ~1 = full-scale). */
278
+ export interface AudioMeterFrame {
279
+ id: string;
280
+ label: string;
281
+ level: number;
282
+ }
283
+
284
+ /** A live metering session. Metering costs real audio nodes (Tone.Meter /
285
+ * AnalyserNode taps), so it is ACQUIRED for exactly as long as a meter UI is
286
+ * visible and MUST be released via `dispose()` — the first-party handle
287
+ * disconnects its analyser taps there (no leaked nodes after Stop). */
288
+ export interface AudioMeterHandle {
289
+ /** Current level per bus. Cheap enough to poll at UI rate (~10 Hz). */
290
+ read(): AudioMeterFrame[];
291
+ /** Release the metering nodes. Idempotent. */
292
+ dispose(): void;
293
+ }
294
+
295
+ /** One entry in the audio event ring (W3c). `seq` is an adapter-lifetime
296
+ * MONOTONIC counter — the same ring-buffer fence discipline as
297
+ * `NetMessageEvent.seq` / `DebugAdapter.events`. `time` is a
298
+ * `performance.now()`-domain ms timestamp at capture. `'error'` is reserved
299
+ * for implementations that can truthfully report one (the first-party
300
+ * adapter has no honest audio-error source today and never emits it). */
301
+ export interface AudioDebugEvent {
302
+ seq: number;
303
+ time: number;
304
+ kind:
305
+ | 'transport-start'
306
+ | 'transport-stop'
307
+ | 'transport-pause'
308
+ | 'context-statechange'
309
+ | 'mute'
310
+ | 'unmute'
311
+ | 'error';
312
+ detail?: string;
116
313
  }
117
314
 
118
315
  /**
@@ -123,14 +320,46 @@ export interface NavigationAdapter {
123
320
  * cannot"). Absence on a world's `mounted.systems` means exactly that:
124
321
  * `Game.play.pause()` reports it loudly once per world rather than silently
125
322
  * leaving that world's audio playing under a "paused" game.
323
+ *
324
+ * The OPTIONAL methods below are the W3c read-only INTROSPECTION capabilities
325
+ * feeding the editor's Audio debugger tab (graph / transport / meters /
326
+ * events). They follow the W3a degradation ladder: an implementer omits what
327
+ * it cannot truthfully provide, and the editor marks that section absent —
328
+ * never fabricated. The full MIXER (sends/effects/gain editing) is SQ-4-gated
329
+ * and deliberately NOT part of this seam. The first-party implementation over
330
+ * the engine's own Tone/Web-Audio stack is
331
+ * `packages/engine/src/audio/audio-introspection.ts`; pre-existing
332
+ * mute-only registrants keep compiling untouched (every addition is
333
+ * optional).
126
334
  */
127
335
  export interface AudioAdapter {
336
+ /** Resume/unlock this adapter's real audio context from a user gesture.
337
+ * Optional for external adapters that do not own a resumable Web Audio
338
+ * context; first-party edit and play adapters both provide it. */
339
+ resume?(): void;
128
340
  /** `true` silences this world's audio; `false` restores it to whatever
129
341
  * level it was at before silencing (an implementer's own concern — the
130
342
  * first-party adapter restores its master-gain value, not a hardcoded 1). */
131
343
  setMuted(muted: boolean): void;
132
344
  /** Current muted state, for inspection (editor mute UI, proofs). */
133
345
  isMuted(): boolean;
346
+ /** Optional: current audio-graph snapshot (active nodes + downstream
347
+ * edges). The first-party adapter reports the engine bus hierarchy plus
348
+ * every Tone node routed through `connectToneBusToMasterGain` — it cannot
349
+ * see a game's private Tone-internal wiring and does not pretend to. */
350
+ graphSnapshot?(): AudioGraphNode[];
351
+ /** Optional: musical-transport snapshot. Returns `null` — an honest
352
+ * "this world has no musical transport" — when none is active (e.g. the
353
+ * first-party adapter when the game never bridged Tone onto this world's
354
+ * context). Method ABSENT means the capability itself is unsupported. */
355
+ transportState?(): AudioTransportState | null;
356
+ /** Optional: begin a per-bus metering session. Returns `null` when the
357
+ * environment cannot meter (headless world with no real AudioContext) —
358
+ * the UI says so rather than showing frozen zeros. */
359
+ acquireMeters?(): AudioMeterHandle | null;
360
+ /** Optional: the audio event ring (bounded, drops oldest). `sinceSeq`
361
+ * filters to `seq > sinceSeq` — the consumer's fence. */
362
+ audioEvents?(sinceSeq?: number): AudioDebugEvent[];
134
363
  }
135
364
 
136
365
  /** One tick-stamped debug event (`ctx.debug.emit`, spec §3.3): `tick`/`simT`
@@ -170,7 +399,7 @@ export interface DebugCommandInfo {
170
399
  * game-scoped introspection + actuation over whatever a game registers via
171
400
  * `ctx.debug` (`registerStateProvider`/`registerCommand`/`emit`). NOT a
172
401
  * gameplay API — this is the one seam the debug bridge, the editor's Debug
173
- * Console/State Watch panels, and `@vgai/probe` all read/drive through.
402
+ * Console/State Watch panels, and `@vgai/e2e` all read/drive through.
174
403
  */
175
404
  export interface DebugAdapter {
176
405
  providers(): { name: string; tier: 'observable' | 'assisted' }[];
@@ -205,6 +434,23 @@ export interface DebugAdapter {
205
434
  events(sinceTick?: number, sinceSeq?: number): TickStampedEvent[];
206
435
  }
207
436
 
437
+ /**
438
+ * Read-only render-debugging introspection (W4b, F11 frame debugger +
439
+ * profiler) — NOT a renderer. `captureFrame` arms the first-party WebGL2
440
+ * capture (`dev/webgl-frame-capture.ts`) and resolves with the NEXT rendered
441
+ * frame's draw list, attributed by wrapping each renderable's `onBeforeRender`
442
+ * (three calls it immediately before that object's main-pass draw; shadow/
443
+ * composer-pass draws stay unattributed BY CONSTRUCTION — an honest gap, not a
444
+ * guess). It REJECTS — never fabricates — if no frame renders within a bounded
445
+ * timeout (a paused/stopped world). `memorySnapshot` is the W3a-style OPTIONAL
446
+ * capability: present only when the mount can supply `renderer.info` (a
447
+ * headless mount omits it, and the editor marks the section absent).
448
+ */
449
+ export interface RenderDebugAdapter {
450
+ captureFrame(): Promise<FrameCapture>;
451
+ memorySnapshot?(): RenderMemorySnapshot;
452
+ }
453
+
208
454
  /**
209
455
  * The set of optional subsystem providers a mounted game may expose. Absence of
210
456
  * a provider means "capability not supported" — the editor degrades gracefully.
@@ -217,4 +463,5 @@ export interface SystemAdapters {
217
463
  navigation?: NavigationAdapter;
218
464
  audio?: AudioAdapter;
219
465
  debug?: DebugAdapter;
466
+ renderDebug?: RenderDebugAdapter;
220
467
  }