@threenative/core 0.3.1 → 0.3.3
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/capabilities.json +1344 -88
- package/dist/{assets-kyoF7JlJ.d.ts → assets-CYKk2WTu.d.ts} +7 -0
- package/dist/{audio-BFiGneTL.d.ts → audio-7i3Xl0l3.d.ts} +32 -1
- package/dist/{canvas-layer-BLVijiUJ.d.ts → canvas-layer-C1SnMoJ-.d.ts} +1 -1
- package/dist/{game-XGrTzapq.d.ts → game-D_6r-k4Y.d.ts} +365 -7
- package/dist/{gpu-readback-D2iRvoe9.d.ts → gpu-readback-CMklJs6r.d.ts} +1 -1
- package/dist/hot.d.ts +5 -5
- package/dist/hot.js +20 -3
- package/dist/index.d.ts +1040 -19
- package/dist/index.js +5111 -470
- package/dist/playtest.d.ts +4 -4
- package/dist/playtest.js +44 -9
- package/dist/react.d.ts +2 -2
- package/dist/{renderer-C6hqZpoG.d.ts → renderer-Cy4qeBOA.d.ts} +247 -13
- package/dist/ui-layer.d.ts +13 -7
- package/dist/ui-layer.js +2 -1
- package/dist/world.d.ts +3 -3
- package/mcp/blender-server.mjs +1 -1
- package/mcp/engine-server.mjs +33 -5
- package/mcp/servers.mjs +3 -3
- package/package.json +6 -6
- package/patches/three@0.185.1.patch +179 -19
package/dist/playtest.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { JsonValue, JsonPrimitive } from '@threenative/playtest/protocol';
|
|
2
|
-
import { b as IGamePluginHooks } from './game-
|
|
3
|
-
import './assets-
|
|
2
|
+
import { b as IGamePluginHooks } from './game-D_6r-k4Y.js';
|
|
3
|
+
import './assets-CYKk2WTu.js';
|
|
4
4
|
import 'three';
|
|
5
|
-
import './renderer-
|
|
5
|
+
import './renderer-Cy4qeBOA.js';
|
|
6
6
|
import 'three/webgpu';
|
|
7
|
-
import './canvas-layer-
|
|
7
|
+
import './canvas-layer-C1SnMoJ-.js';
|
|
8
8
|
import 'zustand/vanilla';
|
|
9
9
|
|
|
10
10
|
/**
|
package/dist/playtest.js
CHANGED
|
@@ -1,27 +1,48 @@
|
|
|
1
1
|
import { assertJsonSafe, PLAYTEST_PROTOCOL_LIMITS } from '@threenative/playtest/protocol';
|
|
2
2
|
import { installThreePlaytestBridge } from '@threenative/playtest/three';
|
|
3
|
-
import { Object3D } from 'three';
|
|
3
|
+
import { Sphere, Vector3, Object3D } from 'three';
|
|
4
4
|
|
|
5
5
|
// src/playtest.ts
|
|
6
|
-
var
|
|
6
|
+
var AUDIO_STATE = /* @__PURE__ */ Symbol.for("threenative.audio.runtime");
|
|
7
|
+
function audioState() {
|
|
8
|
+
const host = globalThis;
|
|
9
|
+
const existing = host[AUDIO_STATE];
|
|
10
|
+
if (existing !== void 0) return existing;
|
|
11
|
+
const created = { buses: /* @__PURE__ */ new Set(), cueCounts: /* @__PURE__ */ new Map(), cueLog: [] };
|
|
12
|
+
host[AUDIO_STATE] = created;
|
|
13
|
+
return created;
|
|
14
|
+
}
|
|
7
15
|
function audioRuntimeSnapshot() {
|
|
8
16
|
let queued = 0;
|
|
9
17
|
let voices = 0;
|
|
10
18
|
let pooled = 0;
|
|
11
19
|
let paused = 0;
|
|
12
20
|
const unsupported = /* @__PURE__ */ new Set();
|
|
13
|
-
|
|
21
|
+
const state = audioState();
|
|
22
|
+
for (const bus of state.buses) {
|
|
14
23
|
queued += bus.queued;
|
|
15
24
|
voices += bus.voices;
|
|
16
25
|
pooled += bus.pooled;
|
|
17
26
|
paused += bus.pausedVoices;
|
|
18
27
|
for (const option of bus.unsupported) unsupported.add(option);
|
|
19
28
|
}
|
|
20
|
-
return {
|
|
29
|
+
return {
|
|
30
|
+
cues: Object.fromEntries(state.cueCounts),
|
|
31
|
+
paused,
|
|
32
|
+
pooled,
|
|
33
|
+
queued,
|
|
34
|
+
recentCues: state.cueLog.map((entry) => ({ ...entry })),
|
|
35
|
+
unsupported: [...unsupported].sort(),
|
|
36
|
+
voices
|
|
37
|
+
};
|
|
21
38
|
}
|
|
39
|
+
var GEOMETRY_CAPTURE_CAPABILITY = "runtime.geometry";
|
|
40
|
+
new Sphere();
|
|
41
|
+
new Vector3();
|
|
42
|
+
new Vector3();
|
|
22
43
|
|
|
23
44
|
// src/version.ts
|
|
24
|
-
var CORE_VERSION = "0.3.
|
|
45
|
+
var CORE_VERSION = "0.3.3";
|
|
25
46
|
|
|
26
47
|
// src/pipeline-census.ts
|
|
27
48
|
var PIPELINE_CENSUS_CAPABILITY = "runtime.pipelineCensus";
|
|
@@ -53,6 +74,7 @@ function playtest(options = {}) {
|
|
|
53
74
|
let attached;
|
|
54
75
|
let startSceneEntered;
|
|
55
76
|
let disposePipelineCensus;
|
|
77
|
+
let disposeGeometryCapture;
|
|
56
78
|
let contactHistory = [];
|
|
57
79
|
const watcher = createTransitionWatcher();
|
|
58
80
|
let readTick;
|
|
@@ -122,14 +144,24 @@ function playtest(options = {}) {
|
|
|
122
144
|
sample: () => ({ pipelineCensus: pipelineCensus() })
|
|
123
145
|
});
|
|
124
146
|
}
|
|
147
|
+
const geometryCapture = runtime?.geometryCapture;
|
|
148
|
+
if (runtime !== void 0 && geometryCapture !== void 0) {
|
|
149
|
+
disposeGeometryCapture = runtime.observations.contribute({
|
|
150
|
+
capabilities: [GEOMETRY_CAPTURE_CAPABILITY],
|
|
151
|
+
// Nothing is armed, walked or hooked unless this request asked for a capture: the
|
|
152
|
+
// capability says the runtime can answer, not that every sample pays for one.
|
|
153
|
+
sample: async (request) => request.geometry === void 0 ? {} : { geometry: await geometryCapture(request.geometry) }
|
|
154
|
+
});
|
|
155
|
+
}
|
|
125
156
|
installRuntimeChannels(installation.bridge, runtime);
|
|
126
|
-
if (
|
|
127
|
-
runtime?.enableRuntimeDiagnostics?.();
|
|
157
|
+
if (runnerAnnounced()) runtime?.enableRuntimeDiagnostics?.();
|
|
128
158
|
dispose = installation.dispose;
|
|
129
159
|
attached = holdUntilAttached(installation.bridge, options, () => startSceneEntered);
|
|
130
160
|
const cleanup = () => {
|
|
131
161
|
disposePipelineCensus?.();
|
|
132
162
|
disposePipelineCensus = void 0;
|
|
163
|
+
disposeGeometryCapture?.();
|
|
164
|
+
disposeGeometryCapture = void 0;
|
|
133
165
|
dispose?.();
|
|
134
166
|
dispose = void 0;
|
|
135
167
|
attached = void 0;
|
|
@@ -157,6 +189,9 @@ var PLAYTEST_ATTACH_TIMEOUT_MS = 3e4;
|
|
|
157
189
|
var PLAYTEST_RUNNER_EXPECTED_GLOBAL = "__THREENATIVE_PLAYTEST_RUNNER_EXPECTED__";
|
|
158
190
|
function shouldHoldUntilAttached(options) {
|
|
159
191
|
if (options.holdUntilAttached !== void 0) return options.holdUntilAttached;
|
|
192
|
+
return runnerAnnounced();
|
|
193
|
+
}
|
|
194
|
+
function runnerAnnounced() {
|
|
160
195
|
const host = globalThis;
|
|
161
196
|
return host[PLAYTEST_RUNNER_EXPECTED_GLOBAL] === true || host.TN_PLAYTEST_ENDPOINT !== void 0;
|
|
162
197
|
}
|
|
@@ -227,10 +262,10 @@ function addRuntimeCapabilities(description, contributions) {
|
|
|
227
262
|
}
|
|
228
263
|
return { ...description, capabilities };
|
|
229
264
|
}
|
|
230
|
-
function addRuntimeObservations(snapshot, request, contributions) {
|
|
265
|
+
async function addRuntimeObservations(snapshot, request, contributions) {
|
|
231
266
|
const result = { ...snapshot };
|
|
232
267
|
for (const contribution of contributions) {
|
|
233
|
-
const slice = contribution.sample(request);
|
|
268
|
+
const slice = await contribution.sample(request);
|
|
234
269
|
if (typeof slice !== "object" || slice === null || Array.isArray(slice)) {
|
|
235
270
|
throw new TypeError("A runtime observation contribution must return a top-level object.");
|
|
236
271
|
}
|
package/dist/react.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import { C as CanvasLayer } from './canvas-layer-
|
|
2
|
+
import { C as CanvasLayer } from './canvas-layer-C1SnMoJ-.js';
|
|
3
3
|
import 'three';
|
|
4
|
-
import './renderer-
|
|
4
|
+
import './renderer-Cy4qeBOA.js';
|
|
5
5
|
import 'three/webgpu';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -1,6 +1,90 @@
|
|
|
1
1
|
import { Object3D, Matrix4, Camera } from 'three';
|
|
2
2
|
import { MRTNode, Node } from 'three/webgpu';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Host-boundary counters, default-off, on the frame budget's own record.
|
|
6
|
+
*
|
|
7
|
+
* Two of the three hypotheses about the render phase's unattributed time are about crossings and
|
|
8
|
+
* bytes — "the V8↔host boundary dominates" and "a buffer write per object per pass dominates" —
|
|
9
|
+
* and neither can be answered by a JS CPU profile, because the time is spent on the other side of a
|
|
10
|
+
* call the profiler attributes to a single frame. This counts the calls instead.
|
|
11
|
+
*
|
|
12
|
+
* What it counts, precisely, because a counter that overstates itself is worse than none:
|
|
13
|
+
*
|
|
14
|
+
* - `hostCalls` — every WebGPU method invoked on the device's command encoders and on its queue.
|
|
15
|
+
* On a native host each of those is one crossing; in a browser it is one API call. It is not
|
|
16
|
+
* every crossing a frame makes: `mapAsync`, `getCurrentTexture` and the presentation path are
|
|
17
|
+
* issued by three and by the host outside these objects, and they are named here rather than
|
|
18
|
+
* folded in so the number can be checked against a host that counts its own.
|
|
19
|
+
* - `gpuBytes` — the byte count handed to `queue.writeBuffer`, exactly. Texture uploads are not
|
|
20
|
+
* included: a `writeTexture` size is a function of the destination's format, and estimating it
|
|
21
|
+
* from `data.byteLength` would be a number nobody could reconcile.
|
|
22
|
+
* - `jsAllocBytes` — the change in `performance.memory.usedJSHeapSize`, where the platform has it.
|
|
23
|
+
* Absent on a host without `performance.memory`, never zero: an unmeasurable allocation rate and
|
|
24
|
+
* a frame that allocated nothing are different facts.
|
|
25
|
+
*
|
|
26
|
+
* Wrapping every method of a command encoder is not free — it is one extra JS call per command, and
|
|
27
|
+
* this is why the counters ride the same opt-in flag as the spans rather than being always on.
|
|
28
|
+
*/
|
|
29
|
+
/** One frame's boundary counts. Every field is absent when the platform cannot report it. */
|
|
30
|
+
interface IFrameCounters {
|
|
31
|
+
readonly gpuBytes?: number;
|
|
32
|
+
readonly hostCalls?: number;
|
|
33
|
+
readonly jsAllocBytes?: number;
|
|
34
|
+
}
|
|
35
|
+
/** The slice of a WebGPU device this reads. Structural, so a test can stand in a fake. */
|
|
36
|
+
interface ICounterDevice {
|
|
37
|
+
createCommandEncoder?(): unknown;
|
|
38
|
+
createRenderBundleEncoder?(): unknown;
|
|
39
|
+
queue?: unknown;
|
|
40
|
+
}
|
|
41
|
+
/** The WebGPU device behind a raw three renderer, when the backend exposes one. */
|
|
42
|
+
declare function counterDeviceOf(raw: unknown): unknown;
|
|
43
|
+
/**
|
|
44
|
+
* Counts the frame's boundary crossings and bytes, and reads what the platform will say about
|
|
45
|
+
* allocation. Install once per renderer; `read` returns the counts since the previous `read`.
|
|
46
|
+
*/
|
|
47
|
+
declare class FrameCounters {
|
|
48
|
+
#private;
|
|
49
|
+
private constructor();
|
|
50
|
+
/** Installs the counters on a device, or answers `undefined` when it has nothing to count. */
|
|
51
|
+
static install(device: unknown): FrameCounters | undefined;
|
|
52
|
+
/** The counts since the previous read. */
|
|
53
|
+
read(): IFrameCounters;
|
|
54
|
+
/** Removes every wrapper. A game that disposes its renderer must not leave the counters behind. */
|
|
55
|
+
uninstall(): void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Per-pass draw and triangle attribution, on by default, for every platform.
|
|
60
|
+
*
|
|
61
|
+
* The frame budget already owned the milliseconds; this owns the submissions. Three's
|
|
62
|
+
* `renderer.info.reset()` runs once per frame before the world render, and a nested shadow or
|
|
63
|
+
* reflection `renderer.render(...)` shares that same `info`, so `info.render` reports main plus
|
|
64
|
+
* every nested pass combined. The aggregate cannot tell a 316-draw shadow lane from a 1,418-draw
|
|
65
|
+
* colour pass, and every optimisation attempt had to hand-roll the split. This is the split: each
|
|
66
|
+
* `render()` is one entry on a stack, and its own submissions are the counter delta across its
|
|
67
|
+
* call minus the deltas of the nested calls it made, so the innermost active render call owns
|
|
68
|
+
* exactly what it submitted.
|
|
69
|
+
*
|
|
70
|
+
* Three names its own shadow pass by temporarily renaming the scene to `Shadow Map [ ... ]`
|
|
71
|
+
* (`renderShadow`), and a reflection pass names its scene with `Reflector` in it. Those are
|
|
72
|
+
* borrowed, not invented: the classifier reads three's vocabulary and falls back to `nested`.
|
|
73
|
+
*
|
|
74
|
+
* Measurement, not policy: installing this alters no draw. `install` answers `undefined` on a
|
|
75
|
+
* renderer whose `info.render` cannot be read, and a consumer must then report the pass split as
|
|
76
|
+
* absent rather than zero.
|
|
77
|
+
*/
|
|
78
|
+
/** The named kinds of a render pass. Closed on purpose: a consumer bounds a known kind. */
|
|
79
|
+
declare const FRAME_PASS_KINDS: readonly ["main", "shadow", "reflection", "nested"];
|
|
80
|
+
type FramePassKind = (typeof FRAME_PASS_KINDS)[number];
|
|
81
|
+
/** One render call's own submissions, attributed to its innermost active render call. */
|
|
82
|
+
interface IRenderPassSample {
|
|
83
|
+
readonly draws: number;
|
|
84
|
+
readonly kind: FramePassKind;
|
|
85
|
+
readonly triangles: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
4
88
|
/**
|
|
5
89
|
* Per-presented-frame cost attribution, on by default, for every platform.
|
|
6
90
|
*
|
|
@@ -23,6 +107,7 @@ import { MRTNode, Node } from 'three/webgpu';
|
|
|
23
107
|
* and a phase that was never measured reports zero samples so a consumer asserting on it fails
|
|
24
108
|
* instead of skipping.
|
|
25
109
|
*/
|
|
110
|
+
|
|
26
111
|
/** Marker printed once per report window. */
|
|
27
112
|
declare const FRAME_BUDGET_MARKER = "TN_FRAME_BUDGET";
|
|
28
113
|
/** Marker printed the moment a gap between presented frames exceeds `hitchMs`. */
|
|
@@ -30,9 +115,14 @@ declare const FRAME_HITCH_MARKER = "TN_FRAME_HITCH";
|
|
|
30
115
|
/**
|
|
31
116
|
* The named parts of one presented frame. They partition the frame: `hostGap` is the time before
|
|
32
117
|
* 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.
|
|
118
|
+
* `render`, `overlay`, `ui` and `residual` sum to the callback's own duration.
|
|
119
|
+
*
|
|
120
|
+
* `overlay` and `ui` are two different draws that happen to sit next to each other. `overlay` is
|
|
121
|
+
* the three.js HUD pass; `ui` is the native UI layer's composite of the page's pixels into the
|
|
122
|
+
* game's own frame — one upload and one quad — which is why it is a phase of its own and not part
|
|
123
|
+
* of `overlay`.
|
|
34
124
|
*/
|
|
35
|
-
declare const FRAME_BUDGET_PHASES: readonly ["hostGap", "update", "render", "overlay", "residual"];
|
|
125
|
+
declare const FRAME_BUDGET_PHASES: readonly ["hostGap", "update", "render", "overlay", "ui", "residual"];
|
|
36
126
|
type FrameBudgetPhase = (typeof FRAME_BUDGET_PHASES)[number];
|
|
37
127
|
/** One frame's cost, split by phase. Every field is milliseconds. */
|
|
38
128
|
interface IFramePhaseSample {
|
|
@@ -40,6 +130,7 @@ interface IFramePhaseSample {
|
|
|
40
130
|
readonly update: number;
|
|
41
131
|
readonly render: number;
|
|
42
132
|
readonly overlay: number;
|
|
133
|
+
readonly ui: number;
|
|
43
134
|
readonly residual: number;
|
|
44
135
|
}
|
|
45
136
|
/**
|
|
@@ -82,16 +173,58 @@ interface IFrameBudgetSummary {
|
|
|
82
173
|
readonly p99: number;
|
|
83
174
|
readonly max: number;
|
|
84
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* One render-pass kind's submissions across a window, so a change that trades triangles for CPU is
|
|
178
|
+
* visible in the same report as the milliseconds it traded for.
|
|
179
|
+
*/
|
|
180
|
+
interface IFrameBudgetPassSummary {
|
|
181
|
+
readonly draws: IFrameBudgetSummary;
|
|
182
|
+
/** Frames in the window that submitted a pass of this kind. */
|
|
183
|
+
readonly frames: number;
|
|
184
|
+
readonly triangles: IFrameBudgetSummary;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* One reported window of the frame meter.
|
|
188
|
+
*
|
|
189
|
+
* **The cadence here is the loop's, not necessarily the display's.** `endFrame` is called once per
|
|
190
|
+
* frame the game's loop runs, and on the web that is one `requestAnimationFrame` per vblank, so the
|
|
191
|
+
* interval is a presented frame's. On a native host the loop's dispatch is what drives it — the host
|
|
192
|
+
* says so itself: "the JavaScript budget reads the same interval as presentedDelta"
|
|
193
|
+
* (`runtime-native/src/runtime.cpp`, `executeAnimationFrameCallbacks`). Its presentation cap paces
|
|
194
|
+
* the **present**, never the loop, so a loop that outruns 60 Hz iterates many times per present and
|
|
195
|
+
* this window's `fps` is inflated by exactly that ratio. Measured on midway's native launch:
|
|
196
|
+
* `fps 2631.58` beside the host's own `TN_PRESENTS_TICK:{"frames":1740,"presents":133,"capHz":60}`.
|
|
197
|
+
* The display's rate is the host's series; a reader that has both must not print this one as though
|
|
198
|
+
* it were a player's — `threenative-playtest perf` refuses to (`TN_PERF_VIRTUAL_DISPLAY`).
|
|
199
|
+
*/
|
|
85
200
|
interface IFrameBudgetWindow {
|
|
86
201
|
/** 1 for the first reported window, incrementing thereafter. */
|
|
87
202
|
readonly window: number;
|
|
88
|
-
/**
|
|
203
|
+
/** Loop frames counted in this window, hitches excluded — one per present on the web. */
|
|
89
204
|
readonly frames: number;
|
|
90
205
|
/** Frames excluded from the window because their present gap exceeded `hitchMs`. */
|
|
91
206
|
readonly hitches: number;
|
|
92
|
-
/**
|
|
207
|
+
/**
|
|
208
|
+
* Derived from the mean interval between loop frames. The number a player would read off a
|
|
209
|
+
* counter only where the loop's cadence is the display's, as it is on the web via rAF.
|
|
210
|
+
*/
|
|
93
211
|
readonly fps: number;
|
|
94
|
-
/**
|
|
212
|
+
/**
|
|
213
|
+
* Frames that reached the display in this window, when the host reports them.
|
|
214
|
+
*
|
|
215
|
+
* Absent on the web, where rAF *is* the display's cadence and a second number would be the same
|
|
216
|
+
* one. On a native host the presentation cap lets the loop dispatch many times per present, so
|
|
217
|
+
* this is the count a player saw and `fps` is not. **Zero is a reading**: a window of loop frames
|
|
218
|
+
* can be shorter than one present period, and the display genuinely showed nothing in it.
|
|
219
|
+
*/
|
|
220
|
+
readonly presents?: number;
|
|
221
|
+
/**
|
|
222
|
+
* Presents per second over this window's own duration. Absent when the window counted none —
|
|
223
|
+
* a rate needs at least one present, and a window too short to contain one carries no rate to
|
|
224
|
+
* assess rather than a zero.
|
|
225
|
+
*/
|
|
226
|
+
readonly presentedFps?: number;
|
|
227
|
+
/** Interval between loop frames — the honest frame period where the loop presents every frame. */
|
|
95
228
|
readonly presented: IFrameBudgetSummary;
|
|
96
229
|
/** Duration of the frame callback itself, entry to exit. */
|
|
97
230
|
readonly frame: IFrameBudgetSummary;
|
|
@@ -100,6 +233,14 @@ interface IFrameBudgetWindow {
|
|
|
100
233
|
readonly phases: Readonly<Record<FrameBudgetPhase, IFrameBudgetSummary>>;
|
|
101
234
|
/** Each phase's mean as a fraction of the mean presented interval. */
|
|
102
235
|
readonly shares: Readonly<Record<FrameBudgetPhase, number>>;
|
|
236
|
+
/**
|
|
237
|
+
* Draw calls and triangles submitted per render pass, when a pass recorder was installed.
|
|
238
|
+
*
|
|
239
|
+
* Absent rather than defaulted: a renderer whose submissions nothing measured and a frame that
|
|
240
|
+
* submitted nothing are different facts, and a zero would merge them. A kind no frame submitted
|
|
241
|
+
* is absent; `frames` says how many frames did.
|
|
242
|
+
*/
|
|
243
|
+
readonly passes?: Readonly<Partial<Record<FramePassKind, IFrameBudgetPassSummary>>>;
|
|
103
244
|
/**
|
|
104
245
|
* The resolution and sampling this window's frames were drawn at, when the loop reported one.
|
|
105
246
|
* Absent rather than defaulted: a consumer asserting on it must fail loudly instead of reading
|
|
@@ -107,14 +248,44 @@ interface IFrameBudgetWindow {
|
|
|
107
248
|
*/
|
|
108
249
|
readonly surface?: IFrameSurfaceState;
|
|
109
250
|
/**
|
|
110
|
-
* GPU milliseconds
|
|
251
|
+
* GPU milliseconds per resolved frame in this window, from `timestamp-query`, summarised like a
|
|
252
|
+
* phase — mean/p50/p95/p99/max over the frames the device actually reported.
|
|
253
|
+
*
|
|
254
|
+
* A single instantaneous `info.render.timestamp` read is lagged by up to `gpuAgeFrames` and
|
|
255
|
+
* spread 3.5x between consecutive reads of one steady frame, so it is not the frame's GPU cost
|
|
256
|
+
* and is not what this reports. Absent rather than zero when no frame resolved a reading: an
|
|
257
|
+
* adapter without timestamps and a frame that genuinely cost no GPU time are different facts,
|
|
258
|
+
* and a zero would merge them.
|
|
259
|
+
*/
|
|
260
|
+
readonly gpu?: IFrameBudgetSummary;
|
|
261
|
+
/**
|
|
262
|
+
* Frames in the window whose GPU reading had not advanced since the previous frame, or was
|
|
263
|
+
* absent. `gpu.samples + gpuStale` is the frames the device was asked about; a window where
|
|
264
|
+
* every frame is stale reports `gpu` absent rather than the last reading looking current.
|
|
265
|
+
*/
|
|
266
|
+
readonly gpuStale: number;
|
|
267
|
+
/**
|
|
268
|
+
* The window mean of `gpu`, when present. The scalar the resolution scaler reads.
|
|
111
269
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
270
|
+
* It is the same number as `gpu.mean`; a single field keeps the scaler and the perf report on
|
|
271
|
+
* one series rather than a second instantaneous read.
|
|
114
272
|
*/
|
|
115
273
|
readonly gpuMs?: number;
|
|
116
|
-
/** Age of the resolved GPU timestamp in Three.js frame IDs; absent means unobservable. */
|
|
274
|
+
/** Age of the most recent resolved GPU timestamp in Three.js frame IDs; absent means unobservable. */
|
|
117
275
|
readonly gpuAgeFrames?: number;
|
|
276
|
+
/**
|
|
277
|
+
* The frame's boundary counts, when something counted them.
|
|
278
|
+
*
|
|
279
|
+
* Each series is absent when nothing measured it, and a series present with zero samples is not
|
|
280
|
+
* possible: an uncounted frame and a frame that crossed the boundary zero times are different
|
|
281
|
+
* facts, and a fabricated zero merges them. `hostCalls` and `gpuBytes` come from
|
|
282
|
+
* `FrameCounters`; `jsAllocBytes` needs a platform that publishes `performance.memory`.
|
|
283
|
+
*/
|
|
284
|
+
readonly counters?: {
|
|
285
|
+
readonly hostCalls?: IFrameBudgetSummary;
|
|
286
|
+
readonly gpuBytes?: IFrameBudgetSummary;
|
|
287
|
+
readonly jsAllocBytes?: IFrameBudgetSummary;
|
|
288
|
+
};
|
|
118
289
|
}
|
|
119
290
|
interface IFrameBudgetOptions {
|
|
120
291
|
/** Presented frames per report window. Default 300. */
|
|
@@ -138,8 +309,14 @@ interface IFrameBudgetOptions {
|
|
|
138
309
|
* loop, which is the only place that knows both the renderer and the window boundary.
|
|
139
310
|
*/
|
|
140
311
|
readonly readSurface?: () => IFrameSurfaceState;
|
|
141
|
-
/**
|
|
142
|
-
|
|
312
|
+
/**
|
|
313
|
+
* Frames the display has presented so far, when the platform can say.
|
|
314
|
+
*
|
|
315
|
+
* Defaults to the native host's `__tnPresentedCount`, whose presence is the whole signal: the
|
|
316
|
+
* web has no such seam and needs none. A counter that goes backwards or non-finite is ignored
|
|
317
|
+
* for the window rather than reported as a negative rate.
|
|
318
|
+
*/
|
|
319
|
+
readonly readPresentCount?: () => number | undefined;
|
|
143
320
|
/** Reads the successful GPU query frame age, not the age of the last resolve attempt. */
|
|
144
321
|
readonly readGpuAgeFrames?: () => number | undefined;
|
|
145
322
|
}
|
|
@@ -147,7 +324,7 @@ interface IFrameBudgetOptions {
|
|
|
147
324
|
* Accumulates one frame at a time and reports windowed attribution.
|
|
148
325
|
*
|
|
149
326
|
* The caller is the frame loop; the sequence per frame is
|
|
150
|
-
* `beginFrame` → `markSimulationEnd` → (`addRender` / `addOverlay`) → `endFrame`.
|
|
327
|
+
* `beginFrame` → `markSimulationEnd` → (`addRender` / `addOverlay` / `addUi`) → `endFrame`.
|
|
151
328
|
* Calling them out of order throws rather than producing a plausible-looking split.
|
|
152
329
|
*/
|
|
153
330
|
declare class FrameBudget {
|
|
@@ -165,6 +342,34 @@ declare class FrameBudget {
|
|
|
165
342
|
markSimulationEnd(nowMs: number, substeps: number): void;
|
|
166
343
|
addRender(ms: number): void;
|
|
167
344
|
addOverlay(ms: number): void;
|
|
345
|
+
addUi(ms: number): void;
|
|
346
|
+
/**
|
|
347
|
+
* Records one presented frame's GPU duration, from a resolved `timestamp-query`.
|
|
348
|
+
*
|
|
349
|
+
* `ms` is `undefined` when the device reported no reading for the frame. `frame` is the
|
|
350
|
+
* Three.js frame id the duration belongs to — `gpuFrameSample`/`gpuFrameAge` on the renderer.
|
|
351
|
+
* A reading whose `frame` has not advanced since the previous frame is the previous frame's
|
|
352
|
+
* resolve still in flight, so it is counted as stale and not pushed again; that repetition was
|
|
353
|
+
* what made one lagged sample read as the current frame's cost. Without a `frame` a reading is
|
|
354
|
+
* always taken as fresh, since there is nothing to tell repeats from a genuine re-measurement.
|
|
355
|
+
*
|
|
356
|
+
* Once per frame. A window with no reading at all reports `gpu` absent, never a zero.
|
|
357
|
+
*/
|
|
358
|
+
addGpuMs(ms: number | undefined, frame?: number): void;
|
|
359
|
+
/**
|
|
360
|
+
* Records the frame's host-boundary counts, from `FrameCounters` or any other source.
|
|
361
|
+
*
|
|
362
|
+
* At most one call per frame; a second replaces the first rather than summing, because the
|
|
363
|
+
* counter's own reader already returns the frame's totals and summing two reads of one frame
|
|
364
|
+
* would double it. A field the platform cannot report is left out of the series entirely.
|
|
365
|
+
*/
|
|
366
|
+
addCounters(counters: IFrameCounters): void;
|
|
367
|
+
/**
|
|
368
|
+
* Records the frame's per-pass submissions, from `RenderPassBudget` or any other source. At most
|
|
369
|
+
* one entry per kind per frame is meaningful; a second of the same kind is summed by the caller.
|
|
370
|
+
* An unknown kind throws rather than being dropped, the same fail-closed rule as a phase.
|
|
371
|
+
*/
|
|
372
|
+
addRenderPasses(passes: readonly IRenderPassSample[]): void;
|
|
168
373
|
/**
|
|
169
374
|
* Closes the frame and returns its phase split, or `undefined` when the frame was a hitch and
|
|
170
375
|
* therefore excluded — a 27-second startup stall is not a frame time and must not enter a
|
|
@@ -179,6 +384,16 @@ declare class FrameBudget {
|
|
|
179
384
|
endFrame(nowMs: number, wantSample?: boolean): IFramePhaseSample | undefined;
|
|
180
385
|
/** Reads the window in progress without disturbing it. */
|
|
181
386
|
window(): IFrameBudgetWindow;
|
|
387
|
+
/**
|
|
388
|
+
* The render phase of the frame that just closed, or `undefined` when that frame was a hitch and
|
|
389
|
+
* therefore not counted.
|
|
390
|
+
*
|
|
391
|
+
* It exists because the phase split object is optional — `endFrame` builds one only when a
|
|
392
|
+
* consumer asked for per-frame samples, which shipping games do not — and a reader that needs
|
|
393
|
+
* the number must not be forced to turn that allocation on to get it. Reading a measurement and
|
|
394
|
+
* collecting a sample are different requests.
|
|
395
|
+
*/
|
|
396
|
+
get lastRenderMs(): number | undefined;
|
|
182
397
|
}
|
|
183
398
|
|
|
184
399
|
/**
|
|
@@ -704,6 +919,19 @@ interface IRendererLike {
|
|
|
704
919
|
gpuFrameMs(): number | undefined;
|
|
705
920
|
/** Age in Three.js frame IDs of the resolved render timestamp; absent when unobservable. */
|
|
706
921
|
gpuFrameAge?(): number | undefined;
|
|
922
|
+
/**
|
|
923
|
+
* The last resolved GPU frame's duration and the Three.js frame id it belongs to, or
|
|
924
|
+
* `undefined` when no resolved reading is available.
|
|
925
|
+
*
|
|
926
|
+
* `gpuFrameMs` is that duration alone; the frame id is what tells a reading still in flight
|
|
927
|
+
* from the current frame's cost, so a caller building a per-frame series never measures one
|
|
928
|
+
* resolve twice. Optional like `gpuFrameAge`, for stubs that implement only the drawing
|
|
929
|
+
* contract. `createRenderer` always provides it.
|
|
930
|
+
*/
|
|
931
|
+
gpuFrameSample?(): {
|
|
932
|
+
readonly frame: number;
|
|
933
|
+
readonly ms: number;
|
|
934
|
+
} | undefined;
|
|
707
935
|
/** Starts a resolve of the GPU timestamps for the frames drawn since the last call. */
|
|
708
936
|
resolveGpuFrame(): void;
|
|
709
937
|
/**
|
|
@@ -717,6 +945,11 @@ interface IRendererLike {
|
|
|
717
945
|
* session because nothing in the measurement could say which one produced it.
|
|
718
946
|
*/
|
|
719
947
|
surface(): IFrameSurfaceState;
|
|
948
|
+
/**
|
|
949
|
+
* The drawing buffer height on its own, for a caller that wants one number every frame and no
|
|
950
|
+
* record. Optional so a platform or test double can keep exposing only `surface()`.
|
|
951
|
+
*/
|
|
952
|
+
surfaceDrawingBufferHeight?(): number;
|
|
720
953
|
dispose(): void;
|
|
721
954
|
}
|
|
722
955
|
interface IRendererPlatformSource {
|
|
@@ -736,6 +969,7 @@ interface IRendererOptions {
|
|
|
736
969
|
/** Requests multisample antialiasing from the renderer. Defaults to true. */
|
|
737
970
|
antialias?: boolean;
|
|
738
971
|
canvas?: HTMLCanvasElement;
|
|
972
|
+
gpuTimestampFrameInterval?: number;
|
|
739
973
|
preferWebGPU?: boolean;
|
|
740
974
|
/** CSS-pixel multiplier for the drawing buffer. The default is intentional DPR 1. */
|
|
741
975
|
resolutionScale?: number;
|
|
@@ -767,4 +1001,4 @@ interface IRendererOptions {
|
|
|
767
1001
|
}>) => unknown;
|
|
768
1002
|
}
|
|
769
1003
|
|
|
770
|
-
export {
|
|
1004
|
+
export { VELOCITY_PREVIOUS_INSTANCE_MATRICES as $, type IRenderChainStageContext as A, type IRenderChainVelocityMeasurement as B, type IRenderChainVelocityReport as C, DEFAULT_PIPELINE_CENSUS_LIMIT as D, type IRenderChainVelocityRequest as E, type FramePassKind as F, type IRenderChainVelocityResult as G, type IRenderPassSample as H, type IRendererLike as I, type IVelocityRenderPass as J, PIPELINE_CENSUS_VERSION as K, PipelineCensus as L, type PipelineCensusMode as M, type PipelineCensusStatus as N, RENDER_CHAIN_STAGE_ORDER as O, PIPELINE_CENSUS_CAPABILITY as P, RENDER_CHAIN_TIERS as Q, RENDER_CHAIN_MARKER as R, RenderChain as S, type RenderChainSource as T, type RenderChainStageId as U, type RenderChainStageName as V, type RenderChainTier as W, type RenderChainTierRequest as X, type RenderChainVelocitySource as Y, VELOCITY_OUTPUT_NAME as Z, VELOCITY_PREVIOUS_BONE_MATRICES as _, type IFrameBudgetWindow as a, VELOCITY_PREVIOUS_WORLD_MATRIX as a0, VelocityTracker as a1, counterDeviceOf as a2, createPipelineCensus as a3, ensureVelocityOutput as a4, prewarm as a5, readRenderChainObservation as a6, readRenderChainReport as a7, readVelocityPreviousBoneMatrices as a8, readVelocityPreviousMatrices as a9, readVelocityPreviousWorldMatrix as aa, velocityTexture as ab, withVelocityContext as ac, type IRendererOptions as ad, FRAME_BUDGET_MARKER as b, FRAME_BUDGET_PHASES as c, FRAME_HITCH_MARKER as d, FrameBudget as e, type FrameBudgetPhase as f, FrameCounters as g, type ICounterDevice as h, type IFrameBudgetOptions as i, type IFrameBudgetPassSummary as j, type IFrameBudgetSummary as k, type IFrameCounters as l, type IFramePhaseSample as m, type IPipelineCensus as n, type IPipelineCensusCounts as o, type IPipelineCensusEvent as p, type IPipelineCensusOptions as q, type IPipelineProvenance as r, type IPipelineShaderObservation as s, type IRenderChainApplied as t, type IRenderChainBudgetWindow as u, type IRenderChainDroppedStage as v, type IRenderChainOptions as w, type IRenderChainRenderer as x, type IRenderChainRequest as y, type IRenderChainStage as z };
|
package/dist/ui-layer.d.ts
CHANGED
|
@@ -57,6 +57,13 @@ declare const HIT_REGIONS_MESSAGE = "tn:hit-regions";
|
|
|
57
57
|
declare const GAME_STATE_MESSAGE = "tn:state";
|
|
58
58
|
/** The message the UI end sends when the player acts on a control. */
|
|
59
59
|
declare const UI_INTENT_MESSAGE = "tn:intent";
|
|
60
|
+
/**
|
|
61
|
+
* The frame rate the game end publishes when it was launched in dev mode.
|
|
62
|
+
*
|
|
63
|
+
* Separate from the state stream because it is not game state: a HUD that showed it would be
|
|
64
|
+
* showing the engine's own measurement, and a game that never asked for dev mode never sends it.
|
|
65
|
+
*/
|
|
66
|
+
declare const UI_DEV_METRICS_MESSAGE = "tn:dev-metrics";
|
|
60
67
|
/**
|
|
61
68
|
* The intent the UI layer sends once, when its tree has rendered and its rectangles are published.
|
|
62
69
|
*
|
|
@@ -118,16 +125,15 @@ declare function connectUiBridge(options: IConnectOptions): IUiBridge;
|
|
|
118
125
|
* the mirror behaves identically on both, because on web the mirror is fed by the same
|
|
119
126
|
* publication through an in-process channel.
|
|
120
127
|
*
|
|
121
|
-
* Publications are **coalesced**: many
|
|
122
|
-
*
|
|
128
|
+
* Publications are **coalesced**: many simulation writes produce one state message per rendered
|
|
129
|
+
* frame. React consumes that snapshot in the UI realm, independently of the game's render work.
|
|
123
130
|
*/
|
|
124
131
|
/**
|
|
125
132
|
* The minimum a store must offer to be published.
|
|
126
133
|
*
|
|
127
134
|
* `getPublishedState` is optional and preferred when present: ThreeNative's game store keeps a
|
|
128
|
-
* live `getState()` that moves every tick and a `getPublishedState()`
|
|
129
|
-
*
|
|
130
|
-
* every tick, which is the thing React must never do.
|
|
135
|
+
* live `getState()` that moves every tick and a stable `getPublishedState()` snapshot published
|
|
136
|
+
* once per rendered frame, unless the game selected a slower `stateFlushMs` interval.
|
|
131
137
|
*/
|
|
132
138
|
interface IPublishableStore<T> {
|
|
133
139
|
getState(): T;
|
|
@@ -162,7 +168,7 @@ interface IPublishOptions {
|
|
|
162
168
|
* directly would be a second source of truth that only diverges on the platform where the two
|
|
163
169
|
* are actually separate processes.
|
|
164
170
|
* @situation publish game state to a HUD in another realm
|
|
165
|
-
* @situation keep a web and native UI mirror on the same
|
|
171
|
+
* @situation keep a web and native UI mirror on the same coalesced state stream
|
|
166
172
|
* @example const publisher = publishUiState(bridge, store);
|
|
167
173
|
*/
|
|
168
174
|
declare function publishUiState<T>(bridge: IUiBridge, store: IPublishableStore<T>, options?: IPublishOptions): IUiStatePublisher;
|
|
@@ -303,4 +309,4 @@ interface IScopeLike extends IEventTargetLike {
|
|
|
303
309
|
*/
|
|
304
310
|
declare function publishHitRegions(options: IRegistryOptions): IHitRegionRegistry;
|
|
305
311
|
|
|
306
|
-
export { GAME_STATE_MESSAGE, HIT_REGIONS_MESSAGE, type IHitRegion, type IHitRegionRegistry, INTERACTIVE_ATTRIBUTE, type IPublishableStore, type IUiBridge, type IUiMessage, type IUiStateMirror, type IUiStatePublisher, UI_BRIDGE_GLOBALS, UI_INTENT_MESSAGE, UI_READY_INTENT, type UiBridgeEnd, type UiBridgeTransport, connectUiBridge, onUiIntent, publishHitRegions, publishUiState, sendUiIntent, subscribeUiState };
|
|
312
|
+
export { GAME_STATE_MESSAGE, HIT_REGIONS_MESSAGE, type IHitRegion, type IHitRegionRegistry, INTERACTIVE_ATTRIBUTE, type IPublishableStore, type IUiBridge, type IUiMessage, type IUiStateMirror, type IUiStatePublisher, UI_BRIDGE_GLOBALS, UI_DEV_METRICS_MESSAGE, UI_INTENT_MESSAGE, UI_READY_INTENT, type UiBridgeEnd, type UiBridgeTransport, connectUiBridge, onUiIntent, publishHitRegions, publishUiState, sendUiIntent, subscribeUiState };
|
package/dist/ui-layer.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
var HIT_REGIONS_MESSAGE = "tn:hit-regions";
|
|
3
3
|
var GAME_STATE_MESSAGE = "tn:state";
|
|
4
4
|
var UI_INTENT_MESSAGE = "tn:intent";
|
|
5
|
+
var UI_DEV_METRICS_MESSAGE = "tn:dev-metrics";
|
|
5
6
|
var UI_READY_INTENT = "tn:ready";
|
|
6
7
|
var UI_BRIDGE_GLOBALS = {
|
|
7
8
|
/** UI end, inbound: the host calls this with one JSON string. */
|
|
@@ -422,4 +423,4 @@ function isDevelopment() {
|
|
|
422
423
|
return import.meta.env?.DEV === true;
|
|
423
424
|
}
|
|
424
425
|
|
|
425
|
-
export { GAME_STATE_MESSAGE, HIT_REGIONS_MESSAGE, INTERACTIVE_ATTRIBUTE, UI_BRIDGE_GLOBALS, UI_INTENT_MESSAGE, UI_READY_INTENT, connectUiBridge, onUiIntent, publishHitRegions, publishUiState, sendUiIntent, subscribeUiState };
|
|
426
|
+
export { GAME_STATE_MESSAGE, HIT_REGIONS_MESSAGE, INTERACTIVE_ATTRIBUTE, UI_BRIDGE_GLOBALS, UI_DEV_METRICS_MESSAGE, UI_INTENT_MESSAGE, UI_READY_INTENT, connectUiBridge, onUiIntent, publishHitRegions, publishUiState, sendUiIntent, subscribeUiState };
|
package/dist/world.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Object3D, Mesh, LOD, Vector3, Group, BufferGeometry } from 'three';
|
|
2
|
-
import { I as IComputeDriven, a as IGPUReadbackSample } from './gpu-readback-
|
|
3
|
-
import { I as IRendererLike } from './renderer-
|
|
4
|
-
import { I as IAssetLoader } from './assets-
|
|
2
|
+
import { I as IComputeDriven, a as IGPUReadbackSample } from './gpu-readback-CMklJs6r.js';
|
|
3
|
+
import { I as IRendererLike } from './renderer-Cy4qeBOA.js';
|
|
4
|
+
import { I as IAssetLoader } from './assets-CYKk2WTu.js';
|
|
5
5
|
import 'three/webgpu';
|
|
6
6
|
|
|
7
7
|
interface IWorldErosionOptions {
|
package/mcp/blender-server.mjs
CHANGED
|
@@ -424,7 +424,7 @@ async function runRecipe(name, request, options = {}) {
|
|
|
424
424
|
|
|
425
425
|
// src/index.ts
|
|
426
426
|
var SERVER_NAME = "threenative-blender-mcp";
|
|
427
|
-
var SERVER_VERSION = "0.1.
|
|
427
|
+
var SERVER_VERSION = "0.1.3";
|
|
428
428
|
var AUTHORING_INSTRUCTIONS = "Call blender_status before anything else: it answers whether this machine can convert models at all, and when it cannot it names the install command rather than failing. Blender is never installed for the user \u2014 ask first, then let them run the command. An .fbx, .blend, .obj or .dae placed in a game's assets directory is converted by the build itself; these tools are for inspecting a source before committing it and for operations the build does not perform. For anything no named tool covers, read a shipped recipe with blender_recipes and adapt it, then run it with blender_run_python.";
|
|
429
429
|
var TOOL_DEFINITIONS = [
|
|
430
430
|
{
|