@threenative/core 0.2.0 → 0.3.1

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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/capabilities.json +5292 -0
  4. package/dist/assets-kyoF7JlJ.d.ts +103 -0
  5. package/dist/audio-BFiGneTL.d.ts +156 -0
  6. package/dist/canvas-layer-BLVijiUJ.d.ts +62 -0
  7. package/dist/game-XGrTzapq.d.ts +1166 -0
  8. package/dist/gpu-readback-D2iRvoe9.d.ts +112 -0
  9. package/dist/hot.d.ts +20 -2
  10. package/dist/hot.js +14 -2
  11. package/dist/index.d.ts +2108 -55
  12. package/dist/index.js +15645 -2222
  13. package/dist/net.d.ts +65 -0
  14. package/dist/net.js +643 -0
  15. package/dist/playtest.d.ts +37 -4
  16. package/dist/playtest.js +246 -546
  17. package/dist/react.d.ts +177 -0
  18. package/dist/react.js +635 -0
  19. package/dist/renderer-C6hqZpoG.d.ts +770 -0
  20. package/dist/ui-layer.d.ts +306 -0
  21. package/dist/ui-layer.js +425 -0
  22. package/dist/world.d.ts +254 -0
  23. package/dist/world.js +2686 -0
  24. package/gpl/LICENSE.GPL +117 -0
  25. package/gpl/convert.py +192 -0
  26. package/gpl/recipes/_common.py +169 -0
  27. package/gpl/recipes/bake_ao.py +111 -0
  28. package/gpl/recipes/decimate.py +64 -0
  29. package/gpl/recipes/retarget.py +131 -0
  30. package/gpl/recipes/unwrap.py +71 -0
  31. package/mcp/assets.mjs +5 -0
  32. package/mcp/blender-server.mjs +632 -0
  33. package/mcp/blender.mjs +27 -0
  34. package/mcp/engine-server.mjs +501 -0
  35. package/mcp/engine.mjs +31 -0
  36. package/mcp/install.d.mts +37 -0
  37. package/mcp/install.mjs +145 -0
  38. package/mcp/launch.mjs +72 -0
  39. package/mcp/sculpt.mjs +5 -0
  40. package/mcp/servers.d.mts +34 -0
  41. package/mcp/servers.mjs +160 -0
  42. package/package.json +76 -6
  43. package/patches/three@0.185.1.patch +522 -0
  44. package/scripts/apply-three-patch.mjs +297 -0
  45. package/scripts/ensure-mcp.mjs +43 -0
  46. package/scripts/postinstall.mjs +6 -0
  47. package/dist/audio-CEAw0w5y.d.ts +0 -35
  48. package/dist/game-DRt1Qhq3.d.ts +0 -429
@@ -0,0 +1,770 @@
1
+ import { Object3D, Matrix4, Camera } from 'three';
2
+ import { MRTNode, Node } from 'three/webgpu';
3
+
4
+ /**
5
+ * Per-presented-frame cost attribution, on by default, for every platform.
6
+ *
7
+ * A device once read 18.3 fps with nothing in the repository able to say where the frame went;
8
+ * the answer took a hand-written 468-line probe that monkey-patched `requestAnimationFrame` and
9
+ * `renderer.render` from inside the game. That probe had to guess the phase boundaries because it
10
+ * lived outside the loop. This lives inside the loop, so it knows them: the framework owns the
11
+ * simulation/render split, the render call and the frame's start and end, and no game should ever
12
+ * write this again.
13
+ *
14
+ * Two consumers, one measurement:
15
+ *
16
+ * - a windowed `TN_FRAME_BUDGET` marker line, printed periodically on stdout (and therefore in
17
+ * logcat on Android), so a cold agent reading standard device-lane output sees the attribution
18
+ * without instrumenting anything;
19
+ * - a per-frame sample carried into the playtest render series, so `assert.performance` can bound
20
+ * an fps floor and a per-phase ceiling and a mobile regression is a red gate rather than a vibe.
21
+ *
22
+ * Fail closed: malformed options throw at construction rather than silently disabling the budget,
23
+ * and a phase that was never measured reports zero samples so a consumer asserting on it fails
24
+ * instead of skipping.
25
+ */
26
+ /** Marker printed once per report window. */
27
+ declare const FRAME_BUDGET_MARKER = "TN_FRAME_BUDGET";
28
+ /** Marker printed the moment a gap between presented frames exceeds `hitchMs`. */
29
+ declare const FRAME_HITCH_MARKER = "TN_FRAME_HITCH";
30
+ /**
31
+ * The named parts of one presented frame. They partition the frame: `hostGap` is the time before
32
+ * the callback (present wait plus whatever the host did between callbacks), and `update`,
33
+ * `render`, `overlay` and `residual` sum to the callback's own duration.
34
+ */
35
+ declare const FRAME_BUDGET_PHASES: readonly ["hostGap", "update", "render", "overlay", "residual"];
36
+ type FrameBudgetPhase = (typeof FRAME_BUDGET_PHASES)[number];
37
+ /** One frame's cost, split by phase. Every field is milliseconds. */
38
+ interface IFramePhaseSample {
39
+ readonly hostGap: number;
40
+ readonly update: number;
41
+ readonly render: number;
42
+ readonly overlay: number;
43
+ readonly residual: number;
44
+ }
45
+ /**
46
+ * What the frame the window measured was actually drawn at.
47
+ *
48
+ * A resolution number without its sample count does not describe an image, and neither of them
49
+ * describes anything at all unless the window that carries the fps also carries them. This is
50
+ * reported whether the scale was pinned by the game or chosen by the engine: turning the
51
+ * convention off does not turn its measurement off.
52
+ */
53
+ interface IFrameSurfaceState {
54
+ /** The applied drawing-buffer scale, in `(0, 1]`. */
55
+ readonly resolutionScale: number;
56
+ /**
57
+ * `"pinned"` when the game fixed the number, `"auto"` when the engine chose it, and
58
+ * `"auto-pinned"` when the engine chose it and then stopped moving it — the oscillation guard
59
+ * holding a rung is a different state from a game that pinned one, and reads as one.
60
+ */
61
+ readonly scaleSource: "pinned" | "auto" | "auto-pinned";
62
+ /** Multisample count of the 3D drawing buffer; 1 when sampling is off. */
63
+ readonly sampleCount: number;
64
+ readonly drawingBufferWidth: number;
65
+ readonly drawingBufferHeight: number;
66
+ /**
67
+ * True when the scaler is at its lowest rung and the tail is still over budget.
68
+ *
69
+ * A window that reports 0.23 and nothing else reads as a met budget at a low resolution. It is
70
+ * the opposite: the engine ran out of room and the game is still missing its target. Always
71
+ * false under a pinned scale, which has no floor to reach.
72
+ */
73
+ readonly atFloor: boolean;
74
+ /** Compilation overlapped the measured window; automatic scaling waits for a clean window. */
75
+ readonly compiling?: boolean;
76
+ }
77
+ interface IFrameBudgetSummary {
78
+ readonly samples: number;
79
+ readonly mean: number;
80
+ readonly p50: number;
81
+ readonly p95: number;
82
+ readonly p99: number;
83
+ readonly max: number;
84
+ }
85
+ interface IFrameBudgetWindow {
86
+ /** 1 for the first reported window, incrementing thereafter. */
87
+ readonly window: number;
88
+ /** Presented frames counted in this window, hitches excluded. */
89
+ readonly frames: number;
90
+ /** Frames excluded from the window because their present gap exceeded `hitchMs`. */
91
+ readonly hitches: number;
92
+ /** Derived from the mean presented interval: the number a player would read off a counter. */
93
+ readonly fps: number;
94
+ /** Interval between presented frames — the honest frame period. */
95
+ readonly presented: IFrameBudgetSummary;
96
+ /** Duration of the frame callback itself, entry to exit. */
97
+ readonly frame: IFrameBudgetSummary;
98
+ /** Fixed simulation steps executed in the callback. */
99
+ readonly substeps: IFrameBudgetSummary;
100
+ readonly phases: Readonly<Record<FrameBudgetPhase, IFrameBudgetSummary>>;
101
+ /** Each phase's mean as a fraction of the mean presented interval. */
102
+ readonly shares: Readonly<Record<FrameBudgetPhase, number>>;
103
+ /**
104
+ * The resolution and sampling this window's frames were drawn at, when the loop reported one.
105
+ * Absent rather than defaulted: a consumer asserting on it must fail loudly instead of reading
106
+ * a fabricated `1.0` that no frame was ever drawn at.
107
+ */
108
+ readonly surface?: IFrameSurfaceState;
109
+ /**
110
+ * GPU milliseconds for a frame in this window, from `timestamp-query`, when the adapter has it.
111
+ *
112
+ * Absent rather than zero when there is nothing to report: an adapter without timestamps and a
113
+ * frame that genuinely cost no GPU time are different facts, and a zero would merge them.
114
+ */
115
+ readonly gpuMs?: number;
116
+ /** Age of the resolved GPU timestamp in Three.js frame IDs; absent means unobservable. */
117
+ readonly gpuAgeFrames?: number;
118
+ }
119
+ interface IFrameBudgetOptions {
120
+ /** Presented frames per report window. Default 300. */
121
+ readonly reportEvery?: number;
122
+ /** A present gap at or above this is a hitch, not a frame. Default 2000 ms. */
123
+ readonly hitchMs?: number;
124
+ /** Ring capacity per series. Default 1024. */
125
+ readonly capacity?: number;
126
+ /** Where marker lines go. Default `console.log`. */
127
+ readonly report?: (line: string) => void;
128
+ /** Wall clock for hitch markers. Default `Date.now`. */
129
+ readonly wallClock?: () => number;
130
+ /**
131
+ * Called with each completed window, after its marker line. A HUD reads it to show the split
132
+ * on screen; a measurement run reads it to advance in lockstep with the instrument instead of
133
+ * guessing when a window closed.
134
+ */
135
+ readonly onWindow?: (window: IFrameBudgetWindow) => void;
136
+ /**
137
+ * Reads what the frames were drawn at, called once per reported window. Wired by the frame
138
+ * loop, which is the only place that knows both the renderer and the window boundary.
139
+ */
140
+ readonly readSurface?: () => IFrameSurfaceState;
141
+ /** Reads the last resolved GPU frame time, called once per reported window. */
142
+ readonly readGpuMs?: () => number | undefined;
143
+ /** Reads the successful GPU query frame age, not the age of the last resolve attempt. */
144
+ readonly readGpuAgeFrames?: () => number | undefined;
145
+ }
146
+ /**
147
+ * Accumulates one frame at a time and reports windowed attribution.
148
+ *
149
+ * The caller is the frame loop; the sequence per frame is
150
+ * `beginFrame` → `markSimulationEnd` → (`addRender` / `addOverlay`) → `endFrame`.
151
+ * Calling them out of order throws rather than producing a plausible-looking split.
152
+ */
153
+ declare class FrameBudget {
154
+ #private;
155
+ readonly reportEvery: number;
156
+ readonly hitchMs: number;
157
+ constructor(options?: IFrameBudgetOptions);
158
+ /**
159
+ * @param timestampMs the frame timestamp the host handed the callback — the presented-frame
160
+ * clock, which is not the same as `nowMs` and is what the interval between frames comes from.
161
+ * @param nowMs the monotonic clock at callback entry.
162
+ */
163
+ beginFrame(timestampMs: number, nowMs: number): void;
164
+ /** The boundary between the fixed-step simulation and everything the render phase does. */
165
+ markSimulationEnd(nowMs: number, substeps: number): void;
166
+ addRender(ms: number): void;
167
+ addOverlay(ms: number): void;
168
+ /**
169
+ * Closes the frame and returns its phase split, or `undefined` when the frame was a hitch and
170
+ * therefore excluded — a 27-second startup stall is not a frame time and must not enter a
171
+ * percentile anybody is asked to act on.
172
+ *
173
+ * `wantSample` is false when the caller is going to discard the split, which is the default
174
+ * shipping configuration. The object is small but it escapes this method, so V8 cannot scalar-
175
+ * replace it, and building one per frame is one dead allocation per frame in every game. The
176
+ * window meters below are pushed either way: turning the sample off must not turn measurement
177
+ * off, and `window()` reads the same numbers whichever way this is called.
178
+ */
179
+ endFrame(nowMs: number, wantSample?: boolean): IFramePhaseSample | undefined;
180
+ /** Reads the window in progress without disturbing it. */
181
+ window(): IFrameBudgetWindow;
182
+ }
183
+
184
+ /**
185
+ * A bounded observation of the pipelines a Three.js renderer actually asked the GPU device to make.
186
+ *
187
+ * This module deliberately knows the shape of Three's private render object, but not its cache-key
188
+ * algorithm. Three owns the identity; the collector only reads the object at the backend boundary
189
+ * where a cache miss becomes a real device creation. That keeps a material label from becoming a
190
+ * pipeline key and makes the same observation useful on browser WebGPU and the native binding.
191
+ *
192
+ * The backend boundary alone is not the whole device, and a census that claims otherwise is
193
+ * wrong by exactly the pipelines it cannot see: Three's texture pass utils build mipmap transfer
194
+ * pipelines straight on the device, which is why a Bayview native capture logged 94 device
195
+ * creations against 92 renderer events. So the device's own creation methods are observed too,
196
+ * with creations reached through the wrapped backend attributed to their backend event rather
197
+ * than counted twice, and a direct creation reported with the label the device was given, an
198
+ * explicitly unknown pass and unknown provenance rather than the render object that happened to
199
+ * be drawing when a texture uploaded.
200
+ */
201
+ declare const PIPELINE_CENSUS_VERSION: 1;
202
+ declare const PIPELINE_CENSUS_CAPABILITY: "runtime.pipelineCensus";
203
+ declare const DEFAULT_PIPELINE_CENSUS_LIMIT = 512;
204
+ type PipelineCensusStatus = "created" | "failed" | "pending";
205
+ type PipelineCensusMode = "sync" | "async";
206
+ interface IPipelineShaderObservation {
207
+ readonly bytes: number;
208
+ readonly hash: string;
209
+ }
210
+ interface IPipelineProvenance {
211
+ readonly material?: {
212
+ readonly id?: number;
213
+ readonly name?: string;
214
+ readonly type?: string;
215
+ };
216
+ readonly object?: {
217
+ readonly id?: number;
218
+ readonly name?: string;
219
+ readonly uuid?: string;
220
+ };
221
+ /** True when no stable material/object label was available at the creation boundary. */
222
+ readonly unknown: boolean;
223
+ }
224
+ interface IPipelineCensusEvent {
225
+ /** Monotonic sequence, useful for detecting a dropped event in a merged native log. */
226
+ readonly sequence: number;
227
+ /** Three's program identity: the generated vertex/fragment source pair. */
228
+ readonly programIdentity: string;
229
+ /** Backend identity, separate from the program identity and from the material label. */
230
+ readonly pipelineIdentity: string;
231
+ readonly kind: "compute" | "render";
232
+ readonly pass: string;
233
+ readonly mode: PipelineCensusMode;
234
+ readonly status: PipelineCensusStatus;
235
+ readonly vertex?: IPipelineShaderObservation;
236
+ readonly fragment?: IPipelineShaderObservation;
237
+ readonly compute?: IPipelineShaderObservation;
238
+ readonly provenance: IPipelineProvenance;
239
+ /** The label the device was handed, when one was observed. A label is never read as a pass. */
240
+ readonly label?: string;
241
+ /** Structural inputs observed beside the generated shader, not a guessed reason. */
242
+ readonly reasons: readonly string[];
243
+ /** Host monotonic milliseconds relative to the capture origin. */
244
+ readonly startedMs: number;
245
+ readonly settledMs?: number;
246
+ /** Synchronous device-call wall time; absent for an async promise. */
247
+ readonly serviceMs?: number;
248
+ /** Promise latency is retained as latency, never labelled native compiler time. */
249
+ readonly promiseMs?: number;
250
+ readonly error?: string;
251
+ /** Whether this event settled before the first present boundary. */
252
+ readonly beforeFirstPresent?: boolean;
253
+ }
254
+ interface IPipelineCensusCounts {
255
+ readonly lookups: number;
256
+ readonly creations: number;
257
+ readonly failures: number;
258
+ readonly pending: number;
259
+ readonly uniquePrograms: number;
260
+ readonly uniquePipelines: number;
261
+ readonly recordedEvents: number;
262
+ readonly droppedEvents: number;
263
+ /**
264
+ * Every creation seen at the device, including those attributed to a backend event. Absent
265
+ * when the device could not be observed at all, so no reader mistakes silence for zero.
266
+ */
267
+ readonly deviceCreations?: number;
268
+ /** Creations seen only at the device, never reached through the wrapped backend methods. */
269
+ readonly directCreations?: number;
270
+ }
271
+ interface IPipelineCensus {
272
+ readonly version: typeof PIPELINE_CENSUS_VERSION;
273
+ readonly complete: boolean;
274
+ readonly overflowed: boolean;
275
+ readonly unsupported: boolean;
276
+ readonly limit: number;
277
+ readonly clock: {
278
+ readonly originMs: number;
279
+ readonly source: "performance" | "date";
280
+ };
281
+ readonly build: {
282
+ readonly identity: string;
283
+ };
284
+ readonly adapter: {
285
+ readonly identity: string;
286
+ readonly thermal: string;
287
+ };
288
+ readonly backend: {
289
+ readonly kind: "webgpu" | "webgl2";
290
+ readonly identity: string;
291
+ };
292
+ readonly firstPresent?: {
293
+ readonly boundaryMs: number;
294
+ readonly eventsSettled: number;
295
+ };
296
+ readonly counts: IPipelineCensusCounts;
297
+ readonly events: readonly IPipelineCensusEvent[];
298
+ readonly incompleteReasons: readonly string[];
299
+ }
300
+ interface IPipelineCensusOptions {
301
+ readonly adapterIdentity?: string;
302
+ readonly buildIdentity?: string;
303
+ readonly kind: "webgpu" | "webgl2";
304
+ readonly limit?: number;
305
+ readonly now?: () => number;
306
+ readonly backendIdentity?: string;
307
+ readonly thermalIdentity?: string;
308
+ }
309
+ interface IRendererHookTarget {
310
+ backend?: unknown;
311
+ renderObject?: (...args: unknown[]) => unknown;
312
+ }
313
+ /**
314
+ * Create and install a bounded census. The returned object is intentionally small: renderers own
315
+ * lifecycle and callers only need `snapshot()`, `withRenderObject()`, and `firstPresent()`.
316
+ */
317
+ declare function createPipelineCensus(options: IPipelineCensusOptions): PipelineCensus;
318
+ declare class PipelineCensus {
319
+ #private;
320
+ constructor(options: IPipelineCensusOptions);
321
+ /** Install the backend creation hooks and the render-object context wrapper once. */
322
+ install(backendValue: unknown): () => void;
323
+ /** Install every renderer-side hook owned by this capture. */
324
+ installRenderer(renderer: IRendererHookTarget): void;
325
+ /** Restore private hooks while retaining the immutable capture already recorded. */
326
+ dispose(): void;
327
+ /** Wrap Three's public render-object method so compileAsync retains the authored pass id. */
328
+ withRenderObject<T>(args: readonly unknown[], callback: () => T): T;
329
+ /** Mark the first renderer render call as the first-present boundary. */
330
+ firstPresent(): void;
331
+ snapshot(): IPipelineCensus;
332
+ }
333
+
334
+ /** The marker shared by alpha-antialiasing logs, playtests, and native diagnostics. */
335
+ declare const ALPHA_ANTIALIASING_MARKER = "TN_ALPHA_ANTIALIASING";
336
+ interface IAlphaAntialiasingReport {
337
+ /** True once at least one cutout material has been put on the coverage mask. */
338
+ readonly applied: boolean;
339
+ readonly marker: typeof ALPHA_ANTIALIASING_MARKER;
340
+ /** Empty when applied; otherwise why the convention did nothing, and what to change. */
341
+ readonly reason: string;
342
+ /** The sample count the decision was made against. One means there is no coverage mask. */
343
+ readonly sampleCount: number;
344
+ /** How many distinct cutout materials this renderer has converted. */
345
+ readonly materials: number;
346
+ }
347
+
348
+ /** The output name shared by the scene pass and temporal consumers. */
349
+ declare const VELOCITY_OUTPUT_NAME = "velocity";
350
+ /**
351
+ * The pass surface needed to add and read the shared screen-space output.
352
+ *
353
+ * Keeping this structural lets the same seam work with Three's `PassNode` and a native adapter
354
+ * without making the game depend on either implementation.
355
+ */
356
+ interface IVelocityRenderPass {
357
+ getMRT(): MRTNode | null;
358
+ getTextureNode(name?: string): Node;
359
+ setMRT(value: MRTNode | null): unknown;
360
+ }
361
+ /** The symbol read by the patched Three.js instance accessor. */
362
+ declare const VELOCITY_PREVIOUS_INSTANCE_MATRICES: unique symbol;
363
+ /** The symbol read by the patched Three.js velocity accessor for rigid history. */
364
+ declare const VELOCITY_PREVIOUS_WORLD_MATRIX: unique symbol;
365
+ /** The symbol read by the patched Three.js skinning accessor for bone history. */
366
+ declare const VELOCITY_PREVIOUS_BONE_MATRICES: unique symbol;
367
+ /**
368
+ * Adds the velocity output to a scene pass without creating its texture until it is read.
369
+ *
370
+ * The existing outputs remain intact. A pass with no prior MRT receives the normal colour output
371
+ * as well, so adding history does not remove the frame's visible colour.
372
+ * @situation add a velocity target before a temporal stage consumes a scene pass
373
+ * @constraint call only when a temporal stage is active
374
+ */
375
+ declare function ensureVelocityOutput(pass: IVelocityRenderPass): MRTNode;
376
+ /**
377
+ * Returns the velocity texture node after ensuring the pass writes it.
378
+ * @situation read screen-space motion for a temporal stage
379
+ */
380
+ declare function velocityTexture(pass: IVelocityRenderPass): Node;
381
+ /**
382
+ * Gives a composed TSL graph the velocity source used by temporal nodes.
383
+ * Plain game values are returned unchanged, which keeps the chain's diagnostic seam lightweight.
384
+ * @situation hand the provisioned velocity source through a composed temporal graph
385
+ */
386
+ declare function withVelocityContext<T>(node: T, source: Node): T;
387
+ /**
388
+ * Captures instance matrices at the framework's pre-render boundary and enables previous data on
389
+ * every renderable in the graph. The previous snapshot is never the array the game writes next.
390
+ * @situation retain previous transforms for animated and instanced renderables
391
+ * @constraint call `update()` after gameplay writes and `commit()` after the render
392
+ */
393
+ declare class VelocityTracker {
394
+ #private;
395
+ /** Schedule the committed snapshot that the current colour and velocity frame must read. */
396
+ update(root: Object3D): void;
397
+ /** Commit transforms after the renderer has consumed the scheduled velocity frame. */
398
+ commit(root: Object3D): void;
399
+ /** Restore caller-owned flags and release all retained frame snapshots. */
400
+ clear(): void;
401
+ private enable;
402
+ private captureWorld;
403
+ private captureBones;
404
+ private captureInstance;
405
+ private captureBatch;
406
+ private commitInstance;
407
+ private restore;
408
+ }
409
+ /**
410
+ * Reads the scheduled previous instance/sub-draw array for conformance tests and renderer adapters.
411
+ * @situation inspect the previous instance frame at a renderer adapter boundary
412
+ */
413
+ declare function readVelocityPreviousMatrices(object: Object3D): Float32Array | undefined;
414
+ /**
415
+ * Reads the scheduled previous world matrix for conformance tests and renderer adapters.
416
+ * @situation inspect the previous rigid transform at a renderer adapter boundary
417
+ */
418
+ declare function readVelocityPreviousWorldMatrix(object: Object3D): Matrix4 | undefined;
419
+ /**
420
+ * Reads the scheduled previous bone array for conformance tests and renderer adapters.
421
+ * @situation inspect the previous skinned pose at a renderer adapter boundary
422
+ */
423
+ declare function readVelocityPreviousBoneMatrices(object: Object3D): Float32Array | undefined;
424
+
425
+ /** The marker shared by render-chain logs, playtests, and native diagnostics. */
426
+ declare const RENDER_CHAIN_MARKER = "TN_RENDER_CHAIN";
427
+ /** Canonical order for the stages a game may request. */
428
+ declare const RENDER_CHAIN_STAGE_ORDER: readonly ["probeVolume", "ambientOcclusion", "ssgi", "godRays", "ssr", "denoise", "temporalReproject", "taa", "traa", "motionBlur", "sharpen", "bloom", "vignette", "lensDistortion", "sparkle", "gradualBackground"];
429
+ type RenderChainStageName = (typeof RENDER_CHAIN_STAGE_ORDER)[number];
430
+ /** Built-in stage names plus an opaque id owned by the game that supplied the stage. */
431
+ type RenderChainStageId = RenderChainStageName | (string & {});
432
+ type RenderChainTier = "high" | "medium" | "low" | "off";
433
+ type RenderChainTierRequest = RenderChainTier | "auto";
434
+ type RenderChainSource = "pinned" | "auto";
435
+ type RenderChainVelocitySource = "mrt" | "per-object" | null;
436
+ interface IRenderChainVelocityMeasurement {
437
+ /** Monotonic frame number derived from the stage's completed velocity result. */
438
+ readonly frame: number;
439
+ /** Share of result pixels whose temporal history was rejected in that frame. */
440
+ readonly rejectionFraction: number;
441
+ }
442
+ interface IRenderChainVelocityResult {
443
+ /** Monotonic frame number from the active temporal stage's completed result. */
444
+ readonly frame: number;
445
+ /** One binary value per result pixel: one means the temporal history was rejected. */
446
+ readonly rejectionMask: ArrayLike<number>;
447
+ }
448
+ interface IRenderChainRenderer {
449
+ readonly kind: RendererKind;
450
+ readonly raw: unknown;
451
+ clearOutputNode?(): void;
452
+ /** Internal renderer seam used to turn on core-owned previous-frame bookkeeping for active temporal stages. */
453
+ setRenderChainVelocityEnabled?(enabled: boolean): void;
454
+ /** Install a graph and identify the authored world pass that follows the rendered scene root. */
455
+ setOutputNode(node: unknown, worldPass?: unknown): void;
456
+ }
457
+ /** Quality values are algorithm parameters, not appearance values. Templates own the latter. */
458
+ declare const RENDER_CHAIN_TIERS: {
459
+ readonly high: {
460
+ readonly denoiseIterations: 3;
461
+ readonly sliceCount: 3;
462
+ readonly stepCount: 16;
463
+ };
464
+ readonly low: {
465
+ readonly denoiseIterations: 1;
466
+ readonly sliceCount: 1;
467
+ readonly stepCount: 12;
468
+ };
469
+ readonly medium: {
470
+ readonly denoiseIterations: 2;
471
+ readonly sliceCount: 2;
472
+ readonly stepCount: 8;
473
+ };
474
+ readonly off: {
475
+ readonly denoiseIterations: 0;
476
+ readonly sliceCount: 0;
477
+ readonly stepCount: 0;
478
+ };
479
+ };
480
+ interface IRenderChainVelocityRequest {
481
+ /** The scene pass that owns the shared velocity output. */
482
+ pass?: IVelocityRenderPass;
483
+ /** Treat the renderer's MRT velocity output as provisioned. */
484
+ mrt?: boolean;
485
+ /** Treat objects carrying `userData.useVelocity === true` as provisioned. */
486
+ objectFlags?: boolean;
487
+ /** Alias accepted by integrations that call this route per-object velocity. */
488
+ perObject?: boolean;
489
+ /**
490
+ * Compatibility source for completed temporal measurements. The stage reader wins when it
491
+ * returns a result; this callback is consulted when an integration cannot expose that reader.
492
+ */
493
+ rejectionMeasurement?: () => IRenderChainVelocityMeasurement | undefined;
494
+ /** Explicit route, useful for a native host whose MRT is not introspectable from JavaScript. */
495
+ source?: Exclude<RenderChainVelocitySource, null>;
496
+ }
497
+ interface IRenderChainRequest {
498
+ /** Built-ins use canonical order; authored stages use their supplied anchor order. */
499
+ stages?: readonly RenderChainStageId[];
500
+ /** A fixed tier pins quality; `auto` enables the measured frame-budget ladder. */
501
+ tier?: RenderChainTierRequest;
502
+ velocity?: IRenderChainVelocityRequest;
503
+ }
504
+ interface IRenderChainStageContext {
505
+ readonly tier: RenderChainTier;
506
+ readonly velocity: IRenderChainVelocityReport;
507
+ /** TSL source handed to temporal stages when the request owns a scene pass. */
508
+ readonly velocityNode?: Node;
509
+ readonly quality: (typeof RENDER_CHAIN_TIERS)[RenderChainTier];
510
+ }
511
+ interface IRenderChainStage {
512
+ readonly name: RenderChainStageId;
513
+ /** Place an authored stage immediately before this built-in or supplied stage. */
514
+ readonly before?: RenderChainStageId;
515
+ /** Place an authored stage immediately after this built-in or supplied stage. */
516
+ readonly after?: RenderChainStageId;
517
+ readonly build: (input: unknown, context: IRenderChainStageContext) => unknown;
518
+ /** Stages below this tier are named as dropped instead of silently changing the graph. */
519
+ readonly minimumTier?: RenderChainTier;
520
+ /** Return a reason to drop the stage on this target, or true when it is available. */
521
+ readonly available?: (context: IRenderChainStageContext) => boolean | string;
522
+ /** Read the completed velocity result after rendering; the chain derives its rejection fraction. */
523
+ readonly readVelocityResult?: (node: unknown) => IRenderChainVelocityResult | undefined;
524
+ /** Defaults from the canonical stage name for the temporal stages. */
525
+ readonly requiresVelocity?: boolean;
526
+ /** Release resources allocated while building this stage's graph. */
527
+ readonly dispose?: () => void;
528
+ }
529
+ interface IRenderChainDroppedStage {
530
+ readonly name: RenderChainStageId;
531
+ readonly reason: string;
532
+ }
533
+ interface IRenderChainVelocityReport {
534
+ readonly provisioned: boolean;
535
+ readonly required: boolean;
536
+ readonly source: RenderChainVelocitySource;
537
+ readonly rejectionFraction?: number;
538
+ readonly measurementFrame?: number;
539
+ }
540
+ interface IRenderChainApplied {
541
+ readonly contributions: readonly {
542
+ readonly graphOutputChanged: boolean;
543
+ readonly name: RenderChainStageId;
544
+ }[];
545
+ readonly dropped: readonly IRenderChainDroppedStage[];
546
+ readonly requested: readonly RenderChainStageId[];
547
+ readonly source: RenderChainSource;
548
+ readonly stages: readonly RenderChainStageId[];
549
+ readonly tier: RenderChainTier;
550
+ readonly velocity: IRenderChainVelocityReport;
551
+ }
552
+ interface IRenderChainMarker {
553
+ readonly applied: IRenderChainApplied;
554
+ readonly marker: typeof RENDER_CHAIN_MARKER;
555
+ }
556
+ interface IRenderChainBudgetWindow {
557
+ /** Compilation-contaminated windows remain observable but cannot drive quality changes. */
558
+ readonly surface?: Pick<NonNullable<IFrameBudgetWindow["surface"]>, "compiling">;
559
+ readonly phases: {
560
+ readonly render: Pick<IFrameBudgetWindow["phases"]["render"], "p95">;
561
+ };
562
+ }
563
+ interface IRenderChainOptions {
564
+ readonly renderer: IRenderChainRenderer;
565
+ readonly input?: unknown;
566
+ /** Explicit scene pass for output retargeting; avoids traversing a composed graph to find it. */
567
+ readonly worldPass?: unknown;
568
+ readonly report?: (line: string) => void;
569
+ readonly request?: IRenderChainRequest;
570
+ readonly stages?: readonly IRenderChainStage[];
571
+ readonly targetFps?: number;
572
+ /** Number of consecutive over-budget windows before the automatic ladder steps down. */
573
+ readonly dwellWindows?: number;
574
+ /** Optional scene-like traversal for detecting `userData.useVelocity` flags. */
575
+ readonly scene?: {
576
+ traverse(callback: (object: {
577
+ userData?: Record<string, unknown>;
578
+ }) => void): void;
579
+ };
580
+ }
581
+ /**
582
+ * Read the last chain marker associated with a renderer or its raw renderer.
583
+ * @situation expose the render tier and dropped-stage reasons to a playtest
584
+ * @situation inspect whether a temporal pass received velocity
585
+ * @constraint an absent value means no chain was installed and must fail a chain assertion
586
+ * @example const chain = readRenderChainReport(ctx.renderer);
587
+ */
588
+ declare function readRenderChainReport(renderer: unknown): IRenderChainMarker | undefined;
589
+ /** JSON-safe observation used by the browser and native playtest bridges. */
590
+ declare function readRenderChainObservation(renderer: unknown): IRenderChainMarker["applied"] | undefined;
591
+ /**
592
+ * Compose game-provided nodes through one ordered, measured, honest render seam.
593
+ *
594
+ * The chain owns only stage ordering, availability, velocity provisioning and quality selection.
595
+ * Every stage factory is supplied by the game/template, so this module never chooses a material,
596
+ * colour, light, shader or post-processing look.
597
+ */
598
+ declare class RenderChain {
599
+ #private;
600
+ constructor(renderer: IRenderChainRenderer, options?: Omit<IRenderChainOptions, "renderer">);
601
+ constructor(options: IRenderChainOptions);
602
+ get applied(): IRenderChainApplied;
603
+ get disposed(): boolean;
604
+ /** Rebuild and install the current tier. */
605
+ apply(): IRenderChainApplied;
606
+ /** Samples the active temporal stage's completed velocity result after rendering. */
607
+ observeFrame(): IRenderChainApplied;
608
+ /** Feed the chain the same render-window evidence used by the frame budget. */
609
+ observeFrameBudget(window: IRenderChainBudgetWindow): RenderChainTier;
610
+ dispose(): void;
611
+ }
612
+
613
+ type RendererKind = "webgpu" | "webgl2";
614
+ /**
615
+ * Put transient meshes through the renderer's first-use shader path during loading.
616
+ *
617
+ * A prewarmed mesh stays in the requested subtree with zero opacity. Do not hide transient effects
618
+ * with `.visible = false`: that defers the pipeline and creates one long frame the first time the
619
+ * effect appears, never again that session. Ancestors above the requested object and unrelated
620
+ * siblings keep their visibility. If the requested subtree is under a hidden ancestor,
621
+ * `compileAsync()` compiles that subtree once as a standalone input instead of revealing the
622
+ * ancestor or its siblings.
623
+ */
624
+ declare function prewarm(object: Object3D | readonly Object3D[]): void;
625
+ interface IRendererLike {
626
+ readonly domElement: HTMLCanvasElement;
627
+ readonly kind: RendererKind;
628
+ readonly raw: unknown;
629
+ /**
630
+ * The underlying renderer's statistics (`render.drawCalls`, `render.triangles`, …).
631
+ *
632
+ * Throws when the running renderer has none — the same fail-closed shape as `setOutputNode` —
633
+ * because a game that cannot count its own draws cannot apply a draw-count lever on evidence.
634
+ */
635
+ get info(): unknown;
636
+ /**
637
+ * Builds and compiles a scene's pipelines before anything draws it.
638
+ *
639
+ * On a phone each distinct shader is compiled the first time something using it is drawn, which
640
+ * happens inside a frame the player is watching: 2,500 ms of a 2,882 ms Pixel 8 cold start sits
641
+ * between the bundle finishing and the first frame reaching the display. Calling this during
642
+ * load moves that cost somewhere the player is already waiting.
643
+ *
644
+ * It is on the wrapper for one reason: without it a game must cast through `.raw` to warm up,
645
+ * and a game that cannot warm up without a cast will not warm up.
646
+ */
647
+ compileAsync(scene: Object3D, camera: Camera, targetScene?: Object3D): Promise<void>;
648
+ /** A warm-up may outlive its caller's timeout; its render targets must remain alive. */
649
+ readonly compiling?: boolean;
650
+ /** Compilation starts, including work that settles entirely between rendered frames. */
651
+ readonly compileCount?: number;
652
+ /** The bounded, fail-closed pipeline observation for this renderer, when enabled. */
653
+ readonly pipelineCensus?: () => IPipelineCensus;
654
+ /**
655
+ * What alpha antialiasing did with the multisampled surface, and why, when it did nothing.
656
+ *
657
+ * MSAA resolves triangle edges; a cutout silhouette is carved inside the triangle by an alpha
658
+ * test and resolves through the coverage mask or not at all. This is where to read whether the
659
+ * samples this surface pays for reach the game's foliage, fences and hair.
660
+ *
661
+ * Optional on the interface for the same reason the render-chain seams are: a stub renderer
662
+ * implements the drawing contract, not every report. `createRenderer` always provides it, and
663
+ * the `TN_ALPHA_ANTIALIASING` marker is printed either way, so nothing is only readable here.
664
+ */
665
+ alphaAntialiasing?: () => IAlphaAntialiasingReport;
666
+ compute(node: unknown): void;
667
+ /**
668
+ * Copies one GPU storage attribute back to the CPU, asynchronously.
669
+ *
670
+ * It is on the wrapper for the same reason `compute` is: the call is WebGPU-only and a game that
671
+ * must cast through `.raw` to read its own simulation will either not read it or read it wrong.
672
+ * The copy is asynchronous by nature — the caller gets the bytes some frames after the frame
673
+ * that produced them, and `GPUReadback` is what turns that latency into a reported number
674
+ * instead of a silent one.
675
+ */
676
+ readback(attribute: unknown): Promise<ArrayBuffer>;
677
+ render(scene: Object3D, camera: Camera): void;
678
+ /** Draws after the world without clearing or passing through the world's output pipeline. */
679
+ renderOverlay(scene: Object3D, camera: Camera): void;
680
+ /** Removes the output pipeline installed by a render-chain. */
681
+ clearOutputNode?(): void;
682
+ /** Creates the core-owned chain seam without making generated render source import the package. */
683
+ createRenderChain?: (options: Omit<IRenderChainOptions, "renderer">) => RenderChain;
684
+ /** Feeds automatic render-chain tiers the completed frame-budget window. */
685
+ observeRenderChainBudget?: (window: IRenderChainBudgetWindow) => void;
686
+ /** Samples render-chain telemetry after the renderer completes a frame. */
687
+ observeRenderChainFrame?: () => void;
688
+ /** Whether the active chain requested core-owned per-object velocity history. */
689
+ renderChainUsesPerObjectVelocity?: () => boolean;
690
+ /** Internal callback used by RenderChain; games should request velocity through the chain. */
691
+ setRenderChainVelocityEnabled?: (enabled: boolean) => void;
692
+ /** Installs a graph; pass the authored world pass when the graph contains auxiliary passes. */
693
+ setOutputNode(node: unknown, worldPass?: unknown): void;
694
+ setSize(width: number, height: number, updateStyle?: boolean): void;
695
+ /**
696
+ * The GPU time the last resolved frame actually cost, in milliseconds, or `undefined` when the
697
+ * adapter has no `timestamp-query` and there is nothing to report.
698
+ *
699
+ * Every GPU number in this repository's performance record before this was wall-clock algebra:
700
+ * ablate scene content, difference a blocking device poll in a diagnostic build that never
701
+ * ships. This is the measurement itself. Resolving is asynchronous and off the frame path — the
702
+ * caller reads whatever the last resolve produced.
703
+ */
704
+ gpuFrameMs(): number | undefined;
705
+ /** Age in Three.js frame IDs of the resolved render timestamp; absent when unobservable. */
706
+ gpuFrameAge?(): number | undefined;
707
+ /** Starts a resolve of the GPU timestamps for the frames drawn since the last call. */
708
+ resolveGpuFrame(): void;
709
+ /**
710
+ * Moves the drawing-buffer scale, deferring while a compile retains its targets. The adaptive scaler is the only
711
+ * caller; a pinned game never reaches this, which is what makes "pinned" mean pinned.
712
+ */
713
+ setResolutionScale(scale: number, scaleSource: "auto" | "auto-pinned"): void;
714
+ /**
715
+ * What this renderer is actually drawing at. Read once per frame-budget window so every fps
716
+ * number is self-describing; a record and a tree once disagreed about the scale for a whole
717
+ * session because nothing in the measurement could say which one produced it.
718
+ */
719
+ surface(): IFrameSurfaceState;
720
+ dispose(): void;
721
+ }
722
+ interface IRendererPlatformSource {
723
+ createCanvas(): HTMLCanvasElement;
724
+ hasWebGPU(): boolean;
725
+ observeResize(canvas: HTMLCanvasElement, resize: () => void): () => void;
726
+ readSize(canvas: HTMLCanvasElement): readonly [width: number, height: number];
727
+ }
728
+ interface IRendererOptions {
729
+ /**
730
+ * Resolves alpha-tested cutout silhouettes — foliage, fences, hair — through the multisample
731
+ * coverage mask instead of a binary `discard`. Defaults to true, and does nothing at all on a
732
+ * single-sampled surface, which it reports rather than pretends. Godot's
733
+ * `alpha_antialiasing_mode`, in Three.js's `alphaToCoverage`.
734
+ */
735
+ alphaAntialiasing?: boolean;
736
+ /** Requests multisample antialiasing from the renderer. Defaults to true. */
737
+ antialias?: boolean;
738
+ canvas?: HTMLCanvasElement;
739
+ preferWebGPU?: boolean;
740
+ /** CSS-pixel multiplier for the drawing buffer. The default is intentional DPR 1. */
741
+ resolutionScale?: number;
742
+ /** Whether the game pinned that scale or the engine chose it. Reported, never inferred. */
743
+ scaleSource?: "pinned" | "auto" | "auto-pinned";
744
+ /**
745
+ * Physical pixels per logical (CSS) pixel of the canvas this draws into. Default: the device's
746
+ * own `devicePixelRatio` on both runtimes (unified 2026-09-01; web's old DPR-1 buffer read as
747
+ * pixelation on any HiDPI display). The resolution scaler composes on top, so an unaffordable
748
+ * density is trimmed by scaler rungs rather than by the developer. An explicit value wins on
749
+ * both runtimes.
750
+ */
751
+ pixelRatio?: number;
752
+ /** Where convention markers go. Defaults to the console, exactly as the render chain reports. */
753
+ report?: (line: string) => void;
754
+ /**
755
+ * Bounded pipeline detail for a real launch capture. It is on by default so a town does not
756
+ * need an instrumented build; pass `false` for an identical-build overhead control.
757
+ */
758
+ pipelineCensus?: false | {
759
+ readonly limit?: number;
760
+ };
761
+ source?: IRendererPlatformSource;
762
+ webgpuFactory?: (canvas: HTMLCanvasElement, options: Readonly<{
763
+ antialias: boolean;
764
+ }>) => Promise<unknown> | unknown;
765
+ webgl2Factory?: (canvas: HTMLCanvasElement, options: Readonly<{
766
+ antialias: boolean;
767
+ }>) => unknown;
768
+ }
769
+
770
+ export { readRenderChainObservation as $, type IVelocityRenderPass as A, PIPELINE_CENSUS_VERSION as B, PipelineCensus as C, DEFAULT_PIPELINE_CENSUS_LIMIT as D, type PipelineCensusMode as E, FRAME_BUDGET_MARKER as F, type PipelineCensusStatus as G, RENDER_CHAIN_STAGE_ORDER as H, type IRendererLike as I, RENDER_CHAIN_TIERS as J, RenderChain as K, type RenderChainSource as L, type RenderChainStageId as M, type RenderChainStageName as N, type RenderChainTier as O, PIPELINE_CENSUS_CAPABILITY as P, type RenderChainTierRequest as Q, RENDER_CHAIN_MARKER as R, type RenderChainVelocitySource as S, VELOCITY_PREVIOUS_BONE_MATRICES as T, VELOCITY_PREVIOUS_INSTANCE_MATRICES as U, VELOCITY_OUTPUT_NAME as V, VELOCITY_PREVIOUS_WORLD_MATRIX as W, VelocityTracker as X, createPipelineCensus as Y, ensureVelocityOutput as Z, prewarm as _, FRAME_BUDGET_PHASES as a, readRenderChainReport as a0, readVelocityPreviousBoneMatrices as a1, readVelocityPreviousMatrices as a2, readVelocityPreviousWorldMatrix as a3, velocityTexture as a4, withVelocityContext as a5, type IRendererOptions as a6, FRAME_HITCH_MARKER as b, FrameBudget as c, type FrameBudgetPhase as d, type IFrameBudgetOptions as e, type IFrameBudgetSummary as f, type IFrameBudgetWindow as g, type IFramePhaseSample as h, type IPipelineCensus as i, type IPipelineCensusCounts as j, type IPipelineCensusEvent as k, type IPipelineCensusOptions as l, type IPipelineProvenance as m, type IPipelineShaderObservation as n, type IRenderChainApplied as o, type IRenderChainBudgetWindow as p, type IRenderChainDroppedStage as q, type IRenderChainOptions as r, type IRenderChainRenderer as s, type IRenderChainRequest as t, type IRenderChainStage as u, type IRenderChainStageContext as v, type IRenderChainVelocityMeasurement as w, type IRenderChainVelocityReport as x, type IRenderChainVelocityRequest as y, type IRenderChainVelocityResult as z };